← Gitignore Debugger

Gitignore Not Working? Here's How to Fix It

A .gitignore rule that plainly should work, and plainly doesn't, is one of Git's more maddening five minutes. The good news is that the cause is nearly always one of a short list of things — and Git will tell you which, if you ask it the right way.

The 30-second triage. Rungit check-ignore -v path/to/file. If it names a rule, that rule is the one deciding — read it and jump to the matching section below. If it prints nothing, no rule matches at all, and the file is either already tracked or your pattern isn't matching the path you think it is.

1. The file is already tracked

This is the single most common cause, and it is not a pattern bug at all..gitignore governs which untracked files Git notices. Once a file is in the index — because someone committed it before the rule existed — Git considers the matter settled and stops consulting the ignore rules for it entirely. The rule you just wrote is correct, and inert.

Check whether that's what you're looking at:

git ls-files --error-unmatch path/to/file

If that prints the path, the file is tracked. The fix is to remove it from the index while leaving it on disk:

git rm --cached path/to/file
git commit -m "Stop tracking path/to/file"

For a directory, add -r:git rm -r --cached path/to/folder. If a long-neglected ignore file has accumulated many such files, you can re-apply the whole thing at once — but only with a clean working tree, since it restages every file in the repository:

git rm -r --cached .
git add .
git commit -m "Apply .gitignore"

One caveat worth knowing before you push: for everyone else, that commitdeletes the file from their working copy when they pull. That is usually what you want for build output, and emphatically not what you want for a config file each developer maintains locally. More detail on this case lives on.gitignore not working on an already-tracked file.

2. The pattern isn't matching what you think

Assuming the file is genuinely untracked, the pattern is the next suspect. The rules that catch people most often:

Why a .gitignore pattern isn't working works through these one at a time with examples.

3. The .gitignore is in the wrong place

Patterns are relative to the directory containing the.gitignore, which makes location part of the rule's meaning. A rule reading /config.json in the repository root matches only the root file; the same line insidesrc/ matches only src/config.json.

Things to rule out here:

Git also reads rules from two places that aren't a.gitignore at all: .git/info/exclude, which is repo-local and never committed, and your global excludes file. A rule you cannot find anywhere in the repository is often in one of them:

git config --get core.excludesFile

4. Wildcards don't cross directory boundaries

* matches any run of characters except the directory separator. This is the single most common reason a rule under-matches. Against docs/api/notes.md:

docs/*.md      → no match; * will not cross the slash in api/notes.md
docs/**/*.md   → matches

The full set is smaller than people expect:

Anywhere else, a doubled asterisk is just * — sofoo**bar buys you nothing over foo*bar.

5. Folder rules and the trailing slash

A trailing slash makes a rule directory-only:build/ matches a directory namedbuild and never a file of that name, whilebuild matches either. If a stray file namedbuild is what you're trying to catch, the slash is what's stopping you.

The more common surprise is the other direction. Once Git ignores a directory it does not descend into it, so nothing inside can be reconsidered — including by a negation on the very next line:

build/
!build/keep.txt

$ git check-ignore -v build/keep.txt
.gitignore:1:build/    build/keep.txt      ← line 2 never fires

Ignore the directory's contents instead, which leaves the directory itself un-ignored and therefore walkable:

build/*
!build/keep.txt

$ git check-ignore -v build/keep.txt
.gitignore:2:!build/keep.txt    build/keep.txt   ← the negation wins

The same shape is how you keep an otherwise-empty directory in the repository — logs/* followed by!logs/.gitkeep. Seewhen a .gitignore folder rule isn't working for the variations.

6. Negation (!) and rule precedence

Two things about ! account for nearly every negation that appears broken. The first is order: within a.gitignore, the last matching pattern decides, not the first. Identical rules in the opposite order give opposite results for important.log:

*.log
!important.log        → important.log is committed

!important.log
*.log                 → important.log is ignored

So broad rules first, exceptions after them. The second is the directory rule from the previous section: a negation cannot rescue a file out of an ignored directory, because Git never looks in there to find it. To whitelist deliberately, re-include each level on the way down:

# ignore everything at the top level...
/*
# ...except these
!/src
!/package.json
!/.gitignore

/* matches only top-level entries, so re-including/src is enough to make Git walk into it and pick up everything beneath.

7. A nested .gitignore is overriding yours

Rules from a .gitignore in a subdirectory take precedence over rules from one higher up, for the paths beneath it. A single forgotten file three directories down can quietly countermand the root ignore file — and because the rule that wins lives somewhere you weren't looking, this one can burn real time:

.gitignore                 contains  *.log
src/logs/.gitignore        contains  !debug.log

$ git check-ignore -v src/logs/debug.log
src/logs/.gitignore:1:!debug.log    src/logs/debug.log

Note that Git names the file that decided, which is exactly what makes this findable. To see every ignore file in the repository at once:

git ls-files --cached --others --exclude-standard -- "*.gitignore"

8. Ask Git directly: git check-ignore -v

Everything above is a guess until you check. This is the authoritative answer, and it takes one command:

$ git check-ignore -v src/debug.log
.gitignore:3:*.log    src/debug.log

The fields are the source file, the line number, the pattern, then a tab and the path you asked about. Three details make it much more useful:

For the wider view, git status --ignored lists every ignored path, which is a quick way to spot a directory you never meant to exclude. And when Git is ignoring a file you wanted committed rather than the other way round,why Git isn't ignoring a file covers that direction.

Test a rule without a repository

git check-ignore needs a repository, a terminal and a real path on disk. When you're reviewing someone else's.gitignore in a pull request, drafting a rule you haven't committed, or reasoning about a path that only exists on a colleague's machine, paste the path and the rules into IgnoreDebugger instead. It reports the deciding line and why it won, and it runs entirely in your browser — nothing is uploaded.

Open the Gitignore Debugger

Frequently asked questions

Why is my .gitignore not working?

Most often the file is already tracked. .gitignore is only consulted for untracked files, so anything Git has already committed keeps appearing in git status no matter what you add to the ignore file. Run git rm --cached path/to/file and commit that.

If the file was never committed, the cause is almost always the pattern itself — a slash that anchored it without you meaning to, or a * that cannot cross a directory boundary.

How do I ignore a file that is already committed?

Add the rule to .gitignore, then untrack the file with git rm --cached path/to/file and commit. The --cached flag removes it from Git's index only, so the file stays on disk. For a directory, use git rm -r --cached path/to/folder.

Be aware that anyone who pulls that commit will have the file deleted from their working copy, which matters if it is a local config file they still need.

Does .gitignore have to be in the repository root?

No. Git reads a .gitignore from any directory in the repository, and its patterns are interpreted relative to the directory holding it. The root is simply the conventional place for the rules that apply to everything.

What does not work is putting the file outside the repository, or naming it gitignore without the leading dot.

Why doesn't * match files in subfolders?

Because * matches any run of characters except the directory separator. docs/*.md therefore matches docs/readme.md but not docs/api/notes.md.

Use docs/**/*.md when you mean any depth, or drop the slash entirely — *.md matches by basename at every level.

How do I ignore a folder but keep one file inside it?

Ignore the folder's contents rather than the folder itself, then negate the exception: build/* followed by !build/keep.txt.

Writing build/ instead will not work. Once a directory is ignored Git does not descend into it, so the negation on the next line is never evaluated.

Why is my ! rule not re-including the file?

Either a later rule matches the same path and overrides it — the last matching rule wins, not the first — or one of the file's parent directories is ignored.

Git skips ignored directories wholesale and never looks inside them, so no negation within one can take effect. Re-include the parent directory first, then the file.

Do I need to commit .gitignore for it to take effect?

No. Git reads it from the working tree, so a rule takes effect the moment you save the file. Committing it only matters for sharing the rules with everyone else on the repository.

For rules you would rather keep to yourself, use .git/info/exclude, which behaves identically but is repo-local and never committed.

How do I find out which rule is ignoring my file?

Run git check-ignore -v path/to/file. It prints the source file, line number and pattern that decided the outcome, which is usually the whole answer.

If it prints nothing, no rule matched — add --no-index to see whether a rule would have matched had the file not been tracked. The debugger on this site answers the same question without needing a repository in front of you.