A bare "!" line in a .gitignore compiles to a whitelist matching everything, re-including the subtree (and .git/)
What version of ripgrep are you using?
ripgrep 14.1.1 (rev 4649aa9700)
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.43 is available (JIT is available)
How did you install ripgrep?
The x86_64-unknown-linux-musl tarball from the 14.1.1 release page.
What operating system are you using ripgrep on?
Linux (x86_64), git 2.55.0 as the reference.
Describe your bug.
A .gitignore line consisting of a bare ! compiles to the glob **/ marked as a whitelist. Since
that matches everything, it re-includes the entire subtree below the file it appears in — including
paths excluded by an ancestor .gitignore, and including .git/ itself, which ripgrep otherwise
never descends into.
git treats the same line as a no-op: after stripping the ! there is no pattern left, so nothing is
negated and the parent's rules keep applying.
Nothing is printed on stderr; the only visible trace is under --debug.
What are the steps to reproduce the behavior?
d=$(mktemp -d); cd "$d"
git init -q
mkdir a
printf '.venv\n' > .gitignore # root .gitignore excludes .venv
printf '!\n' > a/.gitignore # a bare "!" — git makes nothing of this line
touch a/.venv
git check-ignore -v a/.venv
rg --files --hidden | grep -v '^\.git/'
rg --files --hidden --debug 2>&1 >/dev/null | grep 'whitelisting ./a/.venv'
What is the actual behavior?
$ git check-ignore -v a/.venv
.gitignore:1:.venv a/.venv
$ rg --files --hidden | grep -v '^\.git/'
a/.gitignore
a/.venv
.gitignore
$ rg --files --hidden --debug 2>&1 >/dev/null | grep 'whitelisting ./a/.venv'
rg: DEBUG|ignore::walk|crates/ignore/src/walk.rs:1802: whitelisting ./a/.venv: Whitelist(IgnoreMatch(Gitignore(Glob { from: Some("./a/.gitignore"), original: "!", actual: "**/", is_whitelist: true, is_only_dir: false })))
original: "!" → actual: "**/", is_whitelist: true is the whole bug in one line.
Dropping the grep -v shows the other half: the full listing includes .git/config, .git/HEAD,
every .git/hooks/*.sample and so on. The whitelist outranks the .git filter too.
What is the expected behavior?
a/.venv not listed, and .git/ still skipped — a bare ! should leave no pattern behind, the way
git handles it.
Some controls, so the report is about the right thing
Same tree, only a/.gitignore changes. a/.venv is the inherited-rule query; a/x.tmp is one only
a/.gitignore can decide.
a/.gitignore |
a/.venv git / rg |
a/x.tmp git / rg |
|---|---|---|
| (no file) | ignored / ignored | not / not |
#c |
ignored / ignored | not / not |
*.tmp |
ignored / ignored | ignored / ignored |
**local.properties |
ignored / ignored | not / not |
! |
ignored / not | not / not |
! then *.tmp |
ignored / not | ignored / ignored |
*.tmp then ! |
ignored / not | ignored / not |
Two things worth pulling out of that table:
- This is not the general "ripgrep rejects a pattern git accepts" complaint from #373 / #646 / #945.
**local.properties— the exact example in those threads — behaves correctly here in 14.1.1. The bare!is a different shape: not rejected, but compiled into something that matches everything. - Last-match-wins makes the position matter, which is what you would expect from a real whitelist
entry and confirms it is being treated as one:
!after*.tmpcancels*.tmp,!before it doesn't.
A nested a/b/.venv goes the same way as a/.venv, so the effect covers the whole subtree, not
just the directory holding the file.
Where I hit it
I maintain a conformance bench that replays real repositories' .gitignore trees against
git check-ignore as the oracle. ripgrep answered 66 repositories / 4463 queries with 5
divergences, all five from this one mechanism — every other query class came out at 0.0%. The
five all came from psf/black, which
carries a one-line ! in tests/data/invalid_nested_gitignore_tests/a/.gitignore as a fixture for
exactly this kind of file.
Bench, corpus and the adapter: https://github.com/KaizenShogun/gitignore-conformance — happy to run a candidate fix through it.
— Midas
Source: BurntSushi/ripgrep