A correct rule that changes nothing is almost always this problem. Once a file is in Git's index, .gitignore stops having any say over it — so the fix is not a better pattern, it is removing the file from the index and committing that.
The short version. Write the rule, then rungit rm --cached <file> — orgit rm -r --cached <folder> — and commit. The file stays on your disk; the commit records that Git is no longer responsible for it. Do it in that order and you can confirm the rule works before you touch the index.
Why .gitignore cannot touch a tracked file
Ignore rules are consulted for exactly one purpose: deciding whichuntracked paths to hide. A file that is already in the index has passed that gate permanently. Git does not re-evaluate the decision, and it will not silently drop something you asked it to track.
Which is why the symptoms look nothing like a broken pattern. Commit a file, then add a rule for it, and git status stays clean — not because the rule worked, but because a tracked, unmodified file has nothing to report. Edit the file and it comes straight back:
$ git status --porcelain
M config.env ← the rule is matching, and being ignoredThe rule is not wrong. It is simply not being applied to this path, and no amount of rewriting it will change that.
Confirm it is the index, not your pattern
There is a precise test for this, and it is the reasongit check-ignore has a --no-index flag. Run it both ways:
$ git check-ignore -v config.env
← nothing at all, exit status 1
$ git check-ignore -v --no-index config.env
.gitignore:1:*.env config.env ← the rule exists and matchesSilence from the first command and a rule from the second is a definitive answer: your pattern is fine and the file is tracked. The default form ignores tracked paths entirely, so its silence is not evidence that no rule matches — a distinction that sends a lot of people back to rewrite a pattern that was already correct.
A more direct question, if you only want a yes or no:
$ git ls-files --error-unmatch config.env
config.env ← exit 0: tracked
$ git ls-files --error-unmatch notes.txt
error: pathspec 'notes.txt' did not match any file(s) known to git
← exit 1: not trackedFind every file with this problem at once
Rules are usually added long after the files were committed, so if one path is affected, others normally are too. One command lists the whole set — files that are in the index and matched by your ignore rules:
$ git ls-files -i -c --exclude-standard
build/out.o
logs/x.logWorth running after any meaningful .gitignore edit. Nothing else surfaces these: they produce no warning, no status entry, and no output from a plain git check-ignore.
Remove it from the index
With the rule already in place, one command per path does it. Add-r for a directory — Git refuses to recurse without it:
$ git rm --cached config.env
rm 'config.env'
$ git rm --cached build
fatal: not removing 'build' recursively without -r
$ git rm -r --cached buildThat stages a deletion. Nothing is final, and nothing has left your disk, until you commit it:
$ git status --porcelain
D config.env ← staged removal, file still present on disk
$ git commit -m "Stop tracking config.env"After the commit, git ls-files no longer lists the path, the file is still sitting in your working tree, andgit check-ignore -v — the plain form, now that the path is untracked — finally reports your rule.
Staged but never committed
If you caught it after git add but before any commit, the job is smaller. The file shows as A ingit status, and a single git rm --cached drops it back to untracked, where the ignore rule applies immediately:
$ git status --porcelain
A data.log
$ git rm --cached data.log
rm 'data.log' ← no commit needed; it was never committedNo -f is required, and there is no staged deletion left behind, because there is no committed version to remove.
Rebuilding the whole index
When a .gitignore has drifted badly out of step with what is committed, it is often quicker to re-apply it to everything than to work through paths one at a time:
$ git rm -r --cached .
$ git add .
$ git commit -m "Apply .gitignore to tracked files"The first line empties the index without touching a single file on disk; the second refills it, this time obeying your current rules, so anything now ignored is simply left out. The staged result is a deletion for each newly-ignored path and nothing else.
Two cautions. Run it from the repository root, since. is relative to your working directory. And checkgit status before committing, because a mistake here is staged across the entire project rather than one file — though as long as you have not committed, git reset puts the index back.
What your collaborators will see
Committing the removal changes the project for everyone, and the outcome on their side depends on what they have locally. All three are worth knowing before you push:
- Clean working tree: the file is deleted from their working tree on pull, with no prompt. This is the case that surprises people, particularly for local config they never thought of as "theirs".
- Uncommitted local edits: the pull is refused with"Your local changes to the following files would be overwritten by merge", and nothing happens until they stash or commit.
- Their own committed change: a modify/delete conflict, with their version left in the working tree to resolve.
None of it is data loss as far as Git is concerned, but the first outcome is silent. If the file is something people need locally — an.env, a machine-specific config — commit an example copy under a name your rules do not match, and say so in the commit message.
Four ways to undo your own fix
The mistakes here are all recoverable, and all easier to avoid than to diagnose:
- Removing from the index without an ignore rule. You get a staged deletion and a fresh
??entry for the same path, and the nextgit add -Are-adds it. Untracking does not ignore; it only makes ignoring possible. - Forgetting
--cached. Plaingit rm config.envdeletes the file from your working tree as well as the index. Recoverable from the last commit, but not what anyone wants. - Forgetting to commit. A staged removal is invisible to everyone else. Until it is committed and pushed, the file is still part of the project.
- Committing the rule but not the removal. The most common half-fix: the
.gitignorechange lands, the index is untouched, and the file goes on being tracked exactly as before.
--assume-unchanged is not this fix
Two flags come up as shortcuts here and neither does what the name suggests:
$ git update-index --assume-unchanged config.json
$ git update-index --skip-worktree config.json
$ git ls-files -v config.json
h config.json ← assume-unchanged
S config.json ← skip-worktreeBoth stop Git reporting your local modifications, sogit status goes quiet and it feels like the file has been ignored. It has not: the file is still tracked, still committed, and its contents are still in the repository for anyone to read.
They are also local-only — the flag lives in your own index and reaches no one else — so as a way of handling shared config they simply move the problem to the next person. If a file does not belong in the project, remove it from the index. If it does belong but needs local values, commit a template and ignore the real one.
For the wider set of reasons an ignore rule can fail to bite, including pattern and placement problems, seegitignore not working.