如何从Git存储库中的提交历史记录中删除/删除大文件?


708

有时,我将DVD-rip放入一个网站项目中,然后漫不经心git commit -a -m ...,然后,回购协议被激增了2.2次。下次我进行一些编辑,删除视频文件并提交所有内容,但是压缩文件仍在历史记录中。

我知道我可以从这些提交开始分支,并将一个分支重新建立到另一个分支。但是,我应该怎么做才能将2个提交合并在一起,以使大文件不显示在历史记录中,并在垃圾回收过程中清除?


9
本文应为您提供
MBO


1
请注意,如果大文件位于子目录中,则需要指定完整的相对路径。
2015年


下面的许多答案都称BFG比容易git filter-branch,但我发现相反的说法是正确的。
2540625

Answers:


604

使用BFG Repo-Cleaner,这是一种git-filter-branch专门设计用于从Git历史记录中删除不需要的文件的更简单,更快的替代方法。

认真遵循使用说明,核心部分就是这样:

$ java -jar bfg.jar --strip-blobs-bigger-than 100M my-repo.git

任何大小超过100MB的文件(不在您的最新提交中)都将从Git存储库的历史记录中删除。然后,您可以git gc用来清除无效数据:

$ git gc --prune=now --aggressive

BFG通常比运行速度快至少10-50git-filter-branch,并且通常更易于使用。

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


4
@tony值得重复整个克隆和清除​​过程,以查看是否再次出现要求您拉出消息的消息,但这几乎可以肯定是因为您的远程服务器配置为拒绝非快进更新(即,配置为阻止您进行更新)失去历史记录-这正是您要执行的操作)。您需要在遥控器上更改该设置,否则,请将更新的存储库历史记录推送到全新的空白存储库中。
罗伯托·泰利

1
@RobertoTyley谢谢。我已经尝试了3次不同的时间,并且都得到了相同的消息。因此,我还认为您将远程服务器配置为拒绝非快进更新是正确的。我将考虑将更新的存储库推送到全新的存储库。谢谢!
托尼

7
@RobertoTyley完美,您节省了我的时间,非常感谢。顺便说一句,也许应该git push --force在您执行步骤之后进行,否则远程仓库仍然不会更改。
li2

3
+1添加git push --force。另外值得注意的是:远程可能不允许强制推送(默认情况下,gitlab.com不允许。必须“取消保护”分支)。
MatrixManAtYrService

25
我认为该工具输出的特朗普行话有点过多。
克里斯(Chris)

563

如果您已将历史记录发布给其他开发人员,那么您想要做的就是极具破坏性的。有关修复历史记录后的必要步骤,请参阅文档中的“从上游变基恢复”git rebase

您至少有两个选择:git filter-branch和交互式变基,这两个都在下面说明。

使用 git filter-branch

我从Subversion导入中获取庞大的二进制测试数据时遇到了类似的问题,并写了关于从git存储库中删除数据的信息

说您的git历史记录是:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

请注意,这git lola是一个非标准但非常有用的别名。使用该--name-status开关,我们可以看到与每个提交相关联的树修改。

在“ Careless”提交中(SHA1对象名称为ce36c98),该文件oops.iso是偶然添加的DVD-rip,并在下一个提交cb14efd中删除。使用上述博客文章中描述的技术,要执行的命令是:

git filter-branch --prune-empty -d /dev/shm/scratch \
  --index-filter "git rm --cached -f --ignore-unmatch oops.iso" \
  --tag-name-filter cat -- --all

选项:

  • --prune-empty删除由于过滤操作而变为空的提交(,不更改树)。在典型情况下,此选项会产生更清晰的历史记录。
  • -d命名一个尚不存在的临时目录,以用于构建过滤的历史记录。如果您在现代Linux发行版上运行,则在其中指定/dev/shm将加快执行速度
  • --index-filter是主要事件,并且会在历史记录的每个步骤中与索引相对应。您想删除oops.iso找到的所有内容,但并非所有提交中都包含该内容。该命令git rm --cached -f --ignore-unmatch oops.iso删除存在的DVD-rip,否则不会失败。
  • --tag-name-filter描述了如何重写标签名称。过滤器cat是标识操作。您的存储库与上面的示例一样,可能没有任何标签,但是出于全面考虑,我包括了此选项。
  • -- 指定选项的结尾 git filter-branch
  • --all以下--是所有裁判的简写。像上面的示例一样,您的存储库可能只有一个ref(主文件),但是出于全面考虑,我包括了此选项。

经过一番搅拌,现在的历史是:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
|
| * f772d66 (refs/original/refs/heads/master) Login page
| | A   login.html
| * cb14efd Remove DVD-rip
| | D   oops.iso
| * ce36c98 Careless
|/  A   oops.iso
|   A   other.html
|
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

请注意,仅添加了新的“ Careless”提交other.html,并且“ Remove DVD-rip”提交不再位于master分支上。标记为分支的分支refs/original/refs/heads/master包含您的原始提交,以防万一您出错。要删除它,请按照“收缩存储库清单”中的步骤进行操作

$ git update-ref -d refs/original/refs/heads/master
$ git reflog expire --expire=now --all
$ git gc --prune=now

对于更简单的选择,克隆存储库以丢弃不需要的位。

$ cd ~/src
$ mv repo repo.old
$ git clone file:///home/user/src/repo.old repo

使用file:///...克隆URL复制对象而不是仅创建硬链接。

现在您的历史记录是:

$ git lola --name-status
* 8e0a11c (HEAD, master) Login page
| A     login.html
* e45ac59 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

前两个提交(“索引”和“管理页面”)的SHA1对象名称保持不变,因为过滤操作未修改这些提交。“无忧无虑”丢失了oops.iso,“登录页面”有了新的父代,因此他们的SHA1 确实发生了变化。

互动基础

具有以下历史:

$ git lola --name-status
* f772d66 (HEAD, master) Login page
| A     login.html
* cb14efd Remove DVD-rip
| D     oops.iso
* ce36c98 Careless
| A     oops.iso
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

您想oops.iso从“无忧无虑”中删除就好像从未添加它一样,然后“删除DVD-rip”对您无用。因此,我们计划进行交互式基础调整的是保留“管理页面”,编辑“无忧无虑”并丢弃“删除DVD-rip”。

运行将$ git rebase -i 5af4522启动具有以下内容的编辑器。

pick ce36c98 Careless
pick cb14efd Remove DVD-rip
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

执行我们的计划,我们将其修改为

edit ce36c98 Careless
pick f772d66 Login page

# Rebase 5af4522..f772d66 onto 5af4522
# ...

也就是说,我们用“ Remove DVD-rip”删除该行,并将“ Careless”上的操作更改为edit而不是pick

保存退出编辑器后,在命令提示符处显示以下消息。

Stopped at ce36c98... Careless
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

消息告诉我们,我们处于我们要编辑的“ Careless”提交中,因此我们运行两个命令。

$ git rm --cached oops.iso
$ git commit --amend -C HEAD
$ git rebase --continue

第一个从索引中删除有问题的文件。第二个将“ Careless”修改或修改为更新的索引,并-C HEAD指示git重用旧的提交消息。最后,git rebase --continue进行其余的rebase操作。

这提供了以下历史记录:

$ git lola --name-status
* 93174be (HEAD, master) Login page
| A     login.html
* a570198 Careless
| A     other.html
* 5af4522 Admin page
| A     admin.html
* e738b63 Index
  A     index.html

这就是你想要的。


4
为什么我在使用git filter-branch时无法推送,为什么无法将一些引用推送到'git@bitbucket.org:product / myproject.git'为防止丢失历史记录,因此拒绝了非快进更新,请合并远程在再次按下之前进行更改。
阿贡·普拉塞季

11
在命令中添加-f(或--force)选项git push:“通常,该命令拒绝更新不是用于覆盖它的本地引用的祖先的远程引用。该标志禁用检查。这可能导致远程存储库丢失提交。小心使用。”
格雷格·培根

5
这是一个非常彻底的答案,它解释了如何使用git-filter-branch从历史记录中删除不需要的大文件,但值得注意的是,自从Greg写出答案以来,BFG Repo-Cleaner已发布,通常更快更容易使用-有关详细信息,请参见我的答案。
罗伯托·泰利

1
完成上述任一过程后,远程存储库(在GitHub上)不会删除大文件。只有当地人这样做。我强迫推和纳达。我想念什么?
阿扎塔尔

1
这也适用于dirs。... "git rm --cached -rf --ignore-unmatch path/to/dir"...
rynop

198

为什么不使用这个简单但功能强大的命令?

git filter-branch --tree-filter 'rm -f DVD-rip' HEAD

--tree-filter选项在每次签出项目后运行指定的命令,然后重新提交结果。在这种情况下,请从每个快照中删除一个名为DVD-rip的文件,无论该文件是否存在。

如果您知道哪个提交引入了巨大的文件(例如35dsa2),则可以将HEAD替换为35dsa2..HEAD,以避免重写过多的历史记录,从而避免在尚未推送的情况下分散提交。@ alpha_989提供的此评论似乎太重要了,不能在此处省略。

请参阅此链接


3
这是一个很好的解决方案!我创建了一个具有python脚本的gist,以列出文件和git cmd,它将删除您要清除的文件gist.github.com/ariv3ra/16fd94e46345e62cfcbf
punkdata

5
比bfg好得多。我无法使用bfg从git中清除文件,但此命令有所帮助
podarok '16

4
这很棒。请注意,如果大文件位于多个分支中,则必须在每个分支上执行此操作。
詹姆斯,

2
在Windows上fatal: bad revision 'rm',我使用"而不是修复了该问题'。总体指挥官:git filter-branch --force --index-filter "git rm --cached -r --ignore-unmatch oops.iso" --prune-empty --tag-name-filter cat -- --all
marcotama '16

2
如果您知道commit将文件放入的位置(例如35dsa2),则可以替换HEAD35dsa2..HEADtree-filterindex-filter这种方式慢得多,它不会尝试检出所有提交并重写它们。如果您使用HEAD,它将尝试这样做。
alpha_989 '18

86

(我见过的关于此问题的最佳答案是:https : //stackoverflow.com/a/42544963/714112,请复制到此处,因为该线程在Google搜索排名中排名很高,而其他线程则没有)

shell极快的外壳单线🚀

此shell脚本显示存储库中的所有blob对象,从最小到最大排序。

对于我的示例存储库,它的运行速度比此处找到的其他存储库快100倍
在我值得信赖的Athlon II X4系统上,它在短短一分钟内处理了Linux Kernel存储库及其5,622,155个对象。

基本脚本

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print substr($0,6)}' \
| sort --numeric-sort --key=2 \
| cut --complement --characters=13-40 \
| numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

当您运行上述代码时,将得到如下所示的易于阅读的输出

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

File快速删除文件🚀

假设您然后要删除文件,a并且b从中可以访问的每个提交中HEAD,都可以使用以下命令:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' HEAD

3
如果您的回购中有任何标签,您可能还希望添加标记--tag-name-filter cat以在新的相应提交被重写时对其进行重新标签,即,git filter-branch --index-filter 'git rm --cached --ignore-unmatch a b' --tag-name-filter cat HEAD(请参见此相关答案
naitsirhc

3
Mac指令和其他一些信息显示在原始链接的帖子中
nruth

3
git filter-branch --index-filter 'git rm --cached --ignore-unmatch <filename>' HEAD蝙蝠的工作命令权
eleijonmarck,

我最喜欢的答案。稍作调整即可在Mac OS上使用(使用gnu命令)git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | awk '/^blob/ {print substr($0,6)}' \ | sort --numeric-sort --key=2 \ | gnumfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest
Florian Oswald

带有rev-list的很酷的脚本,但是它对我来说不起作用,不知道该怎么做?
罗宾·马诺利

47

在尝试了SO中的几乎所有答案之后,我终于找到了这个宝石,该宝石可以快速删除并删除存储库中的大文件,并允许我再次同步:http : //www.zyxware.com/articles/4027/how-to-delete永久地从您的本地文件和远程git存储库中下载文件

CD到本地工作文件夹,然后运行以下命令:

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch FOLDERNAME" -- --all

将FOLDERNAME替换为您要从给定git存储库中删除的文件或文件夹。

完成此操作后,请运行以下命令来清理本地存储库:

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

现在将所有更改推送到远程存储库:

git push --all --force

这将清理远程存储库。


对我来说就像一个魅力。
Ramon Vasconcelos

3
这也为我工作。删除存储库中的特定文件夹(在我的情况下,该文件夹包含太大的文件或Github存储库),但如果存在则保留在本地文件系统上。
skizzo

为我工作!没有留下可能造成混淆的历史记录(如果有人现在要克隆),请确保您有计划更新任何断开的链接,依赖项等
ruoho ruotsi

38

这些命令在我的情况下有效:

git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

与上述版本几乎没有什么不同。

对于那些需要将其推送到github / bitbucket的用户(我仅使用bitbucket进行了测试):

# WARNING!!!
# this will rewrite completely your bitbucket refs
# will delete all branches that you didn't have in your local

git push --all --prune --force

# Once you pushed, all your teammates need to clone repository again
# git pull will not work

4
与上面有什么不同,为什么更好?
Andy Hayden

1
由于某些原因,在我的情况下,mkljun版本并未减少git空间,因此我已经使用删除了索引文件git rm --cached files。格雷格·培根(Greg Bacon)的命题比较完整,与我的命题完全一样,但是在您多次使用过滤分支的情况下,他错过了--force索引,并且他写了很多信息,我的版本就像简历它的。
Kostanos

1
这真的帮助,但我需要使用的-f不只是选择-rf在这里git rm --cached -rf --ignore-unmatch oops.iso,而不是git rm --cached -r --ignore-unmatch oops.iso按照@ lfender6445如下
drstevok

10

请注意,此命令可能具有很大的破坏性。如果更多的人在回购上工作,他们都将不得不拉新的树。如果您的目标不是减小大小,则不需要三个中间命令。由于filter分支会创建已删除文件的备份,因此可以在其中保留很长时间。

$ 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

11
除非您要自己造成极大的痛苦,否则请不要运行这些命令。它删除了很多我的原始源代码文件。我假设它将清除我在GIT中的提交历史记录中的一些大文件(按照原始问题),但是,我认为此命令旨在永久清除原始源代码树中的文件(有很大的不同!)。我的系统:Windows,VS2012,Git源代码管理提供程序。
Contango 2012年

2
我使用了以下命令:git filter-branch --force --index-filter 'git rm --cached -r --ignore-unmatch oops.iso' --prune-empty --tag-name-filter cat -- --all而不是您的代码中的第一个命令
Kostanos


8

如果您知道自己的提交是最近的,而不是遍历整个树,请执行以下操作: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD


7

我碰巧遇到了一个bitbucket帐户,在该帐户中我意外地存储了我站点的大量* .jpa备份。

git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch MY-BIG-DIRECTORY-OR-FILE' --tag-name-filter cat -- --all

使用MY-BIG-DIRECTORY有问题的文件夹进行重新整理,以完全重写您的历史记录(包括标签)。

来源:https : //web.archive.org/web/20170727144429/http : //naleid.com : 80/blog/2012/01/17/finding-and-purging-big-files-from-git-history/


1
这个回答对我有帮助,除了答案中的脚本有一个小问题,它不能在我所有的分支中搜索。但是链接中的命令完美地做到了。
阿里B

5

这会将其从您的历史记录中删除

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

这对我有用,谢谢!
Sonja Brits

这对我来说有效。我在您的master分支上运行它。
S. Domeng

4

我基本上做了这个答案的事情:https//stackoverflow.com/a/11032521/1286423

(有关历史记录,我将在此处复制粘贴)

$ 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

它没有用,因为我喜欢重命名和移动很多东西。因此,一些大文件位于已重命名的文件夹中,并且我认为gc无法删除对这些文件的引用,因为tree指向这些文件的对象中存在引用。我要真正杀死它的最终解决方案是:

# 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,即使过滤器分支也无法清除。


4

git filter-branch是一个功能强大的命令,您可以使用它从提交历史记录中删除一个大文件。该文件将保留一段时间,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

使用Git Extensions,它是一个UI工具。它有一个名为“查找大文件”的插件,可以在存储库中找到标记文件,并允许永久删除它们。

在使用此工具之前,请勿使用“ git filter-branch”,因为它无法找到被“ filter-branch”删除的文件(全部“ filter-branch”无法从存储库包文件中完全删除文件) 。


对于大型存储库,此方法太慢了。列出大型文件花了一个多小时。然后,当我去删除文件时,一个小时后,它只是处理要删除的第一个文件的三分之一。
kristianp

是的,它运行缓慢,但是工作正常吗?您知道得更快吗?
Nir

1
尚未使用过,但此页面上的另一个答案是BFG Repo-Cleaner。
kristianp

2

您可以使用以下branch filter命令执行此操作:

git filter-branch --tree-filter 'rm -rf path/to/your/file' HEAD


2

在这个线程中有很好的答案,但是与此同时,许多答案已经过时了。使用git-filter-branch不再推荐,因为它是很难使用和大库非常缓慢。

git-filter-repo 更快,更简单地使用。

git-filter-repo是Python脚本,可在github:https : //github.com/newren/git-filter-repo上找到

您只需要一个文件:Python3脚本git-filter-repo。将其复制到PATH变量中包含的路径。在Windows上,您可能必须更改脚本的第一行(请参阅INSTALL.md)。您需要在系统上安装Python3,但这并不重要。

首先你可以跑步

git filter-repo --analyze

这可以帮助您确定下一步要做什么。

您可以在任何地方删除DVD-rip文件:

 git filter-repo --invert-paths --path-match DVD-rip

Filter-repo确实非常快。filter-repo在4分钟内完成了一项任务,该任务在我的计算机上通过filter-branch花费了大约9个小时。您可以使用filter-repo做更多的事情。请参阅该文档。

警告:在您的存储库副本上执行此操作。filter-repo的许多操作无法撤消。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的回答和一些博客条目,我将其组合在一起。感谢他们!


您应该在家自制
Cameron E

0

除了git filter-branch(缓慢但纯正的git解决方案)和BFG(更简便且非常高效)之外,还有另一种过滤器可以提供良好的性能:

https://github.com/xoofx/git-rocket-filter

从其描述:

git-rocket-filter的用途类似于命令,git-filter-branch同时提供以下独特功能:

  • 快速重写提交和树(按x10到x100的顺序)。
  • 内置支持--keep(保留文件或目录)的白名单和--remove选项的黑名单。
  • 使用.gitignore之类的模式进行树过滤
  • 快速简便的C#脚本,用于提交过滤和树过滤
  • 支持按文件/目录模式进行树过滤的脚本
  • 自动修剪空/未更改的提交,包括合并提交
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.