You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A negation under an excluded directory takes effect; git says it cannot #25
gitignore(5): "It is not possible to re-include a file if a parent directory of that file is
excluded." The library re-includes it.
.gitignore: .vscode
!.vscode/ext
.vscode/ext git: ignored this library: NOT ignored
.vscode/ext/keep git: ignored this library: NOT ignored
.vscode excludes the directory, so git never walks into it and the ! line has no effect at all
— not on the directory, not on anything under it. match walks the pattern list backwards and
returns on the first hit, so the later negation wins instead of never being reached.
Note the exclusion has no trailing slash and still excludes the directory; !.vscode/ext/ behaves
the same way, so it isn't about dir-only patterns.
Found by an outside bench (33 repos' real rule files, 8,953 nested-.gitignore queries, git check-ignore as the oracle), where it accounts for 4 divergences — ollama's app/ui/app/.gitignore
has !.vscode/extensions.json under a root .vscode. Separate from #23 and from the negation
inheritance in the issue I'm filing next; this one survives removing the literalSuffix
fast-reject entirely.
The mechanism, measured rather than reasoned about. I asked the library why, with MatchDetail,
and asked git with check-ignore -v, on the same three trees. Only the first line changes:
rules
path
git
this library
.vscode + !.vscode/extensions.json
.vscode/extensions.json
ignored (.vscode)
not ignored (!.vscode/extensions.json)
.vscode/ + same negation
same
ignored (.vscode/)
not ignored (!.vscode/extensions.json)
.vscode/* + same negation
same
not ignored (!.vscode/extensions.json)
not ignored (!.vscode/extensions.json)
The library answers the same thing three times; git answers two different things. That is the whole
bug: Match picks the last pattern that matches the full path, so it never learns that an ancestor
was excluded. git decides per directory and stops descending into one it has excluded, which is why .vscode/* — one segment deep, excluding the contents and not the directory — is the idiom that
actually works.
Worth separating from #26, because the fix points the other way. #26 is the implicit ** reaching down from a negation; here nothing reaches down far enough — a negation needs to be dropped when
an excluded ancestor sits above it. Patching one won't touch the other.
Activity
KaizenShogun commentedon Sep 11, 2026
The mechanism, measured rather than reasoned about. I asked the library why, with
MatchDetail,and asked
gitwithcheck-ignore -v, on the same three trees. Only the first line changes:.vscode+!.vscode/extensions.json.vscode/extensions.json.vscode)!.vscode/extensions.json).vscode/+ same negation.vscode/)!.vscode/extensions.json).vscode/*+ same negation!.vscode/extensions.json)!.vscode/extensions.json)The library answers the same thing three times; git answers two different things. That is the whole
bug:
Matchpicks the last pattern that matches the full path, so it never learns that an ancestorwas excluded. git decides per directory and stops descending into one it has excluded, which is why
.vscode/*— one segment deep, excluding the contents and not the directory — is the idiom thatactually works.
Worth separating from #26, because the fix points the other way. #26 is the implicit
**reachingdown from a negation; here nothing reaches down far enough — a negation needs to be dropped when
an excluded ancestor sits above it. Patching one won't touch the other.
Repro, three lines:
git names
.gitignore:1:.vscode; the library says not ignored.Check parent directories in Match and add git check-ignore conformanc…
andrew commentedon Sep 12, 2026
Fixed alongside #26 in #27 by checking each parent directory before the full path.