git gc --aggressive vs git repack

Posted

技术标签:

【中文标题】git gc --aggressive vs git repack【英文标题】: 【发布时间】:2015-04-27 12:19:38 【问题描述】:

我正在寻找减小git 存储库大小的方法。大多数情况下,搜索会将我带到git gc --aggressive。我还读到这不是首选方法。

为什么?如果我正在运行gc --aggressive,我应该注意什么?

推荐git repack -a -d --depth=250 --window=250 优于gc --aggressive。为什么? repack 如何减小存储库的大小?另外,我不太清楚--depth--window 的标志。

我应该在gcrepack 之间选择什么?我什么时候应该使用gcrepack

【问题讨论】:

【参考方案1】:

现在没有区别:git gc --aggressive按照Linus在2007年提出的建议运行;见下文。从 2.11 版(2016 年第四季度)开始,git 默认深度为 50。大小为 250 的窗口很好,因为它扫描每个对象的较大部分,但是 250 的深度不好,因为它使每个链都引用非常深的旧对象,这会减慢 所有 未来的 git 操作,从而略微降低磁盘使用率。


历史背景

Linus 建议(请参阅下面的完整邮件列表帖子)使用 git gc --aggressive,用他的话来说,只有当你有“一个 非常 糟糕的包”或“非常糟糕的 deltas”,然而“几乎总是,在其他情况下,这实际上是一件非常糟糕的事情。”结果甚至可能使您的存储库处于比您开始时更糟糕的状态!

他建议在导入“漫长而复杂的历史”后正确执行此操作的命令是

git repack -a -d -f --depth=250 --window=250

但这假设您已经从存储库历史记录中获得了removed unwanted gunk,并且您已按照在git filter-branch documentation 中找到的用于缩小存储库的清单进行操作。

git-filter-branch 可用于删除文件子集,通常使用--index-filter--subdirectory-filter 的某种组合。人们期望生成的存储库比原始存储库小,但您需要更多步骤才能真正将其缩小,因为 Git 会努力不丢失您的对象,直到您告诉它这样做。首先确保:

如果 blob 在其生命周期内被移动,您确实删除了文件名的所有变体。 git log --name-only --follow --all -- filename 可以帮你找到重命名。

您确实过滤了所有引用:在调用 git filter-branch 时使用 --tag-name-filter cat -- --all

那么有两种方法可以获得更小的存储库。一种更安全的方法是克隆,这样可以保持您的原件完好无损。

git clone file:///path/to/repo 克隆它。克隆不会有被移除的对象。请参阅 git 克隆。 (请注意,使用普通路径进行克隆只会硬链接所有内容!)

如果您真的不想克隆它,无论出于何种原因,请检查以下几点(按此顺序)。这是一种非常具有破坏性的方法,因此请进行备份或重新克隆它。您已收到警告。

删除由 git-filter-branch 备份的原始 refs:say

git for-each-ref --format="%(refname)" refs/original/ |
  xargs -n 1 git update-ref -d

使用 git reflog expire --expire=now --all 使所有 reflog 过期。

垃圾收集使用git gc --prune=now 的所有未引用对象(或者如果您的git gc 不够新,无法支持--prune 的参数,请改用git repack -ad; git prune)。


Date: Wed, 5 Dec 2007 22:09:12 -0800 (PST)
From: Linus Torvalds <torvalds at linux-foundation dot org>
To: Daniel Berlin <dberlin at dberlin dot org>
cc: David Miller <davem at davemloft dot net>,
    ismail at pardus dot org dot tr,
    gcc at gcc dot gnu dot org,
    git at vger dot kernel dot org
Subject: Re: Git and GCC
In-Reply-To: <4aca3dc20712052111o730f6fb6h7a329ee811a70f28@mail.gmail.com>
Message-ID: <alpine.LFD.0.9999.0712052132450.13796@woody.linux-foundation.org>
References: <4aca3dc20712051947t5fbbb383ua1727c652eb25d7e@mail.gmail.com>
            <20071205.202047.58135920.davem@davemloft.net>
            <4aca3dc20712052032n521c344cla07a5df1f2c26cb8@mail.gmail.com>
            <20071205.204848.227521641.davem@davemloft.net>
            <4aca3dc20712052111o730f6fb6h7a329ee811a70f28@mail.gmail.com>

2007 年 12 月 6 日星期四,Daniel Berlin 写道:

其实,原来git-gc --aggressive做了这件蠢事 有时打包文件,无论您是否从 SVN repo 与否。

当然。 git --aggressive 大多是愚蠢的。它真的只对 “我知道我有一个真的坏包,我想扔掉 我做过的所有糟糕的包装决定。”

为了解释这一点,值得解释(你可能知道,但是 无论如何,让我了解一下基础知识)git delta-chains 是如何工作的,以及如何 它们与大多数其他系统非常不同。

在其他 SCM 中,delta 链通常是固定的。可能是“前锋” 或“向后”,当您使用存储库时,它可能会有所发展, 但通常它是对单个文件的一系列更改,表示为一些 一种单一的 SCM 实体。在 CVS 中,很明显是*,v 文件,还有很多 的其他系统做类似的事情。

Git 也做 delta-chains,但它做起来更“松散”。那里 不是固定的实体。 Deltas 是针对任何随机的其他版本生成的 git 认为是一个很好的 delta 候选者(有各种相当 成功的启发式),并且绝对没有硬性的分组规则。

这通常是一件非常好的事情。它适用于各种概念 原因(,git在内部从来没有真正需要关心整体 修订链——它根本不考虑增量),但是 这也很棒,因为摆脱不灵活的增量规则意味着 git 在合并两个文件时完全没有任何问题, 例如——根本没有任意的*,v“修订文件”有 一些隐藏的含义。

这也意味着增量的选择更加开放 题。如果您将增量链限制为仅一个文件,您真的不会 关于如何处理增量有很多选择,但在 git 中,它真的 可能是一个完全不同的问题。

这就是真正糟糕的--aggressive 的用武之地。虽然 git 通常会尝试重用 delta 信息(因为这是个好主意, 并且它不会浪费 CPU 时间重新找到我们找到的所有好的增量 较早),有时你想说“让我们从头开始,留个空白 slate,并忽略所有先前的 delta 信息,并尝试生成 一组新的增量。”

所以--aggressive 并不是真的要咄咄逼人,而是要浪费 CPU 时间重新做我们之前已经做过的决定!

有时这是一件好事。一些导入工具尤其可以 产生非常糟糕的三角洲。任何使用git fast-import的东西, 例如,可能没有很好的 delta 布局,所以它可能 值得说的是“我想从头开始。”

但几乎总是,在其他情况下,这实际上是一件非常糟糕的事情。 这会浪费 CPU 时间,尤其是如果你真的做了一个 早点完成增量工作,最终结果不会重用所有 你已经找到了那些 good 增量,所以你最终会得到一个 更糟糕的最终结果!

我会向 Junio 发送一个补丁来删除 git gc --aggressive 文档。它可能很有用,但通常只有当你 真正深入了解它在做什么,并且 文档并不能帮助您做到这一点。

一般来说,做增量git gc 是正确的方法,而且更好 比做git gc --aggressive。它将重新使用旧的增量,并且 当那些旧的 deltas 找不到时(进行增量 GC 的原因 首先!)它将创建新的。

另一方面,“长 和涉及的历史”是值得花很多钱的地方 是时候找到非常好的增量了。然后,以后的每个用户(如 只要他们不使用git gc --aggressive 撤消它!)将获得 一次性事件的优势。因此,特别是对于具有 历史悠久,可能值得做一些额外的工作,告诉三角洲 寻找疯狂的代码。

所以git gc --aggressive 的等价物——但完成正确——是 做(一夜之间)类似的事情

git repack -a -d --depth=250 --window=250

深度是指 delta 链的深度 (让它们在旧历史中更长——值得开销的空间),和 窗口问题是关于我们希望每个增量有多大的对象窗口 扫描的候选人。

在这里,您可能想要添加 -f 标志(即“全部删除 旧三角洲,”因为您现在实际上是在尝试确保这个 实际上找到了好的候选人。

然后这将需要永远和一天(,“一夜之间完成” 事物)。但最终的结果是,下游的每个人 存储库将获得更好的包,而无需花费任何精力 自己动手。

          Linus

【讨论】:

您对深度的评论有点令人困惑。起初我要抱怨你大错特错,激进可以大大加快 git 存储库的速度。在进行了一次积极的垃圾收集之后,一个巨大的 repo 需要五分钟才能完成 git status 减少到几秒钟。但后来我意识到你并不是说激进的 gc 减慢了回购的速度,而只是一个非常大的深度大小。【参考方案2】:

什么时候应该使用 gc & repack?

正如我在“Git Garbage collection doesn't seem to fully work”中提到的,git gc --aggressive 本身还不够,甚至不够。 而且,作为I explain below,通常不需要。

最有效的组合是添加git repack,但也添加git prune

git gc
git repack -Ad      # kills in-pack garbage
git prune           # kills loose garbage

注意:Git 2.11(2016 年第四季度)会将默认的 gc aggressive 深度设置为 50

参见Jeff King (peff) 的commit 07e7dbf(2016 年 8 月 11 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 0952ca8,2016 年 9 月 21 日)

gc:默认进取深度为 50

"git gc --aggressive" 用于将 delta-chain 长度限制为 250, 这对于获得额外的空间节省来说太深了,并且是 不利于运行时性能。 限制已降至 50。

总结是:当前默认的 250 没有 节省大量空间,并消耗CPU。这不是一个好的权衡。

git-gc 的“--aggressive”标志做了三件事:

    使用“-f”丢弃现有的增量并从头开始重新计算 使用“--window=250”更仔细地查找增量 使用“--depth=250”来制作更长的三角链

项目 (1) 和 (2) 非常适合“激进”重新包装。 他们要求重新包装做更多的计算工作,以期获得更好的包装。您在重新包装期间支付费用,而其他操作只看到收益。

第 (3) 项不太清楚。 允许更长的链意味着对增量的限制更少,这意味着可能会找到更好的链并节省一些空间。 但这也意味着访问增量的操作必须遵循更长的链,这会影响它们的性能。 所以这是一种折衷,而且这种折衷是否是好的还不清楚。

(见commit for study)

您可以看到,常规操作的 CPU 节省随着我们的改进而提高 减少深度。 但我们也可以看到,随着深度的增加,节省的空间并没有那么大。在 10 到 50 之间节省 5-10% 可能值得 CPU 权衡。从 50 到 100 节省 1%,或者从 100 到 250 再节省 0.5% 可能不是。


说到节省 CPU,“git repack”学会了接受 --threads=&lt;n&gt; 选项并将其传递给 pack-objects。

参见Junio C Hamano (gitster) 的commit 40bcf31(2017 年 4 月 26 日)。(由 Junio C Hamano -- gitster -- 合并于 commit 31fb6f4,2017 年 5 月 29 日)

重新打包:接受--threads=&lt;n&gt; 并将其传递给pack-objects

我们已经为--window=&lt;n&gt;--depth=&lt;n&gt; 这样做了;这会有所帮助 当用户想要强制--threads=1 进行可重复测试时 不受多线程竞争的影响。

【讨论】:

我在“Git 垃圾收集似乎无法完全正常工作”链接中提到了 Linus 线程 感谢的现代更新!这里的所有其他答案都是旧的。现在我们可以看到git gc --aggressive 已经被修复了两次:首先,按照Linus 在2007 年提出的“更好的打包方法”来做。然后在 Git 2.11 中避免 Linus 建议的过度对象深度,但事实证明这是有害的(减慢所有未来的 Git 操作并且没有节省任何值得一提的空间)。 git gc ,然后是 git repack -Ad 和 git prune 增加了我的存储库的大小......为什么? @devops 不确定:您使用的是什么版本的 Git?您可以为此提出一个新问题(提供更多详细信息,例如操作系统、存储库的一般大小......) man git-repack-d 说:`同时运行 git prune-packed 以删除多余的松散对象文件。` 或者git prune 也这样做? man git-prune 说的是In most cases, users should run git gc, which calls git prune.,那么git gc 后面有什么用呢?只使用git repack -Ad &amp;&amp; git gc不是更好或足够吗?【参考方案3】:

git gc --aggressive 的问题在于选项名称和文档具有误导性。

作为Linus himself explains in this mail,git gc --aggressive 的基本工作是这样的:

虽然 git 通常会尝试重用 delta 信息(因为这是一个好主意,并且不会浪费 CPU 时间来重新查找我们之前找到的所有好的 delta),但有时你想说“让我们重新开始, 用一张白纸, 并忽略所有以前的增量信息, 并尝试生成一组新的增量”。

通常不需要在 git 中重新计算增量,因为 git 非常灵活地确定这些增量。只有当你知道你有非常非常糟糕的增量时,它才有意义。正如 Linus 所解释的,主要使用 git fast-import 的工具属于这一类。

大多数时候 git 在确定有用的增量方面做得很好,使用 git gc --aggressive 会给您留下可能更糟的增量,同时浪费大量 CPU 时间。


Linus 在结束他的邮件时得出的结论是,git repack 与一个大的--depth--window 在大多数情况下是更好的选择;尤其是在您导入了一个大型项目并希望确保 git 找到好的增量之后。

所以相当于git gc --aggressive - 但完成正确 - 是(一夜之间)做类似的事情

git repack -a -d --depth=250 --window=250

深度是关于增量链可以有多深(使它们在旧历史中更长 - 它值得空间开销),而窗口是关于我们希望每个增量候选者扫描的对象窗口有多大.

在这里,您可能想要添加-f 标志(即“删除所有旧的增量”,因为您现在实际上是在尝试确保这个确实找到了好的候选人。

【讨论】:

【参考方案4】:

小心。如果您没有备份,请不要使用未与远程同步的存储库运行 git gc --agressive

此操作从头开始重新创建增量,如果正常中断可能会导致数据丢失。

对于我的 8GB 计算机,激进 gc 在 1Gb 存储库上用 10k 个小提交耗尽了内存。当 OOM 杀手终止 git 进程时 - 它给我留下了几乎空的存储库,只有工作树和少数 deltas 幸存下来。

当然,它不是存储库的唯一副本,所以我只是重新创建它并从远程拉取(fetch 在损坏的 repo 上不起作用,并且在我尝试这样做的几次'resolving deltas'步骤中陷入僵局),但是如果您的 repo 是完全没有遥控器的单一开发人员本地 repo - 首先备份它。

【讨论】:

【参考方案5】:

注意:请注意使用 git gc --aggressive,正如 Git 2.22(2019 年第二季度)所阐明的那样。

见commit 0044f77、commit daecbf2、commit 7384504、commit 22d4e3b、commit 080a448、commit 54d56f5、commit d257e0f、commit b6a8d09(2019 年 4 月 7 日)和commit fc559fb、@987@ commit b11e856(2019 年 3 月 22 日)Ævar Arnfjörð Bjarmason (avar)。(由 Junio C Hamano -- gitster -- 合并于 commit ac70c53,2019 年 4 月 25 日)

gc docs:淡化--aggressive 的用处

现有的“gc --aggressive”文档没有推荐到 定期运行它的用户。 我亲自与许多用户交谈过,他们将这些文档作为使用此选项的建议,并且通常(大部分)是浪费时间

所以让我们澄清一下它的真正作用,让用户自己得出结论。

让我们也澄清一下“效果 [...] 是持久的”,以解释 Jeff King's explanation 的简短版本。

这意味着git-gc documentation now includes:

激进

当提供--aggressive 选项时,将使用-f 标志调用git-repack,然后将--no-reuse-delta 传递给git-pack-objects。 这将丢弃任何现有的增量并重新计算它们,代价是 在重新包装上花费更多时间。

这种影响大多是持久的,例如当包和松散的对象合并到另一个包中时,该包中的现有增量可能会被重新使用,但也有各种情况,我们可能会从较新的包中选择一个次优的增量。

此外,提供--aggressive 将调整传递给git-repack--depth--window 选项。 请参阅下面的 gc.aggressiveDepthgc.aggressiveWindow 设置。 通过使用更大的窗口大小,我们更有可能找到更优化的增量。

如果不对给定存储库运行定制的性能基准测试,可能不值得在给定存储库上使用此选项。 这需要更多时间,由此产生的空间/增量优化可能值得也可能不值得。对于大多数用户及其存储库而言,完全不使用它是正确的权衡。

还有(commit 080a448):

gc 文档:注意 --aggressive 如何影响 --window--depth

由于07e7dbf(gc:默认进取深度为 50,2016 年 8 月 11 日,Git v2.10.1)我们在--aggressive 下使用与默认相同的深度有点令人困惑。

正如在那个有意义的提交中指出的那样,将更多深度设置为“积极”的默认值是错误的,从而以牺牲运行时性能为代价来节省磁盘空间,这通常与喜欢“积极的 gc" 想要。

【讨论】:

以上是关于git gc --aggressive vs git repack的主要内容,如果未能解决你的问题,请参考以下文章

完美解决--用VS中的Git做代码管理器,与他人共享代码

JVM 垃圾回收 Minor gc vs Major gc vs Full gc

在VS下使用 GitFlow管理项目开发

Git的维护(git gc和git fsck)

Git的维护 git gc和git fsck

git gc和fsck的用法