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

Posted

技术标签:

【中文标题】如何通过非交互方式压缩除最近提交之外的所有提交来减少臃肿的 Git 存储库的大小?【英文标题】:How do I reduce the size of a bloated Git repo by non-interactively squashing all commits except for the most recent ones? 【发布时间】:2014-07-31 22:58:23 【问题描述】:

我的 Git 存储库有数百 GB 的数据,例如数据库备份,因此我正在尝试删除旧的、过时的备份,因为它们会使所有内容变得更大、更慢。所以我自然需要快速的东西;越快越好。

我如何压缩(或直接删除)除了最近的提交之外的所有提交,并且这样做不必手动压缩每个interactive 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

有一个关于如何squash all commits 的答案,但我想保留一些最近的提交。我也不想squash the most recent commits。 (特别是我需要保持前两个提交从顶部开始计数。)

(编辑,几年后。这个问题的正确答案是使用正确的工具来完成这项工作。Git 不是存储备份的好工具,无论它多么方便。There are better tools. )

【问题讨论】:

一个 git repo 中有数百 GB?这听起来是个坏主意…… 你能举一个你会用手做的例子吗? “squash”和“remove”是完全不同的操作; squashing 会保留更改,而删除会丢弃更改(即将您最近的更改重新定位到某个较旧的点)。 @MattMcNabb 对,所以改用kill;我的意思是我不在乎他们会发生什么,我只需要数据;例如如果我们对提交 10004 进行快照,删除之前的所有提交,并将提交 10004 设为根提交,我会没事的 @nneonneo 常用的交互式 rebase 东西 【参考方案1】:

原海报comments:

如果我们对提交 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

将从您的存储库中清除它。

请注意,您将暂时拥有所有内容的两份副本,直到您拥有gc'd,但这是不可避免的;即使您执行标准的 squash 和 rebase,在 rebase 完成之前,您仍然拥有所有内容的两个副本。

【讨论】:

我有一个三个cmets。 1st,你也可以使用一个简单的分支来保存你之前的状态,而不是一个轻量级的标签(我认为轻量级的标签只是另一个参考,就像一个分支)。您也可以在变基后直接使用<branch>@1 来引用<branch> 的第一个先前位置。第二,另一种方法不是使用孤立分支,而是使用硬重置,然后对根目录进行软重置,提交,然后再次将其他提交重新设置在顶部。 最后,但最重要的是,如果目标是减少 repo 的大小,那么提交的总数不太可能成为膨胀的根源,as I explained above。【参考方案2】:

几乎可以肯定,最快的计数实现时间将是使用移植和filter-branch,尽管您可以通过手动处理commit-tree 序列在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),它会非常快并且在你确定之前不会触及你的主仓库你已经得到了你想要的结果。

所以在 Windows 上,假设 T:\wip 在 ramdisk 上,请注意这里的克隆复制 nothing。除了阅读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

对于您可能想要做的事情以及您的存储库中可能包含的内容有足够多的可能变化,因此这些命令中的几乎所有选项都可能有用。上面的内容已经过测试,会按照它说的做,但这可能不是你想要的。

【讨论】:

我从您的代码中取出了链接,因为看起来它们只是突出显示的语法,而且它们是链接并不明显。【参考方案3】:

一个 XY 问题

请注意,原始发帖人有一个XY problem,他试图找出如何压缩他的旧提交(Y 问题),而他真正的问题实际上是试图减小他的 Git 存储库的大小(X问题),as I've mentioned in the comments:

拥有大量提交不一定会使 Git 存储库的大小膨胀。 Git 在压缩基于文本的文件方面非常有效。您确定提交的数量是导致您的大型 repo 大小的实际问题吗?更有可能的原因是您有太多的二进制资产版本,与纯文本文件相比,Git 的压缩效果不佳(或根本不压缩)。

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

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

正如原始海报已经指出的那样,当有许多提交(编号为数百或数千)时,使用带有 --root 标志的交互式 rebase 可能是不切实际的,特别是因为交互式 rebase 不会在这样的情况下有效运行很多。

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

# 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 的默认设置.

【讨论】:

以上是关于如何通过非交互方式压缩除最近提交之外的所有提交来减少臃肿的 Git 存储库的大小?的主要内容,如果未能解决你的问题,请参考以下文章

如何压缩除 n 最近的所有提交?

有没有办法以非交互方式压缩大量提交?

非交互式方式来压缩一系列git提交,而不是从HEAD开始

如何使用 SVN 提交除一个文件之外的所有文件

如何在下拉列表中获取除隐藏和提交按钮之外的所有表单字段

如何以非交互方式在 Git 中重新排序提交