git gc --aggressive vs git repack

问题描述 投票:72回答:5

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

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

git repack -a -d --depth=250 --window=250被推荐超过gc --aggressive。为什么? repack如何减小存储库的大小?另外,我不太清楚旗帜--depth--window

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

git github version-control
5个回答
67
投票

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


Historical Background

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

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

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可以帮助您找到重命名。
  • 你真的过滤了所有的参考:在调用--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
  • 使用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: <[email protected]>
Message-ID: <[email protected]>
References: <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>

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

事实上,事实证明,无论你是否从SVN回购转换,git-gc --aggressive都会做这个愚蠢的事情来收集文件。

绝对。 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

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

在这里,您可能想要添加-f标志(这是“放弃所有旧的三角洲”,因为您现在实际上正在努力确保这个实际上找到了好的候选人。

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

          Linus

47
投票

我什么时候应该使用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年第4季度)将默认的gc aggressive深度设置为50

请参阅commit 07e7dbfJeff King (peff)(2016年8月11日)。 (由Junio C Hamano -- gitster --commit 0952ca8合并,2016年9月21日)

gc:默认积极深度为50

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

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

--aggressive的“git-gc”旗帜做了三件事:

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

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

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

(见commit for study

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


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

请参阅commit 40bcf31撰写的Junio C Hamano (gitster)(2017年4月26日)。 (Junio C Hamano -- gitster --于2017年5月29日在commit 31fb6f4合并)

重新包装:接受--threads=<n>并将其传递给pack-objects

我们已经为--window=<n>--depth=<n>这样做了;这将有助于当用户想要强制--threads=1进行可重复的测试而不受赛车多线程的影响。


13
投票

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

作为qazxsw poi,qazxsw poi基本上做的是:

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

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

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


Linus结束了他的邮件,得出的结论是git fast-import带有大git gc --aggressivegit repack在大多数时候是更好的选择;特别是在你导入一个大项目并想确保git找到好的增量之后。

所以相当于--depth - 但做得恰当 - 是做(过夜)之类的事情

--window

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

在这里,你可能想要添加git gc --aggressive标志(这是“放弃所有旧的增量”,因为你现在实际上是在努力确保这个实际上找到了好的候选者。


6
投票

警告。如果没有备份,请不要使用与远程同步的存储库运行git repack -a -d --depth=250 --window=250

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

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

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


1
投票

注意:谨防使用-f,因为Git 2.22(2019年第二季度)澄清。

参见git gc --agressivegit gc --aggressivecommit 0044f77commit daecbf2commit 7384504commit 22d4e3bcommit 080a448commit 54d56f5commit d257e0f(2019年4月7日)和commit b6a8d09commit fc559fbcommit cf9cd77(2019年3月22日)。 (由commit b11e856合并于Ævar Arnfjörð Bjarmason (avar),2019年4月25日)

Junio C Hamano -- gitster -- docs:淡化commit ac70c53的用处

现有的“gc”文档只是向用户推荐他们定期运行它。 我个人已经与许多使用过这些文档作为建议使用此选项的用户进行了交谈,并且通常(通常)浪费时间。

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

让我们澄清“影响[...]持久”来解释--aggressive的简短版本。

这意味着gc --aggressive

激进

当提供Jeff King's explanation选项时,将使用git-gc documentation now includes标志调用--aggressive,而git-repack标志将-f传递给--no-reuse-delta。 这将丢弃任何现有的增量并重新计算它们,代价是在重新包装上花费更多的时间。

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

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

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

和(gc.aggressiveWindow):

commit 080a448 docs:注意gc如何影响--aggressive--window

--depth07e7dbf:默认积极深度50,2016-08-11,Git v2.10.1)以来,我们在gc下使用与默认情况下相同的深度。

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

© www.soinside.com 2019 - 2024. All rights reserved.