.gitignore not working: a file listed in it still shows up as
modified: in git status and keeps going into commits. Ignore rules only
apply to files Git does not track yet, and this file was committed before the rule existed. Stop
tracking it with git rm --cached <file>, or git rm -r --cached
<folder>, and commit; your copy stays on disk.
One warning before you do: that commit removes the file from the repository, so everyone who pulls it loses the file from their disk too, and every older commit still contains it. If it held a password, change the password.
The error
There is no error message, only a file that will not go away:
$ cat .gitignore
debug.log
bin/
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: bin/App.dll
modified: debug.log
no changes added to commit (use "git add" and/or "git commit -a")
$ git check-ignore -v debug.log
$ git check-ignore -v bin/App.dll
Both check-ignore commands printed nothing and exited with code 1, as if no rule
matched. Asked to ignore the index, they show that the rules are fine:
$ git check-ignore -v --no-index debug.log
.gitignore:1:debug.log debug.log
$ git check-ignore -v --no-index bin/App.dll
.gitignore:2:bin/ bin/App.dll
Why it happens
.gitignore answers one question: which untracked files should git status
leave out and git add . skip. A file that is already in the index, because it was
committed before the rule was written, is tracked, and Git keeps recording its changes whatever the
rules say. git check-ignore leaves tracked files out by default for the same reason;
--no-index tests the pattern alone, which is how you tell "the rule is wrong" from "the
file is tracked".
The fix
$ git rm --cached debug.log
rm 'debug.log'
$ git rm -r --cached bin
rm 'bin/App.dll'
$ git commit -m 'Stop tracking debug.log and bin/'
[main 8755d5f] Stop tracking debug.log and bin/
2 files changed, 2 deletions(-)
delete mode 100644 bin/App.dll
delete mode 100644 debug.log
$ git check-ignore -v debug.log
.gitignore:1:debug.log debug.log
$ git check-ignore -v bin/App.dll
.gitignore:2:bin/ bin/App.dll
--cached removes the entry from the index only: ls still found both files,
git ls-files no longer listed them, and after another line was written to
debug.log, git status said nothing to commit, working tree
clean. A second clone, still at the first commit, then pulled the change:
$ git pull --ff-only
Updating a9d9143..8755d5f
Fast-forward
.gitignore | 2 ++
bin/App.dll | 1 -
debug.log | 1 -
3 files changed, 2 insertions(+), 2 deletions(-)
create mode 100644 .gitignore
delete mode 100644 bin/App.dll
delete mode 100644 debug.log
$ ls debug.log bin
ls: cannot access 'debug.log': No such file or directory
ls: cannot access 'bin': No such file or directory
For build output that every machine regenerates, that is what you want. For a local settings
file, tell the team first so they can copy theirs aside. And git show HEAD~2:debug.log
still printed the old content: history keeps the file.
When check-ignore -v prints nothing for an untracked file, the pattern is wrong. A
leading slash anchors a rule to the folder of the .gitignore:
$ cat .gitignore
debug.log
bin/
/app.log
$ git check-ignore -v src/app.log
With the third line changed to app.log, the rule matched:
$ git check-ignore -v src/app.log
.gitignore:3:app.log src/app.log
/app.log matches only app.log at the top; app.log matches it
in any folder. A trailing slash narrows a rule the other way: cache/ is for folders,
and in the test it did not match a plain file called cache.
How it was reproduced
Git for Windows 2.45.2 (git version 2.45.2.windows.1) in Git Bash 5.2.26. A repository
with debug.log, bin/App.dll and one source file was committed and pushed
to a local bare repository before any .gitignore existed; the rules were added in a
second commit and both files were changed. A second clone, reset to the first commit, pulled the
fix to show what a teammate gets. The personal global config was replaced by an empty file
(GIT_CONFIG_GLOBAL).
Frequently asked
- Why is .gitignore not ignoring my file?
- Usually because the file was committed before the rule was added, and ignore rules only apply to untracked files. Run git check-ignore -v on the file: if it prints nothing but git check-ignore -v --no-index prints the rule, the file is tracked and git rm --cached is the fix.
- Does git rm --cached delete the file?
- Not from your disk. It removes the file from the index, so the next commit deletes it from the repository while your copy stays. Anyone who pulls that commit loses the file from their disk, and older commits still contain it.
- How do I stop tracking a folder but keep it locally?
- Add the folder to .gitignore with a trailing slash, for example bin/, then run git rm -r --cached bin and commit. The folder stays on your disk and git status stops listing it.
More decoded errors in the Fixes category. For what belongs in a
.gitignore in the first place, see
reading history and undoing mistakes, which covers
it alongside the first rescue moves.