Skip to content

Instantly share code, notes, and snippets.

@HunterDG
Created August 16, 2019 03:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HunterDG/07eadfa7c0196e917bdac13f2a5f5e07 to your computer and use it in GitHub Desktop.
Save HunterDG/07eadfa7c0196e917bdac13f2a5f5e07 to your computer and use it in GitHub Desktop.
How to make Git **properly forget** (historically) about a file now in .gitignore
> 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. <sup>1</sup>
> All methods of enforcing git ignore behavior after-the-fact effectively re-write history and thus have [significant ramifications](https://stackoverflow.com/q/1491001) for any public/shared/collaborative repos that might be pulled after this process. <sup>2</sup>
> 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](https://stackoverflow.com/posts/57454176/revisions) of [this answer](https://stackoverflow.com/a/57454176) ([and revision history](https://stackoverflow.com/posts/57418769/revisions) of [this question](https://stackoverflow.com/posts/57418769)) may be useful/enlightening.
<!-- language: sh -->
#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 --hard 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](https://help.github.com/en/articles/removing-sensitive-data-from-a-repository) (starting at step 6) **which includes important warnings/information about the commands below**.
<!-- language: sh -->
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:
<!-- language: sh -->
#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
#Switching branches after this procedure WILL LOOSE all newly-gitignored files in working directory because they are no longer tracked when switching branches
git reset FETCH_HEAD
----------
##Footnotes##
<sup>1</sup> 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](https://stackoverflow.com/q/1491001).
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](https://stackoverflow.com/q/43463687)
<sup>2</sup> 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](https://stackoverflow.com/q/1491001) on future pulls from public/shared/collaborative repos. Please understand the [ramifications](https://stackoverflow.com/q/1491001) fully before doing this to such a repo. [This GitHub guide](https://help.github.com/en/articles/removing-sensitive-data-from-a-repository) specifies the following:
> Tell your collaborators to [rebase](https://git-scm.com/book/en/Git-Branching-Rebasing), *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](https://stackoverflow.com/a/20241145).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment