git gc --aggressive vs git repack

Aji*_*yak 78 git version-control github

我正在寻找减少git存储库大小的方法.搜索引导我到git gc --aggressive大多数时间.我还读到这不是首选方法.

为什么?如果我跑步,我应该注意什么gc --aggressive

git repack -a -d --depth=250 --window=250推荐结束gc --aggressive.为什么?如何repack减少存储库的大小?此外,我不太清楚旗帜--depth--window.

我应该怎么选择gcrepack?什么时候应该使用gcrepack

Gre*_*con 68

如今没有区别:git gc --aggressive根据Linus 2007年提出的建议运作; 见下文.从版本2.11(2016年第4季度)开始,git默认深度为50.大小为250的窗口是好的,因为它扫描每个对象的较大部分,但是250的深度是坏的,因为它使每个链都指向非常深的旧对象,这会减慢所有未来的git操作,从而降低磁盘使用率.


历史背景

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

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

git repack -a -d -f --depth=250 --window=250
Run Code Online (Sandbox Code Playgroud)

但是这假设您已经从存储库历史记录中删除了不需要的gunk,并且您已经按照清单来缩小git filter-branch文档中找到的存储库.

git的过滤分支可以用来摆脱文件的一个子集,通常用的一些组合--index-filter--subdirectory-filter.人们期望生成的存储库小于原始存储库,但是你需要更多的步骤来实际使它变小,因为Git努力不会丢失你的对象,直到你告诉它.首先要确保:

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

  • 你真的过滤了所有的参考:--tag-name-filter cat -- --all在通话时使用git filter-branch.

然后有两种方法可以获得更小的存储库.更安全的方法是克隆,保持原始原封不动.

  • 克隆它git clone file:///path/to/repo.克隆将没有删除的对象.见git-clone.(请注意,使用普通路径克隆只是硬连接所有内容!)

如果你真的不想克隆它,无论出于何种原因,请检查以下几点(按此顺序).这是一种非常具有破坏性的方法,因此请进行备份或返回克隆它.你被警告了.

  • 删除git-filter-branch备份的原始引用:比如说

    git for-each-ref --format="%(refname)" refs/original/ |
      xargs -n 1 git update-ref -d
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用的所有reflog到期git reflog expire --expire=now --all.

  • 垃圾收集所有未引用的对象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>
Run Code Online (Sandbox Code Playgroud)

2007年12月6日星期四,丹尼尔柏林写道:

实际上,事实证明,git-gc --aggressive无论你是否从SVN回购转换,有时打包文件都是愚蠢的事情.

绝对.git --aggressive大多是愚蠢的.它真的只对"我知道我有一个非常糟糕的包装,并且我想抛弃我所做的所有糟糕包装决定"的情况有用.

为了解释这一点,值得解释一下(你可能已经意识到了这一点,但我还是要了解基础知识)git delta-chains如何工作,以及它们如何与大多数其他系统如此不同.

在其他SCM中,delta链通常是固定的.它可能是"前进"或"后退",并且在您使用存储库时它可能会有所改进,但通常它是对单个文件的一系列更改,表示为某种单个SCM实体.在CVS中,它显然是*,v文件,很多其他系统都做类似的事情.

Git也做delta链,但它更"松散"地做了它们.没有固定的实体.Delt是针对git认为是一个优秀的delta候选者的任何随机其他版本生成的(具有各种相当成功的启发式算法),并且绝对没有硬分组规则.

这通常是一件非常好的事情.它有各种概念上的好处(也就是说,git内部从来都不需要关心整个修订链 - 它根本没有根据增量来考虑),但它也很棒,因为摆脱不灵活的delta规则意味着例如,git没有任何问题将两个文件合并在一起 - 根本就没有任意*,v隐藏含义的"修订文件".

这也意味着增加选择是一个更开放的问题.如果你将delta链限制为一个文件,你真的没有很多关于如何处理增量的选择,但在git中,它确实是一个完全不同的问题.

这就是真正命名--aggressive的地方.虽然git通常会尝试重新使用delta信息(因为它是一个好主意,并且它不会浪费CPU时间重新找到我们之前发现的所有好的增量),有时你我想说"让我们从头开始,用空白的石板,忽略所有以前的增量信息,并尝试生成一组新的增量."

所以--aggressive不是真正的积极进取,而是浪费CPU时间重新做出我们之前做过的决定!

有时这是件好事.特别是一些导入工具可能会产生非常糟糕的增量.git fast-import例如,任何使用的东西都可能没有太大的三角洲布局,因此值得说"我想从一个干净的平板开始".

但几乎总是,在其他情况下,这实际上是一件非常糟糕的事情.它会浪费CPU时间,特别是如果你之前在deltaing中做得很好,最终的结果是不会重复使用你已经找到的那些好的增量,所以你实际上最终会得到很多更糟糕的最终结果!

我将向Junio发送补丁以删除git gc --aggressive 文档.它可能很有用,但它通常只有在你真正理解它正在做的事情时才有用,并且文档对你没有帮助.

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

另一方面,"长期参与历史的最初导入"绝对是一个值得花费大量时间寻找真正好的增量的点.然后,每个用户之后(只要他们不用git gc --aggressive来撤消它!)将获得该一次性事件的优势.因此,特别是对于历史悠久的大型项目,可能值得做一些额外的工作,告诉delta发现代码疯狂.

因此,相当于git gc --aggressive- 但做得恰当 - 是(过夜)类似的事情

git repack -a -d --depth=250 --window=250
Run Code Online (Sandbox Code Playgroud)

那个深度的东西只是关于三角链的深度(让旧历史更长一些 - 它值得空间开销),窗口的事情是我们希望每个delta候选者扫描多大的对象窗口.

在这里,您可能想要添加-f标志(这是"放弃所有旧的增量",因为您现在实际上正在尝试确保这个实际上找到了好的候选者.

然后它将需要永远和一天( "一夜之间"的事情).但最终的结果是,该存储库下游的所有人都将获得更好的包,而不必自己花费任何精力.

          Linus
Run Code Online (Sandbox Code Playgroud)

  • 您对深度的评论有点令人困惑。刚开始我会抱怨你错了,因为积极进取可以大大加快git存储库的速度。在进行了积极的垃圾收集之后,一个巨大的仓库花了五分钟的时间才变成git状态,减少到几秒钟。但是后来我意识到您并不是说侵略性的gc减慢了回购速度,而是很大的深度。 (2认同)

Von*_*onC 49

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

正如我在" Git垃圾收集似乎没有完全发挥作用 "中提到的那样,a git gc --aggressive本身就不够,甚至不够.

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

git gc
git repack -Ad      # kills in-pack garbage
git prune           # kills loose garbage
Run Code Online (Sandbox Code Playgroud)

注意:Git 2.11(2016年第4季度)会将默认gc激进深度设置为50

请参阅Jeff King()提交07e7dbf(2016年8月11日).(由Junio C Hamano合并- -提交0952ca8,2016年9月21日)gc aggressive
peff

gitster:默认积极深度为50

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

总结是:当前默认值250不会节省太多空间,并且会花费CPU.这不是一个很好的权衡.

" git gc --aggressive"标志--aggressive做三件事:

  1. 使用" git-gc"抛弃现有的增量并从头开始重新计算
  2. 使用"--window = 250"来增加增量效果
  3. 使用"--depth = 250"来制作更长的三角链

项目(1)和(2)是"积极"重新包装的良好匹配.
他们要求重新包装做更多的计算工作,以期获得更好的包装.您在重新包装期间支付成本,而其他操作仅看到好处.

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

(参见研究承诺)

您可以看到,随着我们减小深度,常规操作的CPU节省会得到改善.
但我们也可以看到,随着深度的增加,节省的空间并不是那么大.在10到50之间节省5-10%可能值得CPU权衡.节省1%从50到100,或另外0.5%从100到250可能不是.


说到CPU保存," -f"学会接受该git repack选项并将其传递给pack-objects.

请参阅Junio C Hamano()提交的40bcf31(2017年4月26日).(由Junio C Hamano合并- -2017年5月29日的提交31fb6f4中)--threads=<n>
gitster

重新包装:接受gitster并传递给--threads=<n>

我们已经对这样做的pack-objects--window=<n>; 当用户想要强制--depth=<n>进行可重复的测试而不受赛车多线程的影响时,这将有所帮助.

  • 我在"Git Garbage集合似乎没有完全发挥作用"链接中提到了Linus线程 (3认同)
  • **感谢**这次现代更新!这里的所有其他答案都是旧的。现在我们可以看到 `git gc --aggressive` 已被修复两次:首先,按照 Linus 在 2007 年建议的“更好的打包方法”进行操作。然后在 Git 2.11 中,以避免 Linus 建议的过多对象深度,但结果证明这是有害的(减慢了所有未来的 Git 操作,并且没有节省任何值得一提的空间)。 (2认同)

Sas*_*olf 13

问题git gc --aggressive是选项名称和文档具有误导性.

正如Linus本人在这封邮件中解释的那样,git gc --aggressive基本上是这样的:

虽然git通常会尝试重新使用delta信息(因为它是一个好主意,并且它不会浪费CPU时间重新找到我们之前发现的所有好的增量),有时你想说"让我们从头开始,用一个空白石板,并忽略所有以前的增量信息,并尝试生成一组新的增量".

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

大多数时候git在确定有用的增量方面表现相当不错,并且使用git gc --aggressive会留下增量,这可能会在浪费大量CPU时间的情况下更糟糕.


莱纳斯结束了他的邮件与结论,即git repack用大--depth--window在大多数时间是更好的选择; 特别是在你导入一个大项目并想确保git找到好的增量之后.

因此,相当于git gc --aggressive- 但做得恰当 - 是(过夜)类似的事情

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

那个深度的东西只是关于三角链的深度(让旧历史更长一些 - 它值得空间开销),窗口的事情是我们希望每个delta候选者扫描多大的对象窗口.

在这里,你可能想要添加-f标志(这是"丢弃所有旧的增量",因为你现在实际上是在努力确保这个实际上找到了好的候选者.


Von*_*onC 10

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

提交0044f77提交daecbf2提交7384504提交22d4e3b提交080a448提交54d56f5提交d257e0f提交b6a8d09(2019年4月7日),和fc559fb提交提交cf9cd77提交b11e856由(2019年3月22日)ÆvarArnfjörðBjarmason( avar .
(由Junio C gitsterHamano合并-- --ac70c53 提交中,2019 年 4 月 25 日)

gc 文档:淡化了 --aggressive

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

因此,让我们澄清它的真正作用,并让用户得出自己的结论。

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

这意味着git-gc 文档现在包括

挑衅的

--aggressive选项被提供时,git-repack将与被调用-f的标志,这反过来将传递--no-reuse-deltaGIT中包对象
这将丢弃任何现有的增量并重新计算它们,代价是在重新打包上花费更多的时间。

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

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

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

并且(提交 080a448):

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

07e7dbfgc:默认激进深度为 50,2016 年 8 月 11 日,Git v2.10.1)以来,我们有些混淆地使用与--aggressive默认情况下相同的深度。

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


Sag*_*ter 6

警告.git gc --agressive如果没有备份,请不要使用与远程同步的存储库运行.

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

对于我的8GB计算机,攻击性gc在1Gb存储库上运行内存不足10k次.当OOM杀手终止git进程时 - 它给我留下了几乎空的存储库,只有工作树和少数三角洲幸存下来.

当然,它不是存储库的唯一副本,所以我只是重新创建它并从远程拉出(fetch不能用于破坏的回购并且在'解决增量'步骤上遇到了很多次我试图这样做),但如果你的回购是没有遥控器的单开发商本地仓库 - 首先备份它.