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:
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
What version of ripgrep are you using?
How did you install ripgrep?
The
x86_64-unknown-linux-musltarball 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
.gitignoreline consisting of a bare!compiles to the glob**/marked as a whitelist. Sincethat 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 otherwisenever descends into.
git treats the same line as a no-op: after stripping the
!there is no pattern left, so nothing isnegated 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?
What is the actual behavior?
original: "!"→actual: "**/",is_whitelist: trueis the whole bug in one line.Dropping the
grep -vshows the other half: the full listing includes.git/config,.git/HEAD,every
.git/hooks/*.sampleand so on. The whitelist outranks the.gitfilter too.What is the expected behavior?
a/.venvnot listed, and.git/still skipped — a bare!should leave no pattern behind, the waygit handles it.
Some controls, so the report is about the right thing
Same tree, only
a/.gitignorechanges.a/.venvis the inherited-rule query;a/x.tmpis one onlya/.gitignorecan decide.a/.gitignorea/.venvgit / rga/x.tmpgit / rg#c*.tmp**local.properties!!then*.tmp*.tmpthen!Two things worth pulling out of that table:
**local.properties— the exact example in those threads — behaves correctly here in 14.1.1. Thebare
!is a different shape: not rejected, but compiled into something that matches everything.entry and confirms it is being treated as one:
!after*.tmpcancels*.tmp,!before itdoesn't.
A nested
a/b/.venvgoes the same way asa/.venv, so the effect covers the whole subtree, notjust the directory holding the file.
Where I hit it
I maintain a conformance bench that replays real repositories'
.gitignoretrees againstgit check-ignoreas the oracle. ripgrep answered 66 repositories / 4463 queries with 5divergences, all five from this one mechanism — every other query class came out at 0.0%. The
five all came from
psf/black, whichcarries a one-line
!intests/data/invalid_nested_gitignore_tests/a/.gitignoreas a fixture forexactly this kind of file.
Bench, corpus and the adapter: https://github.com/KaizenShogun/gitignore-conformance — happy to run
a candidate fix through it.
— Midas