如何从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
。 你可以在这里获得更多的信息
我把这一切都归功于堆栈溢出和一些博客条目的答案。 学分给他们!
您可以使用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
可达的提交中移除文件a
和b
,你可以使用这个命令:
git filter-branch --index-filter 'git rm --cached --ignore-unmatch ab' HEAD