Remove sensitive files and their commits from Git history

Viewed 188386

I would like to put a Git project on GitHub but it contains certain files with sensitive data (usernames and passwords, like /config/deploy.rb for capistrano).

I know I can add these filenames to .gitignore, but this would not remove their history within Git.

I also don't want to start over again by deleting the /.git directory.

Is there a way to remove all traces of a particular file in your Git history?

12 Answers

For all practical purposes, the first thing you should be worried about is CHANGING YOUR PASSWORDS! It's not clear from your question whether your git repository is entirely local or whether you have a remote repository elsewhere yet; if it is remote and not secured from others you have a problem. If anyone has cloned that repository before you fix this, they'll have a copy of your passwords on their local machine, and there's no way you can force them to update to your "fixed" version with it gone from history. The only safe thing you can do is change your password to something else everywhere you've used it.


With that out of the way, here's how to fix it. GitHub answered exactly that question as an FAQ:

Note for Windows users: use double quotes (") instead of singles in this command

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

Update 2019:

This is the current code from the FAQ:

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

Keep in mind that once you've pushed this code to a remote repository like GitHub and others have cloned that remote repository, you're now in a situation where you're rewriting history. When others try pull down your latest changes after this, they'll get a message indicating that the changes can't be applied because it's not a fast-forward.

To fix this, they'll have to either delete their existing repository and re-clone it, or follow the instructions under "RECOVERING FROM UPSTREAM REBASE" in the git-rebase manpage.

Tip: Execute git rebase --interactive


In the future, if you accidentally commit some changes with sensitive information but you notice before pushing to a remote repository, there are some easier fixes. If you last commit is the one to add the sensitive information, you can simply remove the sensitive information, then run:

git commit -a --amend

That will amend the previous commit with any new changes you've made, including entire file removals done with a git rm. If the changes are further back in history but still not pushed to a remote repository, you can do an interactive rebase:

git rebase -i origin/master

That opens an editor with the commits you've made since your last common ancestor with the remote repository. Change "pick" to "edit" on any lines representing a commit with sensitive information, and save and quit. Git will walk through the changes, and leave you at a spot where you can:

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

For each change with sensitive information. Eventually, you'll end up back on your branch, and you can safely push the new changes.

In my android project I had admob_keys.xml as separated xml file in app/src/main/res/values/ folder. To remove this sensitive file I used below script and worked perfectly.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all

Solution 1:

Using the git filter-branch command

Example:

git filter-branch --index-filter 'git rm -r --cached --ignore-unmatch *file_relative_path*' --prune-empty --tag-name-filter cat -- --all

The terms used above are:

  • --prune-empty: If you just want to prune commits that become empty then you don't even need to specify this flag. If you want to prune commits that started empty in your repo, then you need to specify --prune-empty always.
  • --tag-name-filter <command>: If you are just specifying --tag-name-filter cat, then the correct translation is to specify no extra flags. The fact that filter-branch required that was evidence that it was retarded; it should have been handled automatically. (If you use something other than 'cat', i.e. you really are renaming tags, then there's a --tag-rename option.)
  • -- --all: Rewrite on all branches.
  • --index-filter <command>: This is the filter for rewriting the index. It is similar to the tree filter but does not check out the tree, which makes it much faster. Most people almost always use this just to prune or keep files based on filename, in which case you want to use the various --path* options and, if specifying paths to prune rather than paths to keep, use --invert-paths.

Solution 2 (Recommended Method):

Using the git filter-repo command

git filter-repo is now recommended by the git project instead of git filter-branch since filter-branch is extremely slow (multiple orders of magnitude slower than it should be) for non-trivial repositories.

Example:

git filter-repo --path *file_relative_path* --invert-paths

The term here is:

  • --invert-paths: Invert the selection of files from the specified --path-{match,glob,regex} options, i.e. only select files matching none of those options.
Related