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 manually specifying files and an additional commit, which will result in newly-ignored file deletion when pulled by other developers.
git filter-branch --index-filter 'git rm --cached *.ext'
This requires manually specifying files and deletes the specified files from the local working directory!
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 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.
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. 1All 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.
Finally, follow the rest of this GitHub guide (starting at step 6) which includes important warnings/information about the commands below.
Other devs that pull from now-modified remote repo should make a backup and then:
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 agit filter-branch
that copies an external.gitignore
into each commit, like the answers to this question2 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 thegit 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:Alternative solutions that do not affect the remote repo are
git update-index --assume-unchanged </path/file>
orgit update-index --skip-worktree <file>
, examples of which can be found here.