启动时背景发生了什么,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; DRgit 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,一个易于理解的名称,例如README
或subdir
以及另一个对象的哈希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
定位提交H
; H
找到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-packed
。 prune-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对象,模式为
100644
或100755
,对于子树,模式为004000
,对于符号链接,模式为120000
,依此类推。2为了在Linux上提高查找速度,散列会在前两个字符后分割:散列名称
ab34ef56...
在ab/34e567...
目录中变为.git/objects
。这样可以使每个子目录的大小保持在.git/objects
内,以减少某些目录操作的O(n2)行为。这与git gc --auto
绑定,当一个对象目录变得足够大时,关于git - git在执行操作时会做什么:git gc-git prune,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50135844/