从Git历史记录中删除敏感文件及其提交

Remove sensitive files and their commits from Git history

我想在GitHub上放置一个Git项目,但它包含一些带有敏感数据的文件(用户名和密码,如/config/deploy.rb for capistrano)。

我知道我可以将这些文件名添加到.gitignore,但这不会删除git中的历史记录。

我也不想通过删除/.git目录重新开始。

是否有方法删除Git历史记录中特定文件的所有跟踪?


出于所有实际目的,您首先应该担心的是更改密码!从您的问题来看,还不清楚您的Git存储库是完全本地的,还是您在其他地方是否有远程存储库;如果它是远程的,并且不受其他人的保护,那么您会遇到问题。如果在您修复此问题之前有人克隆了该存储库,他们将在本地计算机上拥有您的密码副本,并且您无法强制他们更新到您的"已修复"版本,因为该版本已从历史记录中删除。你能做的唯一安全的事情就是在你使用它的任何地方把你的密码改成其他的密码。

如果你不介意的话,下面是解决问题的方法。Github准确地回答了这个问题作为常见问题:

Windows用户注意:在该命令中使用双引号(")而不是单引号

1
2
3
4
git filter-branch --index-filter \
'git update-index --remove filename' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

请记住,一旦您将此代码推送到一个远程存储库(如Github)中,而其他人克隆了该远程存储库,那么您现在处于重写历史的状态。当其他人尝试在这之后下拉您的最新更改时,他们将收到一条消息,指示这些更改无法应用,因为它不是快速前进的。

要解决这个问题,他们必须删除现有的存储库并重新克隆它,或者按照git-rebase手册页中"从上游重新备份恢复"下的说明进行操作。

将来,如果不小心用敏感信息提交了一些更改,但在推送到远程存储库之前注意到了这些更改,则会有一些更容易的修复。如果最后一次提交是添加敏感信息的提交,则只需删除敏感信息,然后运行:

1
git commit -a --amend

这将通过您所做的任何新更改来修改以前的提交,包括使用git rm完成的整个文件删除。如果更改进一步回到历史记录中,但仍没有推送到远程存储库,则可以执行交互式重新平衡:

1
git rebase -i origin/master

这将打开一个编辑器,其中包含自上次使用远程存储库的共同祖先以来所做的提交。在表示提交的任何行中将"pick"更改为"edit",并保存并退出。Git将浏览这些更改,并将您留在一个您可以:

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

对于每一个有敏感信息的变更。最终,您将回到您的分支,并且可以安全地推动新的更改。


更改密码是一个好主意,但对于从回购历史中删除密码的过程,我建议使用bfg repo cleaner,这是一种更快、更简单的替代方法,而不是专为从git repo中删除私有数据而设计的git-filter-branch

创建一个private.txt文件,列出要删除的密码等(每行一个条目),然后运行此命令:

1
$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

将扫描您的repo历史记录中阈值大小(默认为1MB)以下的所有文件,并且任何匹配的字符串(不在最新提交中)将替换为字符串"***已删除***"。然后可以使用git gc清除死区数据:

1
$ git gc --prune=now --aggressive

bfg通常比运行git-filter-branch快10-50倍,并且根据这两个常见的用例简化和定制选项:

  • 删除疯狂的大文件
  • 删除密码、凭据和其他私人数据

完全公开:我是bfg repo cleaner的作者。


我推荐大卫·安德希尔的剧本,对我来说很有魅力。

它除了添加natacado的filter分支之外,还添加了以下命令来清理它留下的混乱:

1
2
3
rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune

完整的剧本(全部归功于大卫·安德希尔)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo"Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

如果更改为以下命令,最后两个命令可能工作得更好:

1
2
git reflog expire --expire=now --all && \
git gc --aggressive --prune=now


如果你推到了Github,那么即使你一秒钟后强行将其推开,也为时已晚。

  • Github的承诺一直悬而未决。

    但是,如果您与Github员工联系,他们确实有权删除这些挂起的提交,这是您应该做的:如何从Github中删除挂起的提交?

    悬而未决的承诺可以通过以下方式看到:

    • 提交Web UI:https://github.com/cirosantilli/test-dangling/commit/53df36c09f09b59b59f2faa34eba15cd89ef8e83(回程机器)
    • API:https://api.github.com/repos/cirosantilli/test-dangling/commits/53df36c09f092bbb59f2faa34eba15cd89ef8e83(回程机器)

    在提交时获取源代码的一个方便方法是使用下载zip方法,它可以接受任何引用,例如:https://github.com/cirosantilli/myrepo/archive/sha.zip

  • 可以通过以下方式获取丢失的shas:

    • 使用type":"PushEvent"列出API事件。例如,mine:https://api.github.com/users/cirosantilli/events/public(回程机器)
    • 有时,通过查看试图删除内容的拉请求的shas,更方便
  • 有像http://gtorrent.org/和https://www.githubarchive.org/这样的刮削者定期收集github数据并将其存储到其他地方。

    我找不到他们是否刮取了实际的提交差异,但技术上是可能的。

为了测试这一点,我创建了一个repo:https://github.com/cirosantilli/test-dangling并完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
git init
git remote add origin [email protected]:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

但是,如果删除存储库,即使立即从API中删除提交也会消失,并给出404,例如https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5ff0891696819f3b2d653f7a3824即使重新创建具有相同名称的存储库,也会起作用。

所以我建议的行动方案是:

  • 更改您的凭据

  • 如果这还不够(如裸照):

    • 你有价值的问题数据吗?

      • 否:删除存储库
      • 是:联系支持部门

要清楚:接受的答案是正确的。先试试看。但是,对于某些用例来说,它可能不必要地复杂,特别是当您遇到诸如"致命:错误修订——删减为空"之类令人讨厌的错误,或者真的不关心您的回购历史时。

另一种选择是:

  • CD到项目基础分支机构
  • 删除敏感代码/文件
  • rm-rf.git/从中删除所有git信息你的代码
  • 转到Github并删除您的存储库
  • 按照本指南将代码推送到新的存储库中,就像平常一样-https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
  • 这当然会删除所有提交历史记录分支,以及GitHub回购和本地Git回购中的问题。如果这是不可接受的,你将不得不使用另一种方法。

    称之为核选择。


    这是我在Windows中的解决方案

    git filter-branch --tree-filter"rm -f 'filedir/filename'" HEAD

    git push --force

    确保路径正确否则就不行了

    我希望它有帮助。


    使用筛选器分支:

    1
    2
    3
    git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch *file_path_relative_to_git_repo*' --prune-empty --tag-name-filter cat -- --all

    git push origin *branch_name* -f

    您可以使用git forget-blob

    使用非常简单。你可以在这里得到更多的信息

    Completely remove a file from a git repository with git forget-blob

    它将从您的历史记录、回流、标记等所有提交中消失。

    我偶尔会遇到同样的问题,每次我必须回到这篇文章和其他文章,这就是为什么我自动化了这个过程。

    堆栈溢出的贡献者的信用,允许我把它放在一起


    到目前为止我已经做过几次了。请注意,一次只能处理一个文件。

  • 获取修改文件的所有提交的列表。底部的那个将是第一个提交:

    git log --pretty=oneline --branches -- pathToFile

  • 要从历史记录中删除文件,请使用第一个commit sha1和上一个命令中的文件路径,并将它们填充到该命令中:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch ' -- ..


  • 所以,看起来是这样的:

    1
    2
    git rm --cached /config/deploy.rb
    echo /config/deploy.rb >> .gitignore

    Remove cache for tracked file from git and add that file to .gitignore list


    在我的Android项目中,我在app/src/main/res/values/folder中将admob_keys.xml作为单独的XML文件。为了删除这个敏感文件,我在下面的脚本中使用了,并且工作得很好。

    1
    2
    3
    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