git - git在执行操作时会做什么:git gc-git prune

标签 git garbage-collection git-gc

启动时背景发生了什么,


git gc
git prune


git gc的输出:

Counting objects: 945490, done. 
Delta compression using up to 4 threads.   
Compressing objects: 100% (334718/334718), done. 
Writing objects: 100%   (945490/945490), done. 
Total 945490 (delta 483105), reused 944529 (delta 482309) 
Checking connectivity: 948048, done.


git prune的输出:

Checking connectivity: 945490, done.


这两个选项有什么区别?

谢谢

最佳答案

TL; DR

git prune仅删除松散,无法访问的陈旧对象(对象必须具有所有三个属性才能被修剪)。无法访问的打包对象保留在其打包文件中。可触及的松散物体仍可触及并松动。无法访问但尚未过时的对象也保持不变。陈旧的定义有些棘手(请参阅下面的详细信息)。

git gc的作用更多:它打包引用,打包有用的对象,使reflog条目到期,修剪松散的对象,修剪删除的工作树以及修剪/ gc的旧git rerere数据。



我不确定上面的“在背景中”是什么意思(背景在shell中具有技术含义,此处的所有活动都发生在shell的前景中,但我怀疑您不是在说这些术语)。

git gc的工作是组织整个系列的收集活动,包括但不限于git prune。下面的列表是由前景gc运行而没有--auto的命令集(忽略它们的参数,在某种程度上取决于git gc参数):


git pack-refs:紧凑的引用(将.git/refs/heads/....git/refs/tags/...条目转换为.git/packed-refs中的条目,消除了单个文件)
git reflog expire:使旧的reflog条目过期
git repack:将松散的对象打包为打包对象格式
git prune:清除不需要的松散物品
git worktree prune:删除用户已删除的已添加工作树的工作树数据
git rerere gc:删除旧的重记录


git gc可以单独进行一些单独的文件活动,但是以上是主要步骤。请注意,git prune发生在(1)reflog过期和(2)运行git repack之后:这是因为删除的reflog条目过期可能导致对象成为未引用对象,因此不会被打包然后被修剪,从而它完全消失了。

在重新包装和修剪之前需要了解的东西

在进行更详细的介绍之前,最好在Git中定义一个对象是什么,以及该对象松散或打包意味着什么。我们还需要了解对象可访问的含义。

每个对象都有一个哈希ID(例如,您在git log中看到的那些丑陋的ID中的一个)即该对象的名称,用于检索。 Git将所有对象存储在键-值数据库中,其中名称是键,而对象本身是值。因此,Git的对象就是Git存储文件和提交的方式,实际上,有四种对象类型:提交对象保存实际的提交。树对象包含成对的集合1,一个易于理解的名称,例如READMEsubdir以及另一个对象的哈希ID。如果树中的名称是文件名,则另一个对象是blob对象;如果树的名称是子目录的名称,则另一个对象是树对象。 Blob对象保存实际的文件内容(但请注意,文件名在链接到Blob的树中!)。最后一个对象类型是带注释的标签,用于带注释的标签,这里并不是特别有趣。

一旦制成,就无法更改任何对象。这是因为对象名称(即哈希ID)是通过查看对象内容的每一位来计算的。将任何一位从零更改为一位,反之亦然,哈希ID随之改变:您现在有了一个具有不同名称的不同对象。这就是Git检查没有文件被弄乱的方式:如果文件内容被更改,则对象的哈希ID将会更改。对象ID存储在树条目中,如果更改了树对象,则树的ID也将更改。树的ID存储在提交中,并且如果树ID被更改,则提交的哈希也将改变。因此,如果您知道提交的哈希为a234b67...,并且提交的内容仍为a234b67...哈希,则提交中的内容没有任何变化,并且树ID仍然有效。如果树仍以其自身名称哈希,则其内容仍然有效,因此Blob ID是正确的;因此只要Blob内容散列为自己的名称,该Blob也是正确的。

对象可以是松散的,这意味着它们存储为文件。文件的名称只是哈希ID。2松散对象的内容是zlib定义的。或者,可以打包对象,这意味着许多对象存储在单个打包文件中。在这种情况下,内容不仅会缩小,而且首先是delta-compressed。 Git挑选一个基础对象(通常是某些Blob(文件)的最新版本),然后找到可以用一系列命令表示的其他对象:获取基础文件,在此偏移处删除一些文本,在另一偏移处添加其他文本偏移量等等。打包文件的实际格式略微不足为documented here。请注意,与大多数版本控制系统不同,增量压缩发生在存储对象抽象以下的级别:Git存储整个快照,然后在基础对象上进行增量压缩。 Git仍然通过其哈希ID名称访问对象。仅仅是读取对象涉及到读取打包文件,查找对象及其基础的增量基数以及即时重建完整的对象。

关于打包文件有一条通用规则,该规则规定打包文件中的任何增量压缩对象都必须在同一打包文件中包含其所有基数。这意味着打包文件是自包含的:无需打开多个其他打包文件即可从具有该对象的打包中取出一个对象。 (可以故意违反此特定规则,从而产生Git所谓的精简包,但是这些规则仅用于通过网络连接将对象发送到已经具有基础对象的另一个Git。另一个Git必须“修复”或将胖包“胖”以制作普通的打包文件,然后再将其丢给其余的Git。)

对象的可及性有些棘手。让我们首先看一下提交的可达性。

请注意,当我们有一个提交对象时,该提交对象本身包含多个哈希ID。它具有一个树的哈希ID,该ID包含与该提交一起进行的快照。它还具有一个或多个父提交的哈希ID,除非该特定提交是根提交。根提交的定义是没有父母的提交,因此有点循环:一个提交有父母,除非它没有父母。但是,它已经足够清楚了:给定一些提交,我们可以将该提交绘制为图形中的一个节点,箭头从节点中出来,每个父对象一个:

<--o
   |
   v


这些父箭头指向提交的一个或多个父对象。给定一系列单亲提交,我们得到一个简单的线性链:

... <--o  <--o  <--o ...


这些提交之一必须是链的起点:这是根提交。其中之一必须是结局,这就是技巧提交。所有内部箭头都向后(向左)指向,因此我们可以画出没有箭头的箭头,因为知道根在左边,而尖端在右边:

o--o--o--o--o


现在,我们可以添加一个分支名称,例如master。名称只是指向提示提交:

o--o--o--o--o   <--master


嵌入在提交中的箭头不会改变,因为任何对象中的箭头都不会改变。但是,分支名称master中的箭头实际上只是某些提交的哈希ID,并且可以更改。让我们用字母来表示提交哈希:

A--B--C--D--E   <-- master


名称master现在仅存储提交E的提交哈希。如果我们向master添加新的提交,则可以通过写出一个父提交为E且其树为快照的提交来实现,从而为我们提供一个全新的哈希,可以将其称为F。提交F点回到E。我们让Git将F的哈希ID写入master,现在我们有:

A--B--C--D--E--F   <-- master


我们添加了一个提交,并更改了一个名称master。可以通过名称master开始访问所有先前的提交。我们读出F的哈希ID并读取提交F。哈希ID为E,因此我们已达到提交E。我们读取E以获得D的哈希ID,从而达到D。重复直到读A,发现它没有父级,然后完成。

如果有分支,那仅意味着我们有另一个名称找到的提交,其父代也是名称master找到的提交之一:

A--B--C--D--E--F   <-- master
             \
              G--H   <-- develop


名称develop定位提交HH找到G; G指的是E。因此所有这些提交都是可以实现的。

如果提交本身是可访问的,则与多个父级(即合并提交)的提交使所有其父级都可访问。因此,一旦进行了合并提交,就可以(但不必删除)删除标识已合并的提交的分支名称:现在,可以从进行合并操作时所在的分支的顶端访问该分支。那是:

...--o--o---o   <-- name
      \    /
       o--o   <-- delete-able


通过合并,可以从name到达最下面一行的提交,就像始终可以从name到达最上面一行的提交一样。删除名称delete-able使其仍然可以访问。如果不存在合并提交,则在这种情况下:

...--o--o   <-- name2
      \
       o--o   <-- not-delete-able


然后删除not-delete-able有效地放弃了底部行中的两个提交:它们变得不可访问,因此可以进行垃圾收集。

此相同的可达性属性适用于树和Blob对象。例如,提交G中有一个tree,并且该tree中有<名称,ID>对:

A--B--C--D--E--F   <-- master
             \
              G--H   <-- develop
              |
         tree=d097...
            /   \
 README=9fa3... Makefile=0b41...


因此,从提交G,树对象d097...是可访问的;从该树中,可以到达blob对象9fa3...,而blob对象0b41...也可以到达。提交H可能具有相同的README对象,并且名称相同(尽管树是不同的):很好,这使得9fa3可以双重访问,这对Git来说并不有趣:Git只关心它是完全可以到达的。

外部引用(分支和标签名称)以及在Git存储库中找到的其他引用(包括Git索引中的条目以及通过链接添加的工作树的任何引用),提供了对象图中的入口点。从这些入口点,任何对象都是可到达的-具有一个或多个可能导致该对象的名称-或不可访问,这意味着没有名称可用于找到对象本身。我从此描述中省略了带注释的标签,但是通常通过标签名称找到它们,并且带注释的标签对象具有一个找到的对象引用(任意对象类型),如果标签对象本身可访问,则使该对象可访问。 。

因为引用仅引用一个对象,但是有时我们用分支名称做某事,之后我们想撤消该分支,因此Git会保留该引用所具有的每个值以及何时记录的日志。这些参考日志或参考日志使我们知道昨天的master内容或上周的develop内容。最终,这些reflog条目是旧的并且过时,不太可能再有用了,git reflog expire将丢弃它们。

重新包装和修剪

现在,git repack的大致含义应该是清楚的:它将许多松散对象的集合转换为包含所有这些对象的打包文件。但是,它可以做更多的事情:它可以包含先前包中的所有对象。前一个包装变得多余,以后可以移除。它还可以从包装中遗漏任何无法触及的物体,从而将它们变成松散的物体。当git gc运行git repack时,它会使用依赖于git gc选项的选项,因此此处的确切语义有所不同,但是前景git gc的默认设置是使用git repack -d -l,其具有git repack删除冗余包并运行git prune-packedprune-packed程序删除也出现在包文件中的松散对象文件,因此这将删除进入包中的松散对象。 repack程序将-l选项传递给git pack-objects(这是构建打包文件的实际工作),这意味着将忽略从其他存储库借来的对象。 (对于大多数正常的Git使用情况,最后一个选项并不重要。)

在任何情况下,它都是git repack或技术上称为git pack-objects的打印计数,压缩和写入消息。完成后,您将拥有一个新的打包文件,而旧的打包文件已消失。新的包文件包含所有可到达的对象,包括旧的可到达打包对象和旧的可到达松散对象。如果从一个旧的(现在已拆除并删除)的打包文件中弹出了松散的对象,则它们会与其他松散的(且无法访问的)打包文件合并,从而使您的存储库混乱。如果它们在拆解期间被破坏,则仅保留现有的松散和无法到达的物体。

现在是时候使用git prune了:它找到松散,无法到达的对象并将其删除。但是,它有一个安全开关--expire 2.weeks.ago:默认情况下,由git gc运行,如果这些对象的存在时间不超过两周,它不会删除这些对象。这意味着任何正在创建新对象的Git程序(尚未连接它们)都具有宽限期。在默认情况下,git prune删除新对象之前十四天,它们可能会变得松散且无法访问。因此,一个忙于创建对象的Git程序需要十四天的时间才能完成将这些对象连接到图形中的工作。如果它确定这些对象不值得连接,则可以将其保留下来。从那时起14天后,将来的git prune将会删除它们。

如果手动运行git prune,则必须选择--expire参数。没有--expire的默认值不是2.weeks.ago,而是now



1树对象实际上包含三元组:名称,模式,哈希。对于blob对象,模式为100644100755,对于子树,模式为004000,对于符号链接,模式为120000,依此类推。

2为了在Linux上提高查找速度,散列会在前两个字符后分割:散列名称ab34ef56...ab/34e567...目录中变为.git/objects。这样可以使每个子目录的大小保持在.git/objects内,以减少某些目录操作的O(n2)行为。这与git gc --auto绑定,当一个对象目录变得足够大时,会自动重新打包。 Git假定每个子目录的大小大致相同,因为散列大多应均匀分布,因此只需要计算一个子目录即可。

关于git - git在执行操作时会做什么:git gc-git prune,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50135844/

相关文章:

python - edX 供应问题 : get stuck at, 任务:[edxapp |将 edx-platform repo check out 到 {{edxapp_code_dir}}]

Github 在克隆存储库后从一个分支切换到另一个分支

Git:Git 什么时候进行垃圾回收?

git - 为什么 Git 在(理论上)忽略的文件(路径)上向我建议 "changes"?

python - sourcetree 与 python Hook

java - 使用 JMX 的 GC 使用的 CPU 百分比

c# - 如果 Finalizer 调用 Dispose() ,您可以触发 "Disposing"事件吗?

java - Java 持续垃圾收集

Git gc 使用过多内存,无法完成