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


353

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

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

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

有没有一种方法可以删除您Git历史记录中特定文件的所有痕迹?



Answers:


448

出于所有实际目的,您首先要担心的是更改密码!从您的问题尚不清楚,您的git存储库是完全本地的还是在其他地方是否有远程存储库;如果它是远程的并且不受他人的保护,则您有问题。如果在修复此问题之前有人克隆了该存储库,他们将在其本地计算机上拥有您的密码副本,并且您无法强迫他们将其历史记录更新为“固定”版本。您唯一可以做的安全的事就是将您的密码更改为在您使用过的所有其他地方使用的密码。


解决了这个问题,下面是解决方法。GitHub作为FAQ完全回答了这个问题

Windows用户注意事项:在此命令中使用双引号(“)而不是单引号

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

更新2019:

这是常见问题解答中的当前代码:

  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

请记住,一旦将此代码推送到GitHub之类的远程存储库,而其他人又克隆了该远程存储库,您现在就处于重写历史记录的状态。当其他人在此之后尝试提取您的最新更改时,他们会收到一条消息,指示无法应用更改,因为这不是一个快进操作。

要解决此问题,他们必须删除其现有存储库并重新克隆它,或者按照git-rebase联机帮助页中 “从UPSTREAM REBASE恢复”下的说明进行操作。

提示:执行git rebase --interactive


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

git commit -a --amend

这将使用您所做的任何新更改来修改先前的提交,包括使用删除整个文件git rm。如果更改已恢复到历史记录,但仍未推送到远程存储库,则可以进行交互式变基:

git rebase -i origin/master

这将打开一个编辑器,其中包含自您上次使用远程存储库的共同祖先以来所做的提交。在代表包含敏感信息的提交的任何行上,将“ pick”更改为“ edit”,然后保存并退出。Git将逐步进行更改,并将您留在一个可以进行以下操作的地方:

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

对于每次更改都带有敏感信息。最终,您将回到分支上,并且可以安全地推送新更改。


5
完美的家伙,这是一个很好的答案。你救了我的一天。
zzeroo 2010年

18
我想补充一个位-在Windows上,你应该使用双引号(“),而不是单身。
ripper234

4
得到这个工作。我迷失了翻译。我在这里使用链接而不是命令。同样,Windows命令最终要求在ripper234中提到双引号,如MigDus建议的完整路径,并且不包括链接粘贴为新换行指示符的“ \”字符。最终命令如下所示:git filter-branch --force --index-filter“ git rm --cached --ignore-unmatch src [Project] [File]。[ext]” --prune-empty --tag-名称过滤器猫--全部
Eric Swanson

3
您的filter-branch代码与链接到的github页面中的代码之间似乎存在实质性差异。例如他们的第三条线--prune-empty --tag-name-filter cat -- --all。解决方案是否已更改,或者我缺少什么?
geotheory,2015年

2
这个解决方案看起来不错,但是如果我引入了要在初始提交<introduction-revision-sha1>..HEAD中删除的文件,将无法正常工作。它仅从第二次提交以后删除文件。(如何将初始提交包含在提交范围内?)在这里指出了保存方法:help.github.com/articles/…– 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
white_gecko

91

更改密码是一个好主意,但是对于从存储库历史记录中删除密码的过程,我建议使用BFG Repo-Cleaner,这是一种快速,简单的替代方法,可以git-filter-branch明确地从Git存储库中删除私有数据。

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

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

存储库历史记录中所有阈值大小(默认为1MB以下)的文件都将被扫描,并且所有匹配的字符串(不在您的最新提交中)都将替换为字符串“ *** REMOVED ***”。然后,您可以git gc用来清除无效数据:

$ git gc --prune=now --aggressive

BFG通常比运行速度快10到50倍git-filter-branch,并且围绕以下两种常见用例对选项进行了简化和定制:

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

完全公开:我是BFG Repo-Cleaner的作者。


这是一个选项,但是当使用密码(例如,建立数据库连接)时,它可能会破坏您的应用程序。我希望使用当前接受的答案,因为仍然可以将密码保留在您的工作副本中,并使用.gitignore忽略包含它们的文件。
Henridv

6
这是一个巨大的胜利。经过几次尝试,我能够使用它来彻底清除包含来自私人仓库的敏感信息的提交,并用修订后的历史记录强行更新远程仓库。值得注意的是,您必须确保回购协议(HEAD)的尖端本身干净无任何敏感数据,因为此提交被视为“受保护的”,并且不会被此工具修改。如果不是,则只需手动清洁/更换git commit。否则,请为开发人员工具箱中的新工具+1 :)
Matt Borja 2014年

1
@Henridv根据我最近的评论,假定您的应用程序当前位于分支机构的尖端或顶部(即最新提交),它不会破坏您的应用程序。该工具将These are your protected commits, and so their contents will NOT be altered在遍历和修订其余提交历史记录时显式报告您的最后一次提交。但是,如果需要回滚,则可以,只需***REMOVED***在刚刚回滚的提交中进行搜索。
Matt Borja 2014年

1
BFG +1(如果您已安装Java或不介意安装它)。一个问题是,BFG拒绝删除HEAD中包含的文件。因此最好先进行一次提交,在该提交中将删除所需的文件,然后再运行BFG。之后,您可以还原最后一次提交,现在它不会改变任何事情。
Fr0sT 2014年

1
这实际上应该被接受为正确答案。请问包装盒上的内容!
gjoris '16

21

如果您推送到GitHub,则强行推送还不够,请删除存储库或联系支持部门

即使您随后强行按下一秒钟,也不足以按以下说明进行操作。

唯一有效的措施是:

  • 什么泄露了诸如密码之类的可更改凭证?

    • 是的:立即修改您的密码,并考虑使用更多的OAuth和API密钥!
    • 否(裸照):

      • 您是否关心存储库中的所有问题是否都受到影响?

        • 否:删除存储库
        • 是:

          • 联系支持
          • 如果泄漏对您来说非常关键,以至于您愿意让某些存储库停机以减少泄漏的可能性,请在等待GitHub支持答复时将其设置为私有

强行推一秒钟还不够,因为:

如果删除了存储库,而不是仅仅力的推动。然而,提交你从API马上甚至消失,并给404,如https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a3824该作品即使您重新创建另一个具有相同名称的存储库。

为了测试这一点,我创建了一个仓库:https : //github.com/cirosantilli/test-dangling并做到了:

git init
git remote add origin git@github.com: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

另请参阅:如何从GitHub删除悬空的提交?


20

我推荐大卫·安德希尔(David Underhill)创作的这个剧本,对我来说就像一个魅力。

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

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

完整脚本(全部归David Underhill所有)

#!/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

如果更改为以下内容,则后两个命令可能会更好地工作:

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

1
请注意,您使用的expire和prune是不正确的,如果您未指定日期,则默认为所有超过2周的prune提交。您想要的是所有提交,所以这样做:git gc --aggressive --prune=now
Adam Parkin 2012年

@Adam Parkin我将把代码留在答案中,因为它是来自David Underhill网站上的脚本的,您可以在那里评论,如果他更改了它,我会更改此答案,因为我真的不知道git好。修剪之前的expire命令不会影响吗?
杰森·古玛

1
@MarkusUnterwaditzer:那个不能用于推送的提交。
Max Beikirch 2013年

也许您应该将所有命令都放入答案中;这将更加一致,并且不需要在精神上合并各个职位:)
Andrew Mao

9

要明确:接受的答案是正确的。请先尝试。但是,对于某些用例而言,它可能会不必要地变得复杂,尤其是当您遇到令人讨厌的错误(例如“致命:错误的修订版-删节-清空”),或者真的不在乎您的回购记录时。

一种替代方法是:

  1. cd到项目的基础分支
  2. 删除敏感代码/文件
  3. rm -rf .git /#从代码中删除所有git信息
  4. 转到github并删除您的存储库
  5. 遵循本指南,将您的代码像往常一样推送至新的存储库-https: //help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

当然,这将删除所有提交历史分支,以及来自github存储库和本地git存储库的问题。如果这是不可接受的,则必须使用其他方法。

将此称为核选项。


9

您可以使用git forget-blob

用法很简单git forget-blob file-to-forget。您可以在此处获取更多信息

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

它会从您的历史记录,引用日志,标签等所有提交中消失

我时不时遇到相同的问题,每次不得不回到这篇文章和其他文章时,这就是我使流程自动化的原因。

归功于Stack Overflow的贡献者,这使我可以将它们放在一起


8

这是我在Windows中的解决方案

git filter-branch --tree-filter“ rm -f'filedir / filename'”头

git push --force

确保路径正确,否则将不起作用

希望对您有所帮助


8

使用filter-branch

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

3

迄今为止,我不得不这样做几次。请注意,这一次仅可处理1个文件。

  1. 获取所有修改文件的提交的列表。底部的将是第一次提交:

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

  2. 要从历史记录中删除文件,请使用第一个提交sha1和上一个命令的文件路径,并将其填充到此命令中:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..


3

因此,它看起来像这样:

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

从git中删除跟踪文件的缓存并将其添加到.gitignore列表中


2

在我的Android项目中,我将admob_keys.xml作为app / src / main / res / values /文件夹中的单独xml文件。为了删除此敏感文件,我使用了以下脚本,并且运行良好。

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.