Gitignore tester: is this path ignored, and by which rule?

Paste your .gitignore files and the paths you are unsure about. This answers with Git's own rules, names the line that decided, and explains the one that catches everyone — a file inside an ignored directory cannot be re-included.

Runs entirely in your browser. Your data is never uploaded.

Write each path relative to the repository root, as git check-ignore wants it. End a line with / when the path is a directory — Git treats directories differently, so it changes the answer.

Examples

    How Git decides, in order

    Almost every surprise comes from step 1, and almost every online tester skips it: they match each pattern against the path and take the last one that hits. Git does something else first.

    1. Walk the parent directories, from the repository root down. The moment one of them is ignored, that is the answer, and Git stops. It does not descend into an ignored directory at all, so no rule below can re-include anything inside it.
    2. Then look at the path itself. Git consults the .gitignore files that govern it, deepest directory first, and stops at the first file that has any matching pattern. A shallower file is then not consulted at all, whatever it says.
    3. Inside that one file, the last matching line wins. This is the rule everyone knows, and it only applies once the first two steps have not already decided.
    4. A pattern ending in / matches directories only — and by step 1, that takes everything inside them too.

    Pattern syntax

    Every row is checked by a test against the same engine the tool above uses, so this table cannot drift from the behaviour.

    Pattern Meaning Ignores Leaves alone
    build No slash inside, so it matches a file or directory of that name at any depth. build, src/build/, a/b/build buildfile, src/build.ts
    build/ A trailing slash makes it match directories only — and everything inside them. build/, build/out.o, src/build/out.o build, buildfile
    /build A leading slash anchors the pattern to the directory of the .gitignore file. build, build/out.o src/build
    src/*.js A slash inside also anchors it. `*` matches any run of characters except a slash. src/app.js src/nested/app.js, app.js
    **/node_modules A leading `**/` means "in any directory" — the default for a pattern with no slash. node_modules/, app/node_modules/pkg/index.js my_node_modules
    dist/** A trailing `/**` matches everything inside a directory, but not the directory itself. dist/main.js, dist/a/b/main.js dist/
    logs/**/*.log A `/**/` between slashes stands for zero or more directories. logs/f.log, logs/a/b/f.log logs/f.txt, f.log
    ?.txt `?` matches exactly one character, and never a slash. a.txt, sub/b.txt ab.txt
    [abc].log A bracket expression matches one character from the set; ranges and `[!…]` work too. a.log, c.log d.log, ab.log
    !keep.txt A leading `!` re-includes a path an earlier line ignored — unless a parent directory is gone. keep.txt
    \#notes A backslash escapes a leading `#` or `!`, and any wildcard, so it is taken literally. #notes notes

    Results that surprise people

    Each of these is the verdict this tool gives, verified by a test. Paste any of them above to see the deciding line.

    .gitignore Path Result Why
    docs/ !docs/keep.md docs/keep.md Ignored — line 1: docs/ Git never looks inside an ignored directory, so line 2 is never even read. This is the single most common “why is my file still ignored?”.
    docs/* !docs/keep.md docs/keep.md Not ignored — line 2: !docs/keep.md `docs/*` leaves the directory itself alone, so Git still descends into it and line 2 gets its say.
    /* !/src src/main.ts Not ignored — no rule matched `/*` matches only top-level names — `*` never crosses a slash — so nothing inside `src` was ignored in the first place.
    build/ buildfile Not ignored — no rule matched `build/` matches a directory called build at any depth. `buildfile` is neither that directory nor inside it.
    *.log !sub/keep.log sub/keep.log Not ignored — line 2: !sub/keep.log With only this root file the last matching line wins, so the file is kept. Add a `sub/.gitignore` containing `*.log` and the answer flips: the deepest file with any match decides on its own, and the root file — including line 2 — is not consulted at all.
    logs logs Ignored — line 1: logs The space is not part of the pattern unless you escape it as `logs\ `. To match a file whose name really ends in a space, you must.
    src/*.js src/nested/app.js Not ignored — no rule matched Write `src/**/*.js` if you meant every .js file below src.

    When this tool and git status disagree

    There are three reasons, and none of them is a bug in the patterns:

    The same answer from the command line

    In a repository, git check-ignore -v path/to/file prints the deciding file, line number and pattern, and says nothing at all when the path is not ignored. Add -n to get a line for non-matching paths too, and --no-index to ask about paths that are already tracked. That command is where this tool's test vectors come from.

    About this tool

    The matching is a TypeScript implementation of the rules in gitignore(5) — anchoring, the directory-only slash, *, ?, bracket expressions with POSIX classes, ** at the edges of a pattern, backslash escapes, trailing-space trimming, negation, nested files, and the parent-directory rule. It is pinned by 147 golden vectors recorded from real git check-ignore output (git 2.43.0) across 13 hand-built repositories, and cross-checked by a differential fuzz that puts random .gitignore files and random paths through both this engine and the git binary: roughly 20,000 comparisons over three seeds, with no disagreement. Both the vectors and the fuzz compare the verdict and the file, line and pattern that decided.

    No Git source code is copied: Git is GPL-2.0 and this project is not, so the behaviour was implemented from the documentation and then verified against the program. Everything runs in your browser — the .gitignore files and paths you paste are never uploaded.