How to make Git forget a file that is already tracked
You committed something you should not have — a .env, a build directory — then
added it to .gitignore and it kept showing up. That is not a broken ignore
rule. .gitignore is not consulted for files git already tracks.
Why the ignore rule does nothing
.gitignore : .env
tracked : .env .gitignore app.py
The file is ignored and tracked at the same time, and tracking wins:
after editing it, status : M .env
.gitignore decides which untracked
files git mentions and picks up with git add .. Once a file is in the index,
that list is not consulted for it any more. The rule is doing exactly what it
says; it just does not apply to this file.
Untracking it
$ git rm --cached .env
$ git commit -m "stop tracking .env"
tracked : .gitignore app.py
on disk : .env .git .gitignore app.py
status : D .env
The D looks alarming. It means a deletion is staged — from git’s point of
view the file is going away, which is the whole point. The file itself is still
on disk, as the listing shows.
After the commit it is untracked, .gitignore applies to it for the first time,
and git status is quiet:
after commit, status : ''
One flag away from deleting it
$ git rm .env # removes it from the index AND from disk
after 'git rm .env' : .git .gitignore app.py
The file is gone. On a .env holding credentials that is a bad afternoon, so
it is worth building the habit of typing --cached before you type the
filename.
For a directory
$ git rm -r --cached build
-r for recursive, as with rm. Everything under it is untracked in one go and
nothing is deleted from disk.
Re-applying .gitignore to the whole tree
When several files have crept in, do the lot:
$ git rm -r --cached .
$ git add .
$ git commit -m "apply .gitignore"
This untracks everything and then re-adds it — and the re-add does respect
.gitignore, so anything matching a rule stays out. What is left staged is
exactly the set of files that should have been ignored all along:
D .env
Nothing is deleted from your working tree; only the index is rebuilt. Still, commit or stash your real work first, so the diff you are reading afterwards contains only this.
It is still in the history
This is the part that matters if the file held a secret:
$ git show HEAD~1:.env
SECRET=1
Untracking a file changes the future, not the past. Every commit that contained it still contains it, and anyone with a clone has it.
So if what leaked was a credential, rotate it. That is the fix. Rewriting
history with git filter-repo or BFG removes it from the repository, which is
worth doing for hygiene, but it does not un-leak anything — the old commits may
already be in other people’s clones, in CI caches, in forks and in backups. Treat
the rewrite as tidying up after the real remedy, not as the remedy.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).