Search code examples
gitgitignore

Definitive retroactive .gitignore (how to make Git completely/retroactively *forget* about a file now in .gitignore)


Preface

This question attempts to clear the confusion regarding applying .gitignore retroactively, not just to the present/future.1

Rationale

I've been searching for a way to make my current .gitignore be retroactively enforced, as if I had created .gitignore in the first commit.

The solution I am seeking:

  • Will not require manually specifying files
  • Will not require a commit
  • Will apply retroactively to all commits of all branches
  • Will ignore .gitignore-specified files in working dir, not delete them (just like an originally root-committed .gitignore file would)
  • Will use git, not BFG
  • Will apply to .gitignore exceptions like:
 *.ext
 !*special.ext

Not solutions

git rm --cached *.ext
git commit

This requires 1. manually specifying files and 2. an additional commit, which will result in newly-ignored file deletion when pulled by other developers. (It is effectively just a git rm - which is a deletion from git tracking - but it leaves the file alone in the local (your) working directory. Others who git pull afterwards will receive the file deletion commit)

git filter-branch --index-filter 'git rm --cached *.ext'

While this does purge files retroactively, it 1. requires manually specifying files and 2. deletes the specified files from the local working directory just like plain git rm (and so also for others who git pull)!


Footnotes

1There are many similar posts here on SO, with less-than-specifically-defined questions and even more less-than-accurate answers. See this question with 23 answers where the accepted answer with ~4k votes is incorrect according to the standard definition of "forget" as noted by one mostly-correct answer, and only 2 answers include the required git filter-branch command.

This question with 21 answers is was marked as a duplicate of the previous one, but the question is defined differently (ignore vs forget), so while the answers may be appropriate, it is not a duplicate.

This question is the closest I've found to what I'm looking for, but the answers don't work in all cases (paths with spaces...) and perhaps are a bit more complex than necessary regarding creating an external-to-repository .gitignore file and copying it into every commit.


Solution

  • This may be only a partial answer but here is how I accomplished retroactively removing files from previous git commits based on my current .gitignore file:

    1. Make a backup of the repo folder you are working on. I just made a .7z archive of the entire folder.
    2. Install git-filter-repo
    3. Copy your .gitignore file somewhere else temporarily. Since I'm on Windows and using Command Prompt, I ran copy .gitignore ..\ and just made the temp copy only directory level up
    4. If your .gitignore file has wildcard filters (like nbproject/Makefile-*), you'll need to edit your temp copied .gitignore file so those lines read glob:nbproject/Makefile-*
    5. Run git filter-repo --invert-paths --paths-from-file ..\.gitignore. My understanding is that this uses the temp copy as a list of files/directories to remove. Note: if you receive an error regarding your repo not being a clean clone, search for "FRESH CLONE SAFETY CHECK AND --FORCE" in the git-filter-repo help. Be careful.

    For more info see: git-filter-repo help (Search for "Filtering based on many paths")

    Disclaimer: I have no idea what I'm doing but this worked for me.