.gitignore Tester
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.
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 -v` prints, 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.
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.
.gitignore Generator
Tick the languages, frameworks, editors, and operating systems you use — get one combined `.gitignore` ready to drop into a fresh repo.
Glob Pattern Tester
Test file paths against a glob pattern (*, **, ?, [...], {a,b}) in your browser.
WebAssembly Module Inspector
Inspect a .wasm file in your browser: section sizes, imports and exports with full types, memory pages, the start function, name and producers sections.
JSON Schema Validator
Check a JSON document against a JSON Schema in your browser. Every error gets its JSON Pointer, the line it lands on, and the keyword that failed.
Patch / Diff Inspector
Read a unified diff or .patch file: files touched, insertions and deletions per file, and the malformed hunks that make git apply fail.