Git:如何从历史提交中删除文件?

157

例如,我有一个 ID 为 56f06019 的提交。在那个提交中,我意外地提交了一个大文件(50 MB)。在另一个提交中,我添加了相同的文件,但是大小正确(小)。现在当我克隆我的存储库时,它太重了。如何从存储库历史记录中删除该大文件以减小存储库的大小?


在我的情况下,这不是一个大文件,而是一个包含数据库凭据的配置文件。那时我正在学习Git,当时我还不知道.gitignore的存在。 - Rashi
1
相关内容:https://help.github.com/articles/removing-sensitive-data-from-a-repository/ - Trevor Boyd Smith
5个回答

211

Pro Git书的第9章有一节关于移除对象

让我在这里简要概述一下步骤:

git filter-branch --index-filter \
    'git rm --cached --ignore-unmatch path/to/mylarge_50mb_file' \
    --tag-name-filter cat -- --all

和前面描述的变基选项一样,filter-branch是一种重写操作。如果您已经发布了历史记录,则必须使用--force强制推送新引用。 filter-branch方法比rebase方法更为强大,因为它:
  • 允许您同时处理所有分支/引用
  • 可以即时重命名任何标签
  • 即使自添加文件以来有几个合并提交,也可以干净地操作
  • 即使在(一个)分支的历史记录中多次添加/删除文件,也可以干净地操作
  • 不会创建新的、无关的提交,而是在修改与之关联的树时进行复制。这意味着保留了诸如签名提交、提交注释等内容
filter-branch也会保留备份,因此仓库的大小不会立即减小,除非您过期引用日志并进行垃圾回收。
rm -Rf .git/refs/original       # careful
git gc --aggressive --prune=now # danger

1
值得注意的是,这似乎在Windows cmd.exe下无法工作。不过在cygwin下运行良好。 - Fake Name
2
我通过使用双引号而不是单引号(在Windows Server 2012 cmd.exe上)使上述git filter-branch工作。 - JCii
2
对我而言有效的是这个filter-branch命令行。git filter-branch --force --index-filter 'git rm --ignore-unmatch --cached PathTo/MyFile/ToRemove.dll' -- fbf28b005^.. 然后 rm --recursive --force .git/refs/originalrm --recursive --force .git/logs接着我使用了 git prune --expire nowgit gc --aggressive这对我来说比您上面列出的确切步骤更好。感谢您包含Git Pro书的链接,它非常有价值。 - dacke.geo
为了缩小仓库,我使用了git filter-branch文档中列出的命令:https://git-scm.com/docs/git-filter-branch#_checklist_for_shrinking_a_repository - Ludovic Ronsin
1
@AlexanderMyasnikov 因为通常文件被删除是出于重要原因(比如,它们很大或包含敏感信息)。除非你处理所有分支,否则该文件仍将存在于存储库中。此外,在 filter-branch 之后还有备份是一件好事。 - sehe
显示剩余10条评论

22

2
太棒了!这个完成了任务。如此简单美观。 - Ivan
非常感谢您提供如此精彩而简洁的解决方案。 - Mayank Kataria
这个操作需要重写整个历史记录,尽管我想要删除的文件只有5个提交版本。历史记录总共有大约30000个提交版本。 - thanos.a
git obliterate基本上与接受的答案 相同 - Y. E.

8

我尝试在Windows上使用以下答案:https://dev59.com/9moy5IYBdhLWcg3wJKzR#8741530

单引号在Windows上不起作用,需要用双引号。

以下方法适用于我。

git filter-branch --force --index-filter "git rm --cached --ignore-unmatch PathRelativeRepositoryRoot/bigfile.csv" -- --all

删除大文件后,我成功将更改推送到GitHub主分支。


某种方式下,.\relative\path\to\file* 对我不起作用。我需要使用 *file* 代替。 - Ooker

1

您需要在交互模式下执行git rebase,可以参考这里的示例:如何在GitHub上删除提交?以及如何删除旧的提交记录

如果您的提交记录在HEAD减去10个提交之前:

$ git rebase -i HEAD~10

在编辑完您的历史记录后,您需要推送“新”历史记录,您需要添加+以强制执行(请参见push options中的refspec):
$ git push origin +master

如果其他人已经克隆了你的代码库,你需要通知他们,因为你刚刚更改了历史记录。

4
这并不会将大文件从历史记录中移除。另外,强制推送的官方方式是使用git push --force或者git push -f(这样不需要知道分支推送目标)。 - sehe
根据问题描述,新文件与旧文件完全相同,即路径相同。这就是为什么您不能直接在该路径上使用git rm的原因。 - Loïc d'Anterroches
2
@sehe,如果你使用rebase命令删除包含大文件的提交,那么它将永久消失。 - vonbrand
只从您重新定位的那个分支中选择@vonbrand。我不会假设“from”分支被删除。但是,如果您删除修订树分支,那将有所帮助:_ - sehe
@sehe,没错,你必须追踪所有包含有问题提交的分支。如果它在仓库中的某些繁忙之前,你将需要进行大量的重新组织。但是rebase确实是处理这种情况的工具。 - vonbrand
我猜在单个提交的上下文中,使用rebase是很好的选择,而且可能更容易解释。我的回答只是更加通用。想象一下一个大型二进制文件,在多年的提交中发生了变化。你不会想手动地重复基础操作。@vonbrand感谢您的富有成效的讨论。 - sehe

-1

你可以使用一个简单的命令来删除

 git rm -r -f app/unused.txt 
 git rm -r -f yourfilepath

这将使文件保留在历史记录中。问题是要将文件从历史记录中删除。因此,就像文件从未被添加一样。 - Simon Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接