[git] How to make Git "forget" about a file that was tracked but is now in .gitignore?

There is a file that was being tracked by git, but now the file is on the .gitignore list.

However, that file keeps showing up in git status after it's edited. How do you force git to completely forget about it?

This question is related to git gitignore git-rm

The answer is


Use this when:

1. You want to untrack a lot of files, or

2. You updated your gitignore file

Source link: http://www.codeblocq.com/2016/01/Untrack-files-already-added-to-git-repository-based-on-gitignore/

Let’s say you have already added/committed some files to your git repository and you then add them to your .gitignore; these files will still be present in your repository index. This article we will see how to get rid of them.

Step 1: Commit all your changes

Before proceeding, make sure all your changes are committed, including your .gitignore file.

Step 2: Remove everything from the repository

To clear your repo, use:

git rm -r --cached .
  • rm is the remove command
  • -r will allow recursive removal
  • –cached will only remove files from the index. Your files will still be there.

The rm command can be unforgiving. If you wish to try what it does beforehand, add the -n or --dry-run flag to test things out.

Step 3: Re add everything

git add .

Step 4: Commit

git commit -m ".gitignore fix"

Your repository is clean :)

Push the changes to your remote to see the changes effective there as well.


In my case here, I had several .lock files in several directories that I needed to remove. I ran the following and it worked without having to go into each directory to remove them:

git rm -r --cached **/*.lock

Doing this went into each folder under the 'root' of where I was at and excluded all files that matched the pattern.

Hope this helps others!


I always use this command to remove those untracked files. One-line, Unix-style, clean output:

git ls-files --ignored --exclude-standard | sed 's/.*/"&"/' | xargs git rm -r --cached

It lists all your ignored files, replace every output line with a quoted line instead to handle paths with spaces inside, and pass everything to git rm -r --cached to remove the paths/files/dirs from the index.


The answer from Matt Fear was the most effective IMHO. The following is just a PowerShell script for those in windows to only remove files from their git repo that matches their exclusion list.

# Get files matching exclusionsfrom .gitignore
# Excluding comments and empty lines
$ignoreFiles =  gc .gitignore | ?{$_ -notmatch  "#"} |  ?{$_ -match  "\S"} | % {
                    $ignore = "*" + $_ + "*"
                    (gci -r -i $ignore).FullName
                }
$ignoreFiles = $ignoreFiles| ?{$_ -match  "\S"}

# Remove each of these file from Git 
$ignoreFiles | % { git rm $_}

git add .

The accepted answer does not "make Git "forget" about a file..." (historically). It only makes git ignore the file in the present/future.

This method makes git completely forget ignored files (past/present/future), but does not delete anything from working directory (even when re-pulled from remote).

This method requires usage of /.git/info/exclude (preferred) OR a pre-existing .gitignore in all the commits that have files to be ignored/forgotten. 1

All methods of enforcing git ignore behavior after-the-fact effectively re-write history and thus have significant ramifications for any public/shared/collaborative repos that might be pulled after this process. 2

General advice: start with a clean repo - everything committed, nothing pending in working directory or index, and make a backup!

Also, the comments/revision history of this answer (and revision history of this question) may be useful/enlightening.

#commit up-to-date .gitignore (if not already existing)
#this command must be run on each branch

git add .gitignore
git commit -m "Create .gitignore"

#apply standard git ignore behavior only to current index, not working directory (--cached)
#if this command returns nothing, ensure /.git/info/exclude AND/OR .gitignore exist
#this command must be run on each branch

git ls-files -z --ignored --exclude-standard | xargs -0 git rm --cached

#Commit to prevent working directory data loss!
#this commit will be automatically deleted by the --prune-empty flag in the following command
#this command must be run on each branch

git commit -m "ignored index"

#Apply standard git ignore behavior RETROACTIVELY to all commits from all branches (--all)
#This step WILL delete ignored files from working directory UNLESS they have been dereferenced from the index by the commit above
#This step will also delete any "empty" commits.  If deliberate "empty" commits should be kept, remove --prune-empty and instead run git reset HEAD^ immediately after this command

git filter-branch --tree-filter 'git ls-files -z --ignored --exclude-standard | xargs -0 git rm -f --ignore-unmatch' --prune-empty --tag-name-filter cat -- --all

#List all still-existing files that are now ignored properly
#if this command returns nothing, it's time to restore from backup and start over
#this command must be run on each branch

git ls-files --other --ignored --exclude-standard

Finally, follow the rest of this GitHub guide (starting at step 6) which includes important warnings/information about the commands below.

git push origin --force --all
git push origin --force --tags
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now

Other devs that pull from now-modified remote repo should make a backup and then:

#fetch modified remote

git fetch --all

#"Pull" changes WITHOUT deleting newly-ignored files from working directory
#This will overwrite local tracked files with remote - ensure any local modifications are backed-up/stashed

git reset FETCH_HEAD

Footnotes

1 Because /.git/info/exclude can be applied to all historical commits using the instructions above, perhaps details about getting a .gitignore file into the historical commit(s) that need it is beyond the scope of this answer. I wanted a proper .gitignore to be in the root commit, as if it was the first thing I did. Others may not care since /.git/info/exclude can accomplish the same thing regardless where the .gitignore exists in the commit history, and clearly re-writing history is a very touchy subject, even when aware of the ramifications.

FWIW, potential methods may include git rebase or a git filter-branch that copies an external .gitignore into each commit, like the answers to this question

2 Enforcing git ignore behavior after-the-fact by committing the results of a standalone git rm --cached command may result in newly-ignored file deletion in future pulls from the force-pushed remote. The --prune-empty flag in the following git filter-branch command avoids this problem by automatically removing the previous "delete all ignored files" index-only commit. Re-writing git history also changes commit hashes, which will wreak havoc on future pulls from public/shared/collaborative repos. Please understand the ramifications fully before doing this to such a repo. This GitHub guide specifies the following:

Tell your collaborators to rebase, not merge, any branches they created off of your old (tainted) repository history. One merge commit could reintroduce some or all of the tainted history that you just went to the trouble of purging.

Alternative solutions that do not affect the remote repo are git update-index --assume-unchanged </path/file> or git update-index --skip-worktree <file>, examples of which can be found here.


The BFG is specifically designed for removing unwanted data like big files or passwords from Git repos, so it has a simple flag that will remove any large historical (not-in-your-current-commit) files: '--strip-blobs-bigger-than'

$ java -jar bfg.jar --strip-blobs-bigger-than 100M

If you'd like to specify files by name, you can do that too:

$ java -jar bfg.jar --delete-files *.mp4

The BFG is 10-1000x faster than git filter-branch, and generally much easier to use - check the full usage instructions and examples for more details.

Source: https://confluence.atlassian.com/bitbucket/reduce-repository-size-321848262.html


If you cannot git rm a tracked file because other people might need it (warning, even if you git rm --cached, when someone else gets this change, their files will be deleted in their filesystem). These are often done due to config file overrides, authentication credentials, etc. Please look at https://gist.github.com/1423106 for ways people have worked around the problem.

To summarize:

  • Have your application look for an ignored file config-overide.ini and use that over the committed file config.ini (or alternately, look for ~/.config/myapp.ini, or $MYCONFIGFILE)
  • Commit file config-sample.ini and ignore file config.ini, have a script or similar copy the file as necessary if necessary.
  • Try to use gitattributes clean/smudge magic to apply and remove the changes for you, for instance smudge the config file as a checkout from an alternate branch and clean the config file as a checkout from HEAD. This is tricky stuff, I don't recommend it for the novice user.
  • Keep the config file on a deploy branch dedicated to it that is never merged to master. When you want to deploy/compile/test you merge to that branch and get that file. This is essentially the smudge/clean approach except using human merge policies and extra-git modules.
  • Anti-recommentation: Don't use assume-unchanged, it will only end in tears (because having git lie to itself will cause bad things to happen, like your change being lost forever).

Do the following steps for file/folder:

Remove File:

  1. need to add that file to .gitignore.
  2. need to remove that file using the command (git rm --cached file name).
  3. need to run (git add .).
  4. need to (commit -m) "file removed".
  5. and finally, (git push).

For example:

I want to delete test.txt file. I accidentally pushed to GitHub want to remove commands will be followed as:

1st add test.txt in .gitignore

git rm --cached test.txt
git add .
git commit -m "test.txt removed"
git push

Remove Folder:

  1. need to add that folder to .gitignore.
  2. need to remove that folder using the command (git rm -r --cached folder name).
  3. need to run (git add .).
  4. need to (commit -m) "folder removed".
  5. and finally, (git push).

For example:

I want to delete the .idea folder/dir. I accidentally pushed to GitHub want to remove commands will be followed as:

1st add .idea in .gitignore

git rm -r --cached .idea
git add .
git commit -m ".idea removed"
git push

git update-index does the job for me:

git update-index --assume-unchanged <file>

Note: This solution is actually independent on .gitignore as gitignore is only for untracked files.


Update, a better option

Since this answer was posted, a new option has been created and that should be preferred. You should use --skip-worktree which is for modified tracked files that the user don't want to commit anymore and keep --assume-unchanged for performance to prevent git to check status of big tracked files. See https://stackoverflow.com/a/13631525/717372 for more details...

git update-index --skip-worktree <file>

What didn't work for me

(Under Linux), I wanted to use the posts here suggesting the ls-files --ignored --exclude-standard | xargs git rm -r --cached approach. However, (some of) the files to be removed had an embedded newline/LF/\n in their names. Neither of the solutions:

git ls-files --ignored --exclude-standard | xargs -d"\n" git rm --cached
git ls-files --ignored --exclude-standard | sed 's/.*/"&"/' | xargs git rm -r --cached

cope with this situation (get errors about files not found).

So I offer

git ls-files -z --ignored --exclude-standard | xargs -0 git rm -r --cached
git commit -am "Remove ignored files"

This uses the -z argument to ls-files, and the -0 argument to xargs to cater safely/correctly for "nasty" characters in filenames.

In the manual page git-ls-files(1), it states:

When -z option is not used, TAB, LF, and backslash characters in pathnames are represented as \t, \n, and \\, respectively.

so I think my solution is needed if filenames have any of these characters in them.


  1. Update your .gitignore file – for instance, add a folder you don't want to track to .gitignore.

  2. git rm -r --cached . – Remove all tracked files, including wanted and unwanted. Your code will be safe as long as you have saved locally.

  3. git add . – All files will be added back in, except those in .gitignore.


Hat tip to @AkiraYamamoto for pointing us in the right direction.


Especially for the IDE based files, I use this:

For instance the slnx.sqlite, I just got rid off it completely like following:

git rm {PATH_OF_THE_FILE}/slnx.sqlite -f
git commit -m "remove slnx.sqlite"

Just keep that in mind that some of those files stores some local user settings and preferences for projects (like what files you had open). So every time you navigate or do some changes in your IDE, that file is changed and therefore it checks it out and show as there are uncommitted changes.


Do the following steps serially,you will be fine.

1.remove the mistakenly added files from the directory/storage. You can use "rm -r"(for linux) command or delete them by browsing the directories. Or move them to another location on your PC.[You maybe need to close the IDE if running for moving/removing]

2.add the files / directories to gitignore file now and save it.

3.now remove them from git cache by using these commands (if there are more than one directory, remove them one by one by repeatedly issuing this command)

git rm -r --cached path-to-those-files

4.now do a commit and push, use these commands. This will remove those files from git remote and make git stop tracking those files.

git add .
git commit -m "removed unnecessary files from git"
git push origin

I liked JonBrave's answer but I have messy enough working directories that commit -a scares me a bit, so here's what I've done:

git config --global alias.exclude-ignored '!git ls-files -z --ignored --exclude-standard | xargs -0 git rm -r --cached && git ls-files -z --ignored --exclude-standard | xargs -0 git stage && git stage .gitignore && git commit -m "new gitignore and remove ignored files from index"'

breaking it down:

git ls-files -z --ignored --exclude-standard | xargs -0 git rm -r --cached 
git ls-files -z --ignored --exclude-standard | xargs -0 git stage 
git stage .gitignore 
git commit -m "new gitignore and remove ignored files from index"
  • remove ignored files from index
  • stage .gitignore and the files you just removed
  • commit

git ls-files --ignored --exclude-standard -z | xargs -0 git rm --cached
git commit -am "Remove ignored files"

This takes the list of the ignored files and removes them from the index, then commits the changes.


Using the git rm --cached command does not answer the original question:

How do you force git to completely forget about [a file]?

In fact, this solution will cause the file to be deleted in every other instance of the repository when executing a git pull!

The correct way to force git to forget about a file is documented by GitHub here.

I recommend reading the documentation, but basically:

git fetch --all
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch full/path/to/file' --prune-empty --tag-name-filter cat -- --all
git push origin --force --all
git push origin --force --tags
git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now

just replace full/path/to/file with the full path of the file. Make sure you've added the file to your .gitignore.

You'll also need to (temporarily) allow non-fast-forward pushes to your repository, since you're changing your git history.


I accomplished this by using git filter-branch. The exact command I used was taken from the man page:

WARNING: this will delete the file from your entire history

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

This command will recreate the entire commit history, executing git rm before each commit and so will get rid of the specified file. Don't forget to back it up before running the command as it will be lost.


Move or copy the file to a safe location, so you don't lose it. Then git rm the file and commit. The file will still show up if you revert to one of those earlier commits, or another branch where it has not been removed. However, in all future commits, you will not see the file again. If the file is in the git ignore, then you can move it back into the folder, and git won't see it.


If you don't want to use the CLI and are working on Windows, a very simple solution is to use TortoiseGit, it has the "Delete (keep local)" Action in the menu which works fine.


This is no longer an issue in the latest git (v2.17.1 at the time of writing).

The .gitignore finally ignores tracked-but-deleted files. You can test this for yourself by running the following script. The final git status statement should report "nothing to commit".

# Create empty repo
mkdir gitignore-test
cd gitignore-test
git init

# Create a file and commit it
echo "hello" > file
git add file
git commit -m initial

# Add the file to gitignore and commit
echo "file" > .gitignore
git add .gitignore
git commit -m gitignore

# Remove the file and commit
git rm file
git commit -m "removed file"

# Reintroduce the file and check status.
# .gitignore is now respected - status reports "nothing to commit".
echo "hello" > file
git status

The series of commands below will remove all of the items from the Git Index (not from the working directory or local repo), and then updates the Git Index, while respecting git ignores. PS. Index = Cache

First:

git rm -r --cached . 
git add .

Then:

git commit -am "Remove ignored files"

Or one-liner:

git rm -r --cached . && git add . && git commit -am "Remove ignored files"

I think, that maybe git can't totally forget about file because of its conception (section "Snapshots, Not Differences").

This problem is absent, for example, when using CVS. CVS stores information as a list of file-based changes. Information for CVS is a set of files and the changes made to each file over time.

But in Git every time you commit, or save the state of your project, it basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot. So, if you added file once, it will always be present in that snapshot.

These 2 articles were helpful for me:

git assume-unchanged vs skip-worktree and How to ignore changes in tracked files with Git

Basing on it I do the following, if file is already tracked:

git update-index --skip-worktree <file>

From this moment all local changes in this file will be ignored and will not go to remote. If file is changed on remote, conflict will occure, when git pull. Stash won't work. To resolve it, copy file content to the safe place and follow these steps:

git update-index --no-skip-worktree <file>
git stash
git pull 

File content will be replaced by the remote content. Paste your changes from safe place to file and perform again:

git update-index --skip-worktree <file>

If everyone, who works with project, will perform git update-index --skip-worktree <file>, problems with pull should be absent. This solution is OK for configurations files, when every developer has their own project configuration.

It is not very convenient to do this every time, when file has been changed on remote, but can protect it from overwriting by remote content.


In case of already committed DS_Store:

find . -name .DS_Store -print0 | xargs -0 git rm --ignore-unmatch

Ignore them by:

echo ".DS_Store" >> ~/.gitignore_global
echo "._.DS_Store" >> ~/.gitignore_global
echo "**/.DS_Store" >> ~/.gitignore_global
echo "**/._.DS_Store" >> ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global

Finally, make a commit!


If anyone having hard time on Windows and you wanna ignore entire folder, 'cd' to desired the 'folder' and do 'Git Bash Here'.

git ls-files -z | xargs -0 git update-index --assume-unchanged

The copy/paste answer is git rm --cached -r .; git add .; git status

This command will ignore the files that have already been committed to a Git repository but now we have added them to .gitignore.


move it out, commit, then move it back in. This has worked for me in the past. There is probably a 'gittier' way to accomplish this.


Examples related to git

Does the target directory for a git clone have to match the repo name? Git fatal: protocol 'https' is not supported Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) git clone: Authentication failed for <URL> destination path already exists and is not an empty directory SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 GitLab remote: HTTP Basic: Access denied and fatal Authentication How can I switch to another branch in git? VS 2017 Git Local Commit DB.lock error on every commit How to remove an unpushed outgoing commit in Visual Studio?

Examples related to gitignore

Gitignore not working How to add files/folders to .gitignore in IntelliJ IDEA? Apply .gitignore on an existing repository already tracking large number of files Ignore .classpath and .project from Git .gitignore all the .DS_Store files in every folder and subfolder Correctly ignore all files recursively under a specific folder except for a specific file type What should be in my .gitignore for an Android Studio project? Commit empty folder structure (with git) How to remove files that are listed in the .gitignore but still on the repository? What to gitignore from the .idea folder?

Examples related to git-rm

Remove a folder from git tracking Staging Deleted files Why are there two ways to unstage a file in Git? How to remove multiple deleted files in Git repository "git rm --cached x" vs "git reset head --? x"? Git: list only "untracked" files (also, custom commands) Git add all files modified, deleted, and untracked? Git: How to remove file from index without deleting files from any repository How to revert a "git rm -r ."? How can I delete a file from a Git repository?