如何从Git仓库的提交历史中删除一个大文件?

1081
我不小心把一个DVD剪辑文件放进了一个网站项目中,粗心地使用了git commit -a -m ...命令,结果仓库膨胀了2.2GB。下次我做了一些修改,删除了视频文件,然后提交了所有更改,但是压缩文件仍然存在于仓库的历史记录中。
我知道我可以从这些提交中创建分支,然后将一个分支合并到另一个分支上。但是我应该怎么做才能合并这两个提交,使得这个大文件不会在历史记录中显示,并且在垃圾回收过程中被清除掉呢?

13
这篇文章应该能帮助你:http://help.github.com/removing-sensitive-data/。 - MBO
4
相关链接:完全从Git仓库提交历史中删除文件 - user456814
1
请注意,如果你的大文件在子目录中,你需要指定完整的相对路径。 - Johan
10
请看一下我的回答,它使用了git filter-repo。你不应再使用git filter-branch,因为它非常慢且经常难以使用。git filter-repo快约100倍。 - Donat
2
经过我第十次尝试,正确的答案是git应该拒绝检入这些文件,而不是创建所有这些骚动。 - Todd Hoff
显示剩余7条评论
25个回答

10
请注意,这些命令可能会带来很大的破坏性。如果有更多人在仓库上工作,他们都需要拉取新的树。如果你的目标不是为了减小文件大小,那么中间的三个命令是不必要的。因为过滤分支会创建被移除文件的备份,并且它可能会在那里保存很长时间。
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune
git push origin master --force

14
除非你想给自己带来巨大的痛苦,否则请勿运行这些命令。它删除了我很多原始源代码文件。我认为它会清除GIT提交历史记录中的一些大文件(如原始问题所述),但我认为该命令旨在永久清除您的原始源代码树中的文件(大不相同!)。我的系统:Windows,VS2012,Git源代码控制提供者。 - Contango
2
我使用了这个命令:git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all,而不是你代码中的第一个命令。 - Kostanos
2
@mkljun,请至少删除“git push origin master --force”!首先,这与原始问题无关 - 作者并没有询问如何编辑提交并将更改推送到某个存储库。其次 - 这是危险的,您真的可以删除很多文件并将更改推送到远程存储库,而不先检查已删除的内容是否是一个好主意。 - Ezh

9

8

如果你知道你的提交是最近的,而不是浏览整个树,可以按照以下步骤进行操作:

git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

该命令将删除最近的10个提交中的名为LARGE_FILE.zip的文件。


6

这将从您的历史记录中移除它

git filter-branch --force --index-filter 'git rm -r --cached --ignore-unmatch bigfile.txt' --prune-empty --tag-name-filter cat -- --all

这在我的情况下有效。我在你的主分支上运行它。 - SMPLYJR

5
我基本上按照这个答案所说的做了: https://dev59.com/fXI95IYBdhLWcg3w5iU4#11032521
(为了历史记录,我将在此处复制粘贴它)
$ git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch YOURFILENAME" HEAD
$ rm -rf .git/refs/original/ 
$ git reflog expire --all 
$ git gc --aggressive --prune
$ git push origin master --force

这种方法不奏效,因为我经常会改名或移动文件夹,导致一些大文件处于被重命名的文件夹中。我猜测由于树形对象指向这些文件,垃圾回收器无法删除对这些文件的引用。

我的最终解决方案是:

# First, apply what's in the answer linked in the front
# and before doing the gc --prune --aggressive, do:

# Go back at the origin of the repository
git checkout -b newinit <sha1 of first commit>
# Create a parallel initial commit
git commit --amend
# go back on the master branch that has big file
# still referenced in history, even though 
# we thought we removed them.
git checkout master
# rebase on the newinit created earlier. By reapply patches,
# it will really forget about the references to hidden big files.
git rebase newinit

# Do the previous part (checkout + rebase) for each branch
# still connected to the original initial commit, 
# so we remove all the references.

# Remove the .git/logs folder, also containing references
# to commits that could make git gc not remove them.
rm -rf .git/logs/

# Then you can do a garbage collection,
# and the hidden files really will get gc'ed
git gc --prune --aggressive

我的仓库(.git)从32MB变成了388KB,即使使用filter-branch也无法清理。


4
使用 UI 工具 Git Extensions,它有一个名为“查找大文件”的插件,可以在存储库中查找大文件并允许永久删除它们。
在使用此工具之前,请勿使用 'git filter-branch',因为它将无法找到被 'filter-branch' 删除的文件(尽管 'filter-branch' 不会完全从存储库包文件中删除文件)。

这种方法对于大型代码库来说太慢了。列出大文件需要一个多小时的时间。然后当我尝试删除文件时,经过一个小时,它只处理完我想要删除的第一个文件的三分之一。 - kristianp
是的,它很慢,但它能完成工作…… 你知道更快的方法吗? - Nir
1
没有使用过它,但根据本页面上的另一个答案,BFG Repo-Cleaner 是一个不错的选择。 - kristianp
Git Extension很好用,也很简单。但是它在内部使用git filter-branch,所以删除操作非常缓慢。 - Alex from Jitbit

3

git filter-branch 是一个强大的命令,可以使用它从提交历史中删除巨大的文件。该文件将在一段时间内保留,Git 将在下一次垃圾回收时将其删除。 下面是完整的过程,来自于删除 Git 提交历史中的文件。为了安全起见,以下步骤首先在新分支上运行命令。如果结果符合预期,则将其重置回您实际想要更改的分支。

# Do it in a new testing branch
$ git checkout -b test

# Remove file-name from every commit on the new branch
# --index-filter, rewrite index without checking out
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# HEAD, execute the specified command for each commit reached from HEAD by parent link
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch file-name' HEAD

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -d test

# Push it with force
$ git push --force origin master

2
这是@Lucas的一条非常有用的评论,我决定将其发布为答案,以便更多人看到。
他们建议使用git-filter-repo并运行以下命令:git filter-repo --strip-blobs-bigger-than 10M 如果你在Windows上安装git-filter-repo遇到困难(就像我一样),请参考这个链接。
这个命令是做什么的,它是如何工作的?我不知道。如果你知道,请留下评论。
然而,之后,我的提交历史中仍然保留了所有巨大的文件,但它们不再出现在提交历史中。它起作用了。
像往常一样,在运行此命令之前,请备份你的仓库。

1
当你遇到这个问题时,git rm 是不够的,因为 Git 记得文件曾经存在于我们的历史中,因此会保留对它的引用。
更糟糕的是,变基也不容易,因为任何对 blob 的引用都会阻止 Git 垃圾回收器清理空间。这包括远程引用和 reflog 引用。
我编写了一个小脚本 git forget-blob,尝试删除所有这些引用,然后使用 git filter-branch 重写分支中的每个提交。
一旦你的 blob 完全没有被引用,git gc 就会将其删除。
使用方法非常简单: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 的答案和一些博客文章,我整理了这些内容。鸣谢他们!


你应该在Homebrew中获取这个。 - Cameron E

1
你可以使用“分支过滤器”命令来完成此操作: git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD

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