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

有时候我把一个DVD-rip放到一个网站项目中,然后不小心把git commit -a -m ...和zap,这个回购臃肿了2.2演出。 下一次我做了一些编辑,删除了video文件,并提交了所有内容,但是压缩文件仍然存在于版本库中。

我知道我可以从这些提交开始分支,并将一个分支重新分配到另一个分支。 但是我应该怎么做才能将这两个提交合并在一起,以便这个大文件不会在历史中显示出来,并且在垃圾回收过程中被清除了?

使用BFG Repo-Cleaner是一个更简单,更快捷的替代git-filter-branch专门用于从Git历史中删除不需要的文件的工具。

仔细按照使用说明进行操作 ,核心部分就是这样:

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

任何超过100MB的文件(不在你最近的提交中)都将从你的Git仓库的历史logging中删除。 然后你可以使用git gc清理死亡数据:

 $ git gc --prune=now --aggressive 

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

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

如果您已经向其他开发者发布了历史,那么您想要做的事情是非常具有破坏性的。 修复历史logging后,请参阅git rebase文档中的“从上游Rebase恢复”以获取必要步骤。

你至less有两个选项: git filter-branch和一个交互式rebase,这两个选项都在下面解释。

使用git filter-branch

我有一个类似的问题,从Subversion导入庞大的二进制testing数据,并写了关于从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将删除由于filter操作而变为空的提交( 不更改树)。 在典型的情况下,这个选项产生一个更清洁的历史。
  • -d命名一个不存在的临时目录来build立过滤的历史logging。 如果您正在使用现代Linux发行版,那么在/dev/shm指定一个树会导致更快的执行 。
  • --index-filter是主要事件,并在历史的每一步都与索引运行。 你想删除oops.iso无论它在哪里,但它并不存在于所有的提交。 命令git rm --cached -f --ignore-unmatch oops.iso在存在时删除DVD-rip,否则不会失败。
  • --tag-name-filter描述了如何重写标签名称。 cat的filter是身份操作。 您的存储库(如上面的示例)可能没有任何标签,但是我包含此选项以获得完整的通用性。
  • --指定git filter-branch的选项结束
  • --以下--是所有裁判的简写。 像上面的示例一样,您的存储库可能只有一个参考(主),但是我包含此选项以获得完整的通用性。

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

 $ 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 

请注意,新的“粗心”提交只添加other.html ,并且“删除DVD-rip”提交不再在主分支上。 标有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复制对象而不是仅创build硬链接。

现在你的历史是:

 $ 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 

前两个提交(“索引”和“pipe理页面”)的SHA1对象名称保持不变,因为筛选器操作没有修改这些提交。 “粗心”丢失了oops.iso和“login页面”得到了一个新的父母,所以他们的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”对你来说是没用的。 因此,我们计划进入一个互动的重新组织是保持“pipe理页面”,编辑“粗心”,并放弃“删除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 # ... 

也就是说,我们用“删除DVD-rip”来删除这一行,并将“粗心”的操作改为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 

正如消息告诉我们的,我们正在进行我们要编辑的“粗心”提交,所以我们运行两个命令。

 $ 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 

这是你想要的。

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

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

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

看到这个链接 。

这些命令在我的情况下工作:

 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(我只用bitbuckettesting):

 # 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 

git filter-branch --tree-filter 'rm -f path/to/file' HEAD对我来说工作得相当好,虽然我遇到了同样的问题,正如我在这里所描述的那样,我按照这个build议解决了这个问题。

亲git书有整个章节重写历史 – 看看filter-branch /从每个提交部分删除文件 。

只要注意,这个命令可能是非常具有破坏性的。 如果有更多的人在回购工作,他们都必须拉新树。 如果您的目标不是减小尺寸,则三个中间命令不是必需的。 由于filter分支会创build已删除文件的备份,因此可以长时间保留该文件。

 $ 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 

如果你知道你的提交是最近的,而不是通过整个树进行以下操作: git filter-branch --tree-filter 'rm LARGE_FILE.zip' HEAD~10..HEAD

在尝试了几乎所有的答案后,我终于发现这个gem,迅速删除和删除我的存储库中的大文件,并允许我再次同步: http : //www.zyxware.com/articles/4027/how-to-delete -files永久性地从-您-本地和远程的Git储存库

CD到您的本地工作文件夹并运行以下命令:

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

将FOLDERNAMEreplace为您希望从给定的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 

这将清理远程存储库。

我用一个bitbucket账号跑过去,在那里我不小心存储了我的网站的ginormous * .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重新分配,以完全重写您的历史logging( 包括标签 )。

来源: http : //naleid.com/blog/2012/01/17/finding-and-purging-big-files-from-git-history

使用Git扩展 ,它是一个UI工具。 它有一个名为“查找大文件”的插件,用于查找存储库中的文件并允许将其轻松移除。

在使用这个工具之前不要使用'git filter-branch',因为它不能find被'filter-branch'删除的文件(Altough'filter-branch'不能从存储库包文件中完全删除文件) 。

当你遇到这个问题时, git rm不够的,因为git记得这个文件在我们的历史中曾经存在过,因此会保留对它的引用。

更糟糕的是,rebasing也不容易,因为任何对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/

我把这一切都归功于堆栈溢出和一些博客条目的答案。 学分给他们!

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

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

我基本上做了这个答案: 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对象的引用。 我真正杀死它的最终解决scheme是:

 # 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无法清理。

(我见过这个问题的最好的答案是: https : //stackoverflow.com/a/42544963/714112 ,在这里复制,因为这个线程似乎在谷歌search排名高,但另一个不)

🚀一个快速的shell单行🚀

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

对于我的示例回购,它比其他在这里find的速度快100倍
在我可靠的Athlon II X4系统上,它在一分钟之内处理了有着5,622,155个对象的Linux内核仓库

基本脚本

 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 

🚀快速文件删除🚀

假设你想从每个从HEAD可达的提交中移除文件ab ,你可以使用这个命令:

 git filter-branch --index-filter 'git rm --cached --ignore-unmatch ab' HEAD