.gitignore Tester
Paste a .gitignore and a list of paths to see which are ignored and exactly which line decided — the answer git check-ignore -v gives.
Ignored
5
Not ignored
3
Rules
7
Dead rules
1
Git does not descend into an excluded directory, so a ! rule below one is never consulted. Exclude the contents (dir/*) instead of the directory (dir/) to make it work.
- .vscode/settings.json — !.vscode/settings.json (Line 10) · via excluded parent .vscode/
| Path | Verdict | Line | Deciding rule |
|---|---|---|---|
| src/index.ts | Not ignored | — | No rule matched |
| node_modules/react/index.js | Ignored | 2 | node_modules/via excluded parent node_modules/ |
| dist/ | Ignored | 3 | dist/ |
| dist/app.js | Ignored | 3 | dist/via excluded parent dist/ |
| debug.log | Not ignored | 6 | !debug.log |
| logs/debug.log | Not ignored | 6 | !debug.log |
| server.log | Ignored | 5 | *.log |
| .vscode/settings.json | Ignored | 9 | .vscode/via excluded parent .vscode/this ! rule cannot take effect |
None of the paths above matched these lines. On a representative path list they are candidates for deletion.
- Line 4 coverage/
Matching follows git on a case-sensitive filesystem, for one .gitignore at the repository root.
How to use
- Paste your .gitignore into the first box, exactly as it is on disk — comments, blank lines, CRLF endings and a byte-order mark are all handled the way git handles them.
- List the paths you want to test in the second box, one per line, relative to the repository root. End a line with a slash to say that path is a directory: dir-only rules like
logs/depend on it. - Read the table. Each row gives the verdict, the line number that decided and the rule text — the same three facts
git check-ignore -vprints, plus the excluded parent when there is one. - Check the amber panel. It appears when a
!rule matches a path but cannot take effect because a parent directory is already excluded, which is the usual reason a negation looks right and does nothing. - Look at the dead-rule list at the bottom: rules that matched none of the paths you pasted. Against a representative path list, those are the lines worth deleting.
About this tool
ShowHide
A .gitignore is an ordered list of patterns and the LAST one that matches a path wins, so the answer to "why is this file ignored?" is very often not the line you are staring at. This tester takes the file and a list of paths and answers it the way git check-ignore -v does: for every path, the verdict, the line number, and the text of the pattern that decided, plus the parent directory when the decision came from one instead of from the file itself.
That last part is where most .gitignore bugs live. Git never descends into an excluded directory, so once build/ is ignored, !build/keep.txt is dead text — the file is judged through its parent, and the negation is never even consulted. The tester flags exactly that case rather than leaving you to guess, and it also lists rules that matched none of the paths you pasted, which is how a stale Thumbs.db entry or a coverage/ directory you renamed years ago finally gets noticed.
The matcher follows git's own reading of the format rather than a shell glob: a pattern with no slash matches a basename at any depth, a pattern with a slash in it is anchored to the directory holding the .gitignore, a trailing slash restricts it to directories, * stops at a slash while a ** that occupies a whole path segment crosses one, character classes such as [0-9] and [!a-z] work, trailing spaces are dropped unless escaped with a backslash, a leading # or ! can be escaped, and a UTF-8 BOM or CRLF endings are stripped before anything is parsed. Because a pasted list of paths carries no file types, mark directories with a trailing slash: build/ is judged as a directory and build as a file, and that alone flips the answer for every dir-only rule.
Two things it cannot tell you. It reads one .gitignore, so per-directory files deeper in the tree, .git/info/exclude and the global core.excludesFile are outside the picture — and each of those can override what you see here. And it matches case-sensitively, like git on Linux, so a repository with core.ignorecase set on macOS or Windows may ignore more than this table shows. Everything runs in the browser; neither the file nor the path list is uploaded.
Frequently asked questions
Why does my ! rule not bring the file back?
Because git never looked at the file. When it walks the tree and finds that a directory is excluded, it stops there and does not descend, so no rule about anything inside that directory is ever evaluated — the documentation states it plainly: "It is not possible to re-include a file if a parent directory of that file is excluded." With logs/ and !logs/important.log, the log stays ignored, and this tester shows the decision coming from the directory rule. The fix is to exclude the contents instead of the directory: logs/* followed by !logs/important.log works, because logs/* matches the entries inside logs without excluding logs itself. For a whole subtree the recipe is /logs/**, then !/logs/keep/, then !/logs/keep/**.
What is the difference between build and /build?
A pattern with no slash anywhere in it is matched against the basename of every path, at every depth, so build ignores /build, /src/build and /a/b/c/build alike. As soon as the pattern contains a slash that is not its last character, it is anchored to the directory containing the .gitignore, so /build and docs/output only ever match from the root of that directory downward. This is why a bare node_modules in a root .gitignore also hides a nested package's node_modules — usually what people want, but not what they wrote down on purpose. The leading slash is the way to say "only here".
Does logs/ match a file named logs?
No. A trailing slash means the pattern only matches directories, so a plain file called logs is left alone while a directory called logs, at any depth, is ignored together with everything under it. This matters here because a pasted list of paths carries no type information: git decides by looking at the real filesystem, and this tester has to be told. Write logs/ in the path box for the directory and logs for the file — the same string with and without the slash can get opposite verdicts, and that is not a quirk of the tool but the reason git check-ignore sometimes disagrees with what people expect when the path does not exist yet.
What do the three forms of ** actually mean?
A ** only has special meaning when it occupies a whole path segment. **/foo at the start matches foo at any depth, and is the explicit form of what a slashless pattern already does. A trailing foo/** matches everything inside foo — but not foo itself, which is exactly why the re-include recipe above works. And a/**/b in the middle matches a/b, a/x/b and a/x/y/b, zero or more directories included. Anywhere else, the stars are ordinary: a**b is just a*b, and a single * never crosses a slash, so src/*.c matches src/main.c but not src/lib/main.c.
The rule looks right, but git still shows my file as modified.
.gitignore only ever applies to files git is not already tracking. Once a file has been committed, adding a pattern for it changes nothing: git keeps reporting its changes and keeps committing them. Untrack it first with git rm --cached path (which leaves the working copy in place) and commit that removal; from the next commit on, the ignore rule governs. This tester answers the untracked-file question — the one check-ignore answers — so a path it calls ignored can still show up in git status if it is in the index, and that is the first thing to check when the two disagree.
Which .gitignore wins when there are several?
Git reads several sources in a fixed order and the more specific one wins: patterns from the command line, then .gitignore files, with a file in a deeper directory overriding one above it, then .git/info/exclude for repository-local rules that are not shared, and finally the global file named by core.excludesFile. Within one file the last matching line decides. This tester models a single .gitignore, so if its verdict disagrees with your repository, a nested .gitignore, an info/exclude entry or your global file is the likely culprit — run git check-ignore -v there and the source column will name the file.
Related tools
EditorConfig Tester
Paste an .editorconfig and some file paths: see the property set each file resolves to, and which section on which line decided every value.
Developer
.gitattributes Tester
Paste a .gitattributes and a list of paths to see every attribute git ends up setting on each path, and which line — or which macro — set it.
Developer
.mailmap Tester
Paste a .mailmap and a list of author identities to see what each one resolves to, which entry did it, and which people finally merge into one.
Developer
.gitignore Generator
Tick the languages, frameworks, editors, and operating systems you use — get one combined .gitignore ready to drop into a fresh repo.
Developer
Git Config Precedence Tester
Paste your git config files and see which value wins for every key, the scope, file and line it came from, what it overrode, and which includeIf fired.
Developer
Glob Pattern Tester
Test file paths against a glob pattern (*, **, ?, [...], {a,b}) in your browser.
Developer