git - delete a folder from the repository after adding it to .gitignore
A folder is still in the git repo although .gitignore lists it? git rm -r —cached takes it out of the index without deleting it locally, including the three catches that come with it.
You add node_modules to your .gitignore, commit, push, and the folder is still sitting in the repository. Nothing is broken. The rule simply arrived too late.
A .gitignore only ever decides about files that Git is not already watching. The documentation puts it plainly: “Files already tracked by Git are not affected”. Once a folder has been committed, it stays in the index no matter what pattern you write. Getting it out is a separate step, and this article walks through it, along with three consequences that are easy to miss.
Why the pattern has no say over a tracked folder
Two commands tell you where you stand. git ls-files lists everything the index currently holds. git check-ignore, described in the manual as a way to debug gitignore files, reports which rule would apply to a given path:
git ls-files | grep my-folder
git check-ignore -v my-folder/file.txt
The second command answers with source, line number and pattern, for example .gitignore:1:my-folder/ my-folder/file.txt. This is where the confusion usually starts, because the file also keeps showing up in git ls-files. Both answers are correct at the same time. The pattern matches, and the pattern is irrelevant, because ignore rules are consulted for untracked paths only.
Taking the folder out of the index
So put the folder in the .gitignore first, then use the following command to delete it from the repo. This will keep the folder on your machine and only remove it from the git repo:
git rm -r --cached my-folder
git commit -m 'Delete the now ignored folder "my-folder"'
git push origin master
--cached is what makes the difference: “Use this option to unstage and remove paths only from the index. Working tree files, whether modified or not, will be left alone.” -r extends that from a single file to a directory, since the manual only allows “recursive removal when a leading directory name is given”. Once the commit is in, the folder is untracked, which is exactly the state your .gitignore was written for.
What the command leaves behind
Every earlier commit still carries the files. The removal starts here, it does not reach backwards, and git show still prints the content. For a build directory nobody cares. For credentials the real work begins at this point, and GitHub is explicit about the order: “as a first step you need to revoke and/or rotate that secret”. Rewriting history with git filter-repo comes second, and clones or forks that already exist are beyond the reach of either step.
Your colleagues lose the folder for real. The --cached flag protects your working copy, not theirs. What travels with the commit is a deletion, and Git carries it out on every machine that pulls. A build directory rebuilds itself in a minute; a local config file or a development database does not. Announce the change before you push, and keep a committed example.env around so nobody is left guessing what the missing file contained.
The branch name master in the command above is a guess. New repositories on GitHub have been created with main since 1 October 2020. A local git init still produces master unless init.defaultBranch says otherwise; that switch is scheduled for Git 3.0, and no release date has been set (checked against Git 2.55, released 29 June 2026). Rather than guessing, ask the repository:
git branch --show-current
git push origin HEAD
git push origin HEAD pushes whichever branch you have checked out, so it is right under either convention.
When git rm refuses
Sometimes the command stops instead of removing anything. The cause sits in the index: you edited a file, staged it with git add, and then edited it again, so three different versions are in play. The manual states the condition as “the staged content has to match either the tip of the branch or the file on disk”. The error reads:
error: the following file has staged content different from both the
file and the HEAD:
my-folder/file.txt
(use -f to force removal)
Take the last line literally. -f throws the staged version away. If you still need it, commit it first or park it with git stash.
Rebuilding the index in one go
When a .gitignore has grown over the years, it is rarely obvious which of its entries are still tracked. Clearing the index and refilling it applies every rule at once:
git rm -r --cached .
git add .
git status --short
The lines marked D in the status output are precisely the paths that will be ignored from now on. If that list looks wrong, git reset puts everything back. None of the three commands touches the working tree.
All new changes in the folder will be ignored by git from now on. The next time you stage and push, the folder will no longer be part of the repository.
Have fun!
Why does git still track a folder that is listed in .gitignore?
Because ignore rules apply to untracked paths only. A folder that has been committed once stays in the index regardless of any pattern. git rm -r --cached my-folder removes it, and from then on the rule takes effect.
Does git rm --cached delete files from my disk?
No. With --cached the command works on the index alone and leaves the working tree untouched. Without it, git rm deletes the file in both places.
Will the folder disappear for everyone else as well?
Yes. The commit contains a deletion, and a git pull applies it to the working tree. Anything that cannot simply be regenerated should be backed up before you push.
Is the data gone from the repository afterwards?
Only from this commit onwards. Every earlier commit still contains it. Credentials therefore need to be revoked or rotated first, and the history rewritten with git filter-repo afterwards.
How do I know whether my branch is called master or main?
git branch --show-current prints the name. git push origin HEAD avoids the question entirely by pushing the branch you are on.