如何通过非交互式地压缩所有提交(除了最近的提交)来减小Git仓库的体积?

10

我有一个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 都不是存储备份的很好的工具。有更好的工具。


3
Git仓库中有数百GB的数据?这听起来不太好… - nneonneo
@nneonneo 常规的交互式变基操作。 - sanmai
@sanmai:我的意思是,你会将rebase脚本编辑成什么样子? - nneonneo
拥有大量的提交并不一定会使您的Git存储库膨胀。Git在压缩基于文本的文件方面非常高效。您确定提交数量是导致存储库大小过大的实际问题吗?更可能的候选原因是您有太多二进制资产版本,这些资产与纯文本文件相比,Git无法很好地(或根本不)进行压缩。 - user456814
显示剩余14条评论
3个回答

3

原帖作者在评论中表示:

如果我们快照一个提交 10004,删除它之前的所有提交,并将提交 10004 设为根提交,那么我就没问题了。

一种方法是在假定当前工作被称为 branchname 的情况下执行此操作。我喜欢在进行大型 rebase 时使用临时标签,以便双重检查是否有更改,并标记可以 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之前,您将暂时拥有所有内容的两个副本,但这是不可避免的;即使您执行标准的压缩和变基操作,直到变基完成,您仍然会拥有所有内容的两个副本。


我有三个评论。首先,您可以使用一个简单的分支来保存上一个状态,而不是使用轻量级标签(我认为轻量级标签只是另一个引用,就像分支一样)。在变基后,您还可以直接使用<branch>@{1}引用<branch>的第一个先前位置。第二,另一种方法是使用硬重置,然后软重置到根,提交,然后再次将其他提交重新基于其之上,而不是使用孤立分支。 - user456814
最后,但最重要的是,如果目标是减小repo的大小,总提交数不太可能是膨胀的源头,正如我在上面解释的那样。 - user456814

3

一个XY问题

请注意,原帖作者存在一个XY问题,他试图解决如何压缩旧的提交记录(Y问题),而实际上他真正的问题是尝试减小Git存储库的大小(X问题),正如我在评论中提到的

拥有大量提交记录不一定会使您的Git存储库膨胀。Git非常有效地压缩基于文本的文件。您确定提交次数是导致存储库过大的实际问题吗?更有可能的候选者是您有太多二进制资产版本化,这些与纯文本文件相比Git无法很好地(或根本不)压缩。

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

压缩(数百或数千个)旧提交记录

正如原帖作者已经指出的那样,使用带有--root标志的交互式变基在存在许多提交记录(数百或数千个)时可能不切实际,特别是因为交互式变基在如此大量的提交记录上运行效率低下。

正如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
--prune=all选项用于git gc命令,将确保回收所有的悬挂提交,而不仅仅是默认设置为2周之前的那些。请注意保留HTML标签。

3
最快的计数实现方式几乎肯定是使用嫁接和filter-branch,虽然你可能能够通过手动编写commit-tree序列并在rev-list输出上工作来获得更快的执行速度。
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在ramdisk上,注意这里的clone不复制任何内容。除了阅读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

在您的代码库中可能有许多不同的情况需要处理,因此这些命令中的任何一个选项都可能很有用。以上命令已经过测试并能够完成其所述功能,但可能并非完全符合您的要求。


我从你的代码中删除了链接,因为它们似乎只是语法突出显示,并且不明显它们是链接。 - user456814

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接