如何通过非交互方式压缩除最新提交外的所有提交来减少臃肿的Git仓库的大小?



我的Git仓库有数百gb的数据,比如数据库备份,所以我试图删除旧的、过时的备份,因为它们会使所有东西变得更大、更慢。所以我自然需要速度快的东西;越快越好

我如何压缩(或只是简单地删除)除了最近的所有提交,并且这样做而不必在交互式rebase中手动压缩每个 ?具体来说,我不想使用

git rebase -i --root
例如,我有这些提交:
A .. B .. C ... ... H .. I .. J .. K .. L

我想要的是这个(将AH之间的所有内容压缩成A):

A .. H .. I .. J .. K .. L

或者这样也可以:

H .. I .. J .. K .. L

有一个关于如何压缩所有提交的答案,但我想保留一些最近的提交。我也不想压制最近的提交。(特别是我需要保持前两次提交从顶部开始计数。)

(编辑,几年后。这个问题的正确答案是为工作使用正确的工具。Git不是一个非常好的存储备份的工具,不管它有多方便。有更好的工具

原海报评论:

如果我们拍一张提交10004的快照,删除之前的所有提交,并将提交10004作为根提交,我就没事了

一种方法是这样做的,假设您当前的工作称为branchname。我喜欢使用一个临时标签,每当我做一个大的重新检查,以双重检查有没有变化,并标记一个点,我可以reset回到如果出现问题(不确定这是否是标准程序,但它适用于我):

git tag temp
git checkout 10004
git checkout --orphan new_root
git commit -m "set new root 10004"
git rebase --onto new_root 10004 branchname
git diff temp   # verification that it worked with no changes
git tag -d temp
git branch -D new_root

要摆脱旧的分支,你需要删除所有的标签和分支标签;

git prune
git gc

将从您的repo中清除它。

请注意,你将暂时有两个副本的一切,直到你有gc 'd,但这是不可避免的;即使你做了一个标准的压缩和重置,你仍然有两个副本,直到重置完成。

最快的计数实现时间几乎肯定是使用嫁接和过滤器分支,尽管您可能能够通过处理提交树序列在rev-list输出中获得更快的执行。

Rebase是用来对不同的内容应用更改的。您在这里所做的是保留内容并故意丢失产生它们的更改历史,因此几乎所有rebase最繁琐和最缓慢的工作都被浪费了。

这里的有效载荷是,从你的图片工作,

echo `git rev-parse H; git rev-parse A` > .git/info/grafts  
git filter-branch -- --all

git rev-parsegit filter-branch的文档。

Filter-branch非常小心地在任何点发生故障后都可以恢复,这当然是最安全的....但只有当简单地重做才会真正有帮助,如果事情不顺利,它不会更快更容易恢复。故障很少,重新启动通常很便宜,要做的是做一个不"安全"但非常快速的操作,几乎肯定能工作。为此,这里最好的选择是在tmpfs上做(我知道在Windows上最接近的等同是像ImDisk这样的ramdisk),它将会非常快,并且在你确定你已经得到你想要的结果之前不会触及你的主repo。

因此,在Windows上,假设T:wip在一个ram磁盘上,并注意这里的克隆不复制任何。除了阅读git clone--shared选项的文档外,请检查克隆的内部以查看实际效果,它非常简单。
# switch to a lightweight wip clone on a tmpfs
git clone --shared --no-checkout . /t/wip/filterwork
cd !$
# graft out the unwanted commits
echo `git rev-parse $L; git rev-parse $A` >.git/info/grafts
git filter-branch -- --all
# check that the repo history looks right
git log --graph --decorate --oneline --all
# all done with the splicing, filter-branch has integrated it
rm .git/info/grafts
# push the rewritten histories back
git push origin --all --force

有足够多的可能的变化,你可能想要做什么,什么可能在你的repo中,几乎任何这些命令的选项可能是有用的。上面的代码是经过测试的,将按照它所说的去做,但这可能不是您想要的。

一个XY问题

请注意,原来的发布者有一个XY问题,他试图找出如何压缩他的旧提交(Y问题),当他真正的问题实际上是试图减少他的Git存储库的大小(X问题),正如我在评论中提到的:

有很多的提交并不一定会膨胀你的Git仓库的大小。Git在压缩基于文本的文件方面非常有效。您确定提交的数量是导致庞大的回购规模的实际问题吗?更有可能的情况是,您有太多的二进制资产版本化了,与纯文本文件相比,Git不能很好地(或根本不能)压缩它们。

尽管如此,为了完整起见,我还将添加Matt McNabb对Y问题的答案的替代解决方案。

压缩(数百或数千)旧提交

正如最初的海报已经指出的那样,当有许多提交(数百或数千)时,使用带有--root标志的交互式rebase可能是不切实际的,特别是因为交互式rebase在如此大量的提交上无法有效运行。

正如Matt McNabb在他的回答中指出的那样,一个解决方案是使用一个孤儿分支作为一个新的(被压扁的)根,然后在它的基础上重新建立。另一个解决方案是使用分支的几个不同的重置来达到相同的效果:

# Save the current state of the branch in a couple of other branches
git branch beforeReset
git branch verification
# Also mark where we want to start squashing commits
git branch oldBase <most_recent_commit_to_squash>
# Temporarily remove the most recent commits from the current branch,
# because we don't want to squash those:
git reset --hard oldBase
# Using a soft reset to the root commit will keep all of the changes
# staged in the index, so you just need to amend those changes to the
# root commit:
git reset --soft <root_commit>
git commit --amend
# Rebase onto the new amended root,
# starting from oldBase and going up to beforeReset
git rebase --onto master oldBase beforeReset
# Switch back to master and (fast-forward) merge it with beforeReset
git checkout master
git merge beforeReset
# Verify that master still contains the same state as before all of the resets
git diff verification
# Cleanup
git branch -D beforeReset oldBase verification
# As part of cleanup, since the original poster mentioned that
# he has a lot of commits that he wants to remove to reduce
# the size of his repo, garbage collect the old, dangling commits too
git gc --prune=all

git gc--prune=all选项将确保所有悬空提交都被垃圾收集,而不仅仅是那些超过2周的提交,这是git gc的默认设置。

最新更新