Skip to content

fix: color names ignore case, and bright requires a standard color, like in Git. - #2863

Merged
Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
shuvamk:fix/color-name-case-and-bright-prefix
Aug 1, 2026
Merged

fix: color names ignore case, and bright requires a standard color, like in Git.#2863
Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
shuvamk:fix/color-name-case-and-bright-prefix

Conversation

@shuvamk

Copy link
Copy Markdown
Contributor

Written by an AI agent (Claude) operating through the shuvamk account, per the agent impersonation policy.

What's wrong (AI)

gix_config_value::color::Name::from_str() compares color names with == against lowercase
literals and strips the bright prefix with a case-sensitive strip_prefix("bright"). Git
compares them with strncasecmp (match_word() in color.c), so every spelling Git accepts
that is not all-lowercase is rejected by gix.

Measured with git version 2.50.1 (Apple Git-155) against gix at 77dc1ff87, using
printf '[color]\n\ttest = "%s"\n' "$input" > f; git config --file f --get-color color.test
on one side and gix_config_value::Color::try_from(input) on the other:

input git --get-color Color::try_from on main with this PR
RED ESC[31m error red
rEd ESC[31m error red
NoRmAl (empty — i.e. normal) error normal
DEFAULT ESC[39m error default
BLACK ESC[30m error black
BRIGHTRED ESC[91m error brightred
BrightRed ESC[91m error brightred
brightRED ESC[91m error brightred
RED brightBLUE bold ESC[1;31;104m error red brightblue bold

Second, from_str() strips bright and then falls through to the ANSI and hex fallbacks
without consulting the flag again, so a bright prefix on anything that is not one of the
eight standard colors is silently dropped instead of being an error:

input git --get-color Color::try_from on main with this PR
bright0 error: invalid color value: bright0 0 error
bright1 error: invalid color value: bright1 1 error
bright255 error: invalid color value: bright255 255 error
bright#ff0010 error: invalid color value: bright#ff0010 #ff0010 error

Both have been there since the code was moved into this crate in edb1162e28 (#450) — git blame shows every line of the match block still at that commit — and a search of the issue
tracker for colour parsing turned up nothing that chooses this behaviour deliberately.

Over the 67 inputs I probed, gix disagrees with git on 28 before this change and on
3 after — see the last section for the three.

What this does

Name::from_str() now matches the eight standard colors from a table with
eq_ignore_ascii_case(), mirroring color_names[] in Git's color.c, and a bright prefix
that does not resolve against that table is an error rather than falling through to the ANSI
and hex parsers. normal and default are matched case-insensitively too, and -1 keeps its
meaning as an alias for normal. The prefix test uses split_at_checked(), so an input whose
multi-byte character straddles byte 6 still cannot panic (f9d566f82) — I checked brighét,
brigh€x and brighté, all of which return an error.

What deliberately does not change: attributes stay case-sensitive, with one exception
documented below, because Git's parse_attr() compares them with memcmpgit config --get-color rejects BOLD, NOBOLD and NO-BOLD, and so does gix, before and after.

One incidental improvement: an error now reports the whole input rather than the remainder
after bright was stripped, so brightfoo reports brightfoo instead of foo.

eq_ignore_ascii_case() is already how the sibling boolean.rs in this crate matches
yes/on/true.

Alternative you might prefer

A match on string literals cannot be made case-insensitive, so the other way to write this is
to keep all 24 match arms and lowercase the input once up front with s.to_ascii_lowercase().
That is a smaller textual diff, at the cost of one allocation on every Name::from_str() call;
I chose the table because it allocates nothing and AGENTS.md asks plumbing crates to avoid
avoidable copies. Say the word and I will switch it over.

Tests

Three new tests in gix-config-value/tests/value/color.rs, and all three fail without the
source change
. Reverting only gix-config-value/src/color.rs and keeping the tests:

test color::from_git::color_names_ignore_case ... FAILED
test color::name::any_case ... FAILED
test color::name::bright_only_applies_to_standard_colors ... FAILED

---- color::name::any_case stdout ----
assertion `left == right` failed: "RED": color names and the 'bright' prefix are case-insensitive, like in Git
  left: Err(Error { message: "Colors are specific color values and their attributes, like 'brightred', or 'blue'", input: "RED", utf8_err: None })
 right: Ok(Red)

---- color::name::bright_only_applies_to_standard_colors stdout ----
"bright0": 'bright' may only precede one of the eight standard colors, like in Git

---- color::from_git::color_names_ignore_case stdout ----
input color is expected to be valid: Error { message: "Colors are ...", input: "RED brightBLUE bold", utf8_err: None }

test result: FAILED. 22 passed; 3 failed

With the source restored: test result: ok. 25 passed; 0 failed. No existing assertion was
modified.

What I ran

just and cargo-nextest are not installed here, so these are the raw cargo invocations,
on rustc 1.95.0 (workspace MSRV is 1.85):

command result
cargo test -p gix-config-value integration suite 51 → 54 passed, 0 failed
cargo test -p gix-config -p gix 806 passed, 0 failed across 12 suites
cargo fmt --all -- --check clean
cargo clippy --workspace --all-targets -- -D warnings -A unknown-lints -A unfulfilled_lint_expectations rc=0, 0 warnings
cargo doc -p gix-config-value --no-deps ok

cargo test --workspace --no-fail-fast on the branch: 181 suites, 3673 passed, 2 failed, 22
ignored
. The two failures are gix-date's parse::fuzz::artifact_inputs_can_be_parsed_without_panicking
and gix-imara-diff's packaged_files_have_matching_provenance_and_modified_files_have_notices,
both Os { code: 2, kind: NotFound } on files that are present in the checkout; both pass when
their crate is run on its own, and cargo tree -e normal,build,dev shows no path from either
crate to gix-config-value. The same run on unmodified main @ 77dc1ff87 failed 9 tests
of the same shape, so this is noise from my machine rather than anything on either side of the
diff. -A unfulfilled_lint_expectations is needed because clean main reports three of those
in gix-ref/src/lib.rs on 1.95.0.

One divergence I did not fix

Git also matches the reset attribute case-insensitively — it is special-cased with
strncasecmp while every other attribute goes through memcmp — and gix still rejects it:

input git --get-color gix, before and after this PR
RESET ESC[m error
Reset ESC[m error
red RESET ESC[;31m error

Those are the 3 remaining mismatches out of 67. I left them out because they live in
Attribute::from_str() rather than in the name parser and would look like an arbitrary
exception among the case-sensitive attributes. Happy to fold a one-line fix for it into this
PR, or send it separately — whichever you prefer.

Shuvam Kumar (shuvamk) and others added 2 commits August 1, 2026 21:18
… like in Git.

`Name::from_str()` compared color names with `==` against lowercase literals and
stripped the `bright` prefix with a case-sensitive `strip_prefix()`, so every
spelling Git accepts but that is not all-lowercase was rejected. Measured against
`git config --file <f> --get-color color.test` with git 2.50.1 (Apple Git-155):

    input                 git                       gix before   gix after
    RED                   ESC[31m                   error        red
    NoRmAl                (empty, i.e. normal)      error        normal
    DEFAULT               ESC[39m                   error        default
    BrightRed             ESC[91m                   error        brightred
    brightRED             ESC[91m                   error        brightred
    RED brightBLUE bold   ESC[1;31;104m             error        red brightblue bold

The same function stripped `bright` and then fell through to the ANSI and hex
fallbacks without consulting the flag again, so a `bright` prefix on anything
that is not one of the eight standard colors was silently dropped:

    input                 git                       gix before   gix after
    bright0               invalid color value       0            error
    bright1               invalid color value       1            error
    bright255             invalid color value       255          error
    bright#ff0010         invalid color value       #ff0010      error

The eight standard colors are matched from a table with `eq_ignore_ascii_case()`,
mirroring `color_names[]` in Git's `color.c`, and a `bright` prefix that did not
resolve there is now an error instead of falling through. `normal`, `default` and
`-1` keep their existing meaning; attributes stay case-sensitive, which is what
Git does too (`git config --get-color` rejects `BOLD`, `NOBOLD` and `NO-BOLD`).

Errors now report the whole input rather than the remainder after `bright` was
stripped.

Over 67 probed inputs, gix disagreed with git on 28 before and disagrees on 3
after; the three are `RESET`, `Reset` and `red RESET`, because Git also matches
the `reset` attribute case-insensitively. That is left alone here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assisted-by: GPT 5.6
Co-authored-by: GPT 5.6 <codex@openai.com>
@Byron

Copy link
Copy Markdown
Member

Thanks a lot, much appreciated!

@Byron
Sebastian Thiel (Byron) merged commit da5fa73 into GitoxideLabs:main Aug 1, 2026
32 checks passed
@shuvamk
Shuvam Kumar (shuvamk) deleted the fix/color-name-case-and-bright-prefix branch August 1, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants