Skip to content

A negation under an excluded directory takes effect; git says it cannot #25

Description

@KaizenShogun

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.

Activity

  1. KaizenShogun commented on Sep 11, 2026

    @KaizenShogun
    ContributorAuthor

    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.

    Repro, three lines:

    mkdir -p t/.vscode && cd t && git init -q
    printf '.vscode\n!.vscode/extensions.json\n' > .gitignore
    touch .vscode/extensions.json && git check-ignore -v .vscode/extensions.json

    git names .gitignore:1:.vscode; the library says not ignored.

  2. added a commit that references this issue on Sep 12, 2026
    66f08d2
  3. andrew commented on Sep 12, 2026

    @andrew
    Contributor

    Fixed alongside #26 in #27 by checking each parent directory before the full path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions