完全删除所有Git存储库提交历史logging中的文件

我意外地提交了一个不需要的文件(parsing合并时, filename.orig )到我的数据库几个提交前,没有我知道,直到现在。 我想彻底删除存储库历史logging中的文件。 是否有可能重写更改历史logging,使得filename.orig从未添加到存储库中?

如果您的情况不是问题中描述的情况,请不要使用这个配方。 这个配方是为了修复一个不好的合并,并把你的好提交重放到一个固定的合并。

虽然filter-branch将做你想做的,这是一个相当复杂的命令,我可能会select用git rebase做到这一点。 这可能是个人喜好。 filter-branch可以在一个稍微复杂一点的命令中完成,而rebase解决scheme一次只能执行一次等价的逻辑操作。

尝试下面的配方:

 # create and check out a temporary branch at the location of the bad merge git checkout -b tmpfix <sha1-of-merge> # remove the incorrectly added file git rm somefile.orig # commit the amended merge git commit --amend # go back to the master branch git checkout master # replant the master branch onto the corrected merge git rebase tmpfix # delete the temporary branch git branch -d tmpfix 

(注意,你实际上并不需要一个临时分支,你可以使用'detached HEAD'来做到这一点,但是你需要记下由git commit --amend生成的commit id – 为了提供给git rebase命令而不是使用临时分支名称。)

简介:您有5个解决scheme可用

原始的海报说:

我意外地提交了一个不需要的文件…到我的数据库几个提交之前…我想完全删除存储库的历史文件。

是否有可能重写更改历史logging,使得filename.orig从未添加到存储库中?

有许多不同的方法可以从git中彻底删除文件的历史logging:

  1. 修改提交。
  2. 硬重置(可能加上rebase)。
  3. 非交互式的rebase。
  4. 互动资产。
  5. 筛选分支。

在原始海报的情况下,修改提交本身并不是一个真正的select,因为之后他做了几个额外的提交,但为了完整起见,我还将解释如何去做,对于任何只是想要的人修改他们以前的承诺。

请注意,所有这些解决scheme都涉及以另一种方式更改/重写历史logging/提交,因此任何具有旧提交副本的人都必须做额外的工作才能将历史logging与新历史logging重新同步。


解决scheme1:修改提交

如果您在之前的提交中意外地进行了更改(例如添加文件),并且您不希望更改的历史logging再次存在,那么您可以简单地修改之前的提交以从中删除该文件:

 git rm <file> git commit --amend --no-edit 

解决scheme2:硬重置(可能再加一个重置)

像解决scheme#1一样,如果你只是想摆脱你以前的提交,那么你也可以select简单地对其父进行重置:

 git reset --hard HEAD^ 

该命令将硬重置您的分支到前一个父母提交。

但是 ,如果像原来的海报一样,在提交后您已经提交了多个提交,您可以使用硬重置对其进行修改,但这样做也涉及到使用重设。 以下是您可以用来修改历史logging中提交的步骤:

 # Create a new branch at the commit you want to amend git checkout -b temp <commit> # Amend the commit git rm <file> git commit --amend --no-edit # Rebase your previous branch onto this new commit, starting from the old-commit git rebase --preserve-merges --onto temp <old-commit> master # Verify your changes git diff master@{1} 

解决scheme3:非交互式Rebase

如果你只是想完全从历史中删除一个提交,这将工作。

 # Create a new branch at the parent-commit of the commit that you want to remove git branch temp <parent-commit> # Rebase onto the parent-commit, starting from the commit-to-remove git rebase --preserve-merges --onto temp <commit-to-remove> master # Or use `-p` insteda of the longer `--preserve-merges` git rebase -p --onto temp <commit-to-remove> master # Verify your changes git diff master@{1} 

解决scheme4:交互式Rebases

这个解决scheme将允许你完成与解决scheme#2和#3相同的事情,即修改或删除比前一个提交更早的提交,那么你select使用哪种解决scheme就取决于你。 由于性能方面的原因,交互式底图并不适合重新绑定数百个提交,所以我会在这种情况下使用非交互式底图或滤镜分支解决scheme(请参见下文)。

要开始交互式资料库,请使用以下内容:

 git rebase --interactive <commit-to-amend-or-remove>~ # Or `-i` instead of the longer `--interactive` git rebase -i <commit-to-amend-or-remove>~ 

这将导致git将提交历史回退到您要修改或删除的提交的父级。 然后,它会在任何编辑器git被设置为使用(默认是Vim)下以相反的顺序呈现一个rewound提交列表:

 pick 00ddaac Add symlinks for executables pick 03fa071 Set `push.default` to `simple` pick 7668f34 Modify Bash config to use Homebrew recommended PATH pick 475593a Add global .gitignore file for OS X pick 1b7f496 Add alias for Dr Java to Bash config (OS X) 

您想要修改或删除的提交将位于此列表的顶部。 要删除它,只需在列表中删除它的行。 否则,用第一行的“编辑”replace“pick”,如下所示:

 edit 00ddaac Add symlinks for executables pick 03fa071 Set `push.default` to `simple` 

接下来,inputgit rebase --continue 。 如果您select完全删除提交,那么您只需执行此操作(validation除外,请参阅此解决scheme的最后一步)。 另一方面,如果你想修改commit,那么git会重新提交commit,然后暂停rebase。

 Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue 

在这一点上,你可以删除文件并修改提交,然后继续rebase:

 git rm <file> git commit --amend --no-edit git rebase --continue 

而已。 作为最后一步,无论您是修改了提交还是完全删除了提交,最好通过在分支之前将其状态与其分支进行比较来validation是否没有其他意外的更改发生:

 git diff master@{1} 

解决scheme5:过滤分支

最后,如果你想从历史中彻底清除文件存在的所有痕迹,这个解决scheme是最好的,而其他的解决scheme都不能完全胜任。

 git filter-branch --index-filter \ 'git rm --cached --ignore-unmatch <file>' 

这将从根提交开始,从所有提交中移除<file> 。 如果你只是想重写提交范围HEAD~5..HEAD ,那么你可以把它作为一个额外的parameter passing给filter-branch ,正如在这个答案中指出的那样:

 git filter-branch --index-filter \ 'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD 

同样,在filter-branch完成之后,最好通过在过滤操作之前将其分支与以前的状态进行比较来validation没有其他意外的更改:

 git diff master@{1} 

Filter-Branch Alternative:BFG Repo Cleaner

我听说BFG Repo Cleaner工具的运行速度比git filter-branch快,所以你可能也想检查一下。 甚至在filter分支文档中正式提到这是一个可行的select:

git-filter-branch允许你对Git历史进行复杂的shell脚本重写,但如果你只是删除不需要的数据,比如大文件或密码,你可能不需要这种灵活性。 对于这些操作,您可能需要考虑BFG Repo-Cleaner ,一种基于JVM的git-filter-branch替代scheme,对于这些用例来说,速度通常要快10-50倍,而且具有不同的特性:

  • 任何特定版本的文件清理一次 。 与git-filter-branch不同的是,BFG不会让你有机会根据历史logging中的何时何地提交文件。 这个限制提供了BFG的核心性能优势,非常适合清理不良数据的任务 – 你不关心坏数据在哪里 ,你只是希望它消失

  • 默认情况下,BFG充分利用多核机器,并行清理提交文件树。 git-filter-branch按照顺序(即以单线程方式)清除提交,尽pipe可以在针对每个提交执行的脚本中编写包含它们自己的并行策略的filter。

  • 命令选项比git-filter分支更具限制性,专门用于去除不需要的数据 – 例如:– --strip-blobs-bigger-than 1M

其他资源

  1. Pro Git§6.4 Git工具 – 重写历史logging 。
  2. git-filter-branch(1)手册页 。
  3. git-commit(1)手册页 。
  4. git-reset(1)手册页 。
  5. git-rebase(1)手册页 。
  6. BFG Repo Cleaner (另请参阅创build者自己的答案 )。

如果你还没有提交任何东西,只要把文件和git commit --amend

如果你有

 git filter-branch \ --index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD 

会经历从merge-pointHEAD每个变化,删除文件名.orig并重写改变。 使用--ignore-unmatch表示如果由于某种原因filename.orig从一个更改中丢失,命令将不会失败。 这是从git-filter-branch手册页的Examples部分推荐的方法。

Windows用户请注意:文件path必须使用正斜杠

这是最好的方法:
http://github.com/guides/completely-remove-a-file-from-all-revisions

只要确保先备份文件的副本。

编辑

氖的编辑不幸被拒绝在审查。
看到下面的霓虹灯后,它可能包含有用的信息!


例如,要删除所有意外落入git仓库的*.gz文件:

 $ du -sh .git ==> eg 100M $ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD $ git push origin master --force $ rm -rf .git/refs/original/ $ git reflog expire --expire=now --all $ git gc --prune=now $ git gc --aggressive --prune=now 

那仍然不适合我? (我目前在git版本1.7.6.1)

 $ du -sh .git ==> eg 100M 

不知道为什么,因为我只有一个主分支。 无论如何,我终于通过推入一个新的空的和纯粹的git仓库,真正清理了我的git repo,例如

 $ git init --bare /path/to/newcleanrepo.git $ git push /path/to/newcleanrepo.git master $ du -sh /path/to/newcleanrepo.git ==> eg 5M 

(是!)

然后我克隆到一个新的目录,并将它的.git文件夹移到这个。 例如

 $ mv .git ../large_dot_git $ git clone /path/to/newcleanrepo.git ../tmpdir $ mv ../tmpdir/.git . $ du -sh .git ==> eg 5M 

(耶!终于清理了!)

validation一切正常后,可以删除../large_dot_git../tmpdir目录(也许在几个星期或一个月后,以防万一…)

重写Git历史logging要求更改所有受影响的提交标识,因此每个在项目上工作的人都需要删除其旧版本的repo,并在清理历史logging后重新创build一个克隆。 越不便的人越多,你需要一个很好的理由去做 – 你多余的文件并不是真的会造成问题,但是如果只是在做这个项目,那么你也可以清理Git的历史至!

为了尽可能简单,我build议使用BFG Repo-Cleaner ,它是一个更简单,更快速的替代git-filter-branch专门用于从Git历史中删除文件的工具。 让你的生活更轻松的一个方法就是它实际上默认处理所有的裁判(所有标签,分支等),但速度也快了10-50倍 。

你应该仔细按照这里的步骤: http : //rtyley.github.com/bfg-repo-cleaner/#usage – 但核心是这样的:下载BFG jar (需要Java 6或更高版本)并运行此命令:

 $ java -jar bfg.jar --delete-files filename.orig my-repo.git 

您的整个存储库历史logging将被扫描,并且任何名为filename.orig (不在您最新的提交中 )将被删除。 这比使用git-filter-branch做同样的事情要容易得多!

充分披露:我是BFG Repo-Cleaner的作者。

 You should probably clone your repository first. Remove your file from all branches history: git filter-branch --tree-filter 'rm -f filename.orig' -- --all Remove your file just from the current branch: git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD Lastly you should run to remove empty commits: git filter-branch -f --prune-empty -- --all 

我发现最简单的方法是由leontalbot (作为评论),这是由Anoopjohn发表的post 。 我觉得它值得自己的空间作为答案:

(我把它转换成bash脚本)

 #!/bin/bash if [[ $1 == "" ]]; then echo "Usage: $0 FILE_OR_DIR [remote]"; echo "FILE_OR_DIR: the file or directory you want to remove from history" echo "if 'remote' argument is set, it will also push to remote repository." exit; fi FOLDERNAME_OR_FILENAME=$1; #The important part starts here: ------------------------ git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all rm -rf .git/refs/original/ git reflog expire --expire=now --all git gc --prune=now git gc --aggressive --prune=now if [[ $2 == "remote" ]]; then git push --all --force fi echo "Done." 

所有的信用都去Annopjohn ,并leontalbot指出来。

注意

请注意,该脚本不包括validation,所以请确保您不会犯错误,并确保您有备份以防出现问题。 它为我工作,但它可能不适用于你的情况。 谨慎使用(如果您想知道发生了什么,请点击链接)。

为了补充Charles Bailey的解决scheme,我只是使用git rebase -i从早期的提交中删除了不需要的文件,它像一个魅力一样工作。 步骤:

 # Pick your commit with 'e' $ git rebase -i # Perform as many removes as necessary $ git rm project/code/file.txt # amend the commit $ git commit --amend # continue with rebase $ git rebase --continue 

当然, git filter-branch是要走的路。

可悲的是,这不足以从你的回购完全删除filename.orig ,因为它仍然可以被标签,reflog条目,遥控器等引用。

我build议删除所有这些引用,然后调用垃圾回收器。 你可以使用这个网站上的git forget-blob脚本来完成所有这一切。

git forget-blob filename.orig

你也可以使用:

git reset HEAD file/path

这是git filter-branchdevise目的。