启动时后台发生了什么,
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 修剪的输出:
Checking connectivity: 945490, done.
这两个选项有什么区别?
谢谢
TL;博士
git prune
仅删除松散、无法访问的过时对象(对象必须具有所有三个属性才能修剪)。 无法访问的打包对象仍保留在其打包文件中。 可触及的松散对象仍可触及和松散。 无法访问但尚未过时的对象也保持不变。陈旧的定义有点棘手(详见下文)。
git gc
的功能更多:它打包引用、打包有用的对象、使 reflog 条目过期、修剪松散的对象、修剪删除的工作树以及修剪/GC 的旧git rerere
数据。
长
我不确定你上面所说的"在后台"是什么意思(背景在贝壳中具有技术含义,这里的所有活动都发生在贝壳的前景中,但我怀疑你不是这些术语的意思)。
git gc
所做的是策划一系列的收集活动,包括但不限于git prune
。 下面的列表是由前台gc
运行的命令集,没有--auto
(省略它们的参数,这在一定程度上取决于git gc
参数):
git pack-refs
:紧凑的引用(将.git/refs/heads/...
和.git/refs/tags/...
条目转换为.git/packed-refs
条目,消除单个文件)git reflog expire
:使旧的引用日志条目过期git repack
:将松散的对象打包为打包的对象格式git prune
:去除不需要的松散物体git worktree prune
:删除用户已删除的已添加工作树的工作树数据git rerere gc
:删除旧的rerere记录
还有一些单独的文件活动git gc
自己执行,但以上是主序列。 请注意,git prune
发生在 (1) 过期的 reflogs 和 (2) 运行git repack
之后:这是因为删除的过期 reflog 条目可能会导致对象变得未被引用,因此不会被打包然后被修剪,以便它完全消失。
在我们查看重新包装和修剪之前需要了解的内容
在详细介绍之前,最好在 Git 中定义对象是什么,以及对象松散或打包意味着什么。 我们还需要了解对象可访问意味着什么。
每个对象都有一个哈希 ID——例如,你在git log
中看到的那些丑陋的大 ID 之一——这是该对象的名称,用于检索目的。 Git 将所有对象存储在键值数据库中,其中名称是键,对象本身是值。 因此,Git 的对象是 Git 存储文件和提交的方式,实际上,有四种对象类型:提交对象包含实际提交。树对象包含一组对,1人类可读的名称(如README
或subdir
)以及另一个对象的哈希 ID。 如果树中的名称是文件名,则另一个对象是blob对象,如果名称是子目录的名称,则另一个对象是另一个树对象。 Blob 对象保存实际的文件内容(但请注意,文件名位于链接到 Blob的树中! 最后一个对象类型是带注释的标签,用于带注释的标签,这里不是特别有趣。
一旦制作完成,任何对象都无法更改。 这是因为对象的名称(哈希 ID)是通过查看对象内容的每一位来计算的。 将任何一位从零更改为 1,反之亦然,哈希 ID 就会更改:您现在有一个不同的对象,具有不同的名称。 这就是 Git 检查没有文件被弄乱的方式:如果文件内容被更改,对象的哈希 ID 就会改变。 对象 ID 存储在树条目中,如果更改了树对象,则树的 ID 也会更改。 树的 ID 存储在提交中,如果树 ID 被更改,提交的哈希也会改变。 因此,如果您知道提交的哈希值是a234b67...
的,并且提交的内容仍然散列到a234b67...
,则提交中没有任何更改,并且树 ID 仍然有效。 如果树仍哈希为其自己的名称,则其内容仍然有效,因此 blob ID 是正确的;因此,只要 Blob 内容哈希为其自己的名称,Blob 也是正确的。
对象可以是松散的,这意味着它们存储为文件。 文件的名称只是哈希ID.2松散对象的内容是 zlib 放气的。 或者,可以打包对象,这意味着许多对象存储在单个打包文件中。 在这种情况下,内容不仅会放气,还会首先进行增量压缩。 Git 选取一个基本对象(通常是某个 blob(文件)的最新版本),然后查找可以表示为一系列命令的其他对象:获取基本文件、删除此偏移量处的一些文本、在另一个偏移量处添加其他文本,等等。 包文件的实际格式记录在这里,如果有点轻。 请注意,与大多数版本控制系统不同,增量压缩发生在存储对象抽象以下的级别:Git 存储整个快照,然后稍后在底层对象上进行增量压缩。 Git 仍然通过其哈希 ID 名称访问对象;只是读取该对象涉及读取包文件,查找对象及其底层增量基,并动态重建完整的对象。
关于包文件有一条一般规则,规定包文件中的任何增量压缩对象必须将其所有基元都放在同一个包文件中。 这意味着包文件是自包含的:永远不需要打开多个额外的包文件即可从包含该对象的包中取出对象。 (可以故意违反此特定规则,从而生成 Git 所谓的精简包,但这些规则仅用于通过网络连接将对象发送到另一个已经具有基本对象的 Git。 另一个 Git 必须"修复"或"增肥"精简包以制作普通包文件,然后再将其留给 Git 的其余部分。
对象的可访问性有点棘手。 让我们先看一下提交可访问性。
请注意,当我们有一个提交对象时,该提交对象本身包含多个哈希 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
。 这具有E
的哈希 ID ,因此我们已经达到了提交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 会记录引用的每个值以及时间。 这些参考日志或reflogs让我们知道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...
在.git/objects
目录中变为ab/34e567...
。 这会将每个子目录的大小保持在.git/objects
小范围内,从而驯服某些目录操作的 O(n2) 行为。 这与git gc --auto
有关,当一个对象目录变得足够大时,它会自动重新打包。 Git 假设每个子目录的大小大致相同,哈希应该大部分是均匀分布的,因此它只需要计算一个子目录。
自从最近添加了git maintenance
命令(Git 2.29(2020 年第 4 季度))以来,git gc -prune
的替代品将是:
git maintenance pack-refs
# for
git pack-refs --all --prune
在 Git 2.31(2021 年第 1 季度)中,"git maintenance
">(人)工具学习了新的pack-refs
维护任务。
请参阅提交 acc1c4d,提交 41abfe1 (09 Feb 2021) 由 Derrick Stolee (derrickstolee
).
(由 Junio C Hamano --gitster
-- 合并于 提交 d494433,2021 年 2 月 17 日)
maintenance
:添加包引用任务签名者:德里克·斯托利
评论者:泰勒·布劳
将松散的引用收集成更压缩的形式很有价值.
这通常是打包的引用文件,尽管将来这可能是可引用的。
在具有许多标记或远程分支的存储库中,打包的引用可能非常有价值,这些标记或远程分支未被本地用户修改,但对于其他查询仍然是必需的。例如,对于许多分解的引用,诸如
git describe --tags --exact-match HEAD
可能非常慢(数秒).
添加新的 '
终端提示符尤其使用此命令来显示已解绑的 HEAD 何时指向现有标记,因此速度较慢会导致用户出现显著延迟。pack-refs
' 维护任务.它运行 '
git pack-refs --all --prune
'(man)将松散的引用移动到打包形式中.
目前,这是打包的引用文件,但将来可以调整为其他文件格式。这是"gc"任务的几个子任务中的第一个,可以提取到它们自己的任务中。
在这个过程中,我们不应该改变'gc'任务的行为,因为这仍然是维护存储库的默认方式。
为其中一个子任务创建新任务只会为那些选择不使用'gc'任务的人提供更多的自定义选项。
当然可以同时拥有两者"gc"和"pack-refs"任务启用并定期运行.
虽然它们可能会重复工作,但它们不会以破坏性的方式发生冲突。'
auto_condition
' 函数指针暂时保留NULL
.
我们可以在将来扩展它,以检查是否应该在 'git maintenance run --auto
'(man)期间运行包引用的条件。
git maintenance
现在在其手册页中包含:
pack-refs
pack-refs
任务收集松散的参考文件和 将它们收集到单个文件中。这加快了操作速度 需要遍历许多引用。
它可以按计划运行,作为其新的 pack-refs 任务的一部分:
maintenance
:增量策略每周运行包参考签名者:德里克·斯托利
评论者:泰勒·布劳
当"
maintenance.strategy
"配置选项设置为"incremental
"时,将启用默认维护计划.
按每周节奏将"pack-refs"任务添加到该策略中。
git config
现在在其手册页中包含:
任务,但每小时运行
prefetch
和commit-graph
任务, 每天loose-objects
和incremental-repack
任务,以及pack-refs
任务每周。
"git maintenance register
"(man) 命令在注册裸存储库时遇到问题,该存储库已在 Git 2.31(2021 年第 1 季度)中进行了更正。
参见 提交 26c7974 (2021 年 2 月 23 日) 由 Eric Sunshine (sunshineco
).
(由 Junio C Hamano --gitster
-- 合并于 提交 d166e8c,2021 年 2 月 25 日)
maintenance
:使用裸存储库修复不正确的maintenance.repo
路径报告人:Clement Moyroud
签名者:Eric Sunshine
由
git maintenance start
(man) 配置的定期维护任务调用git for-each-repo
(man) 以在多值全局配置变量maintenance.repo
.
指定的每个路径上运行git maintenance run
(man)由于git for-each-repo
可能会在需要定期维护的存储库之外运行,因此maintenance.repo
指定的存储库路径必须是绝对的。然而,不幸的是,
git maintenance register
(man)没有采取任何措施来确保它分配给maintenance.repo
的路径确实是绝对的,并且实际上可能 - 特别是在裸存储库的情况下 - 为maintenance.repo
分配相对路径.
通过在将所有路径分配给maintenance.repo
之前将所有路径转换为绝对路径来解决此问题。同时,还要修复
git maintenance unregister
(man)以将路径转换为绝对路径,以确保它可以正确地从maintenance.repo
通过git maintenance register
分配的路径中删除。
在 Git 2.30(2020 年第 4 季度)中,"git maintenance
"(man)是"git gc
">(man)的扩展老大哥,继续发展,使用新命令代替git gc
和git prune
:
请参阅提交 e841a79、提交 a13e3d0、提交 52fe41f、提交 efdd2f0、提交 18e449f、提交 3e220e6、提交 252cfb7、提交 28cb5e6(2020 年 9 月 25 日)由 Derrick Stolee (derrickstolee
).
(由 Junio C Hamano --gitster
-- 合并于 提交 52b8c8c,2020 年 10 月 27 日)
maintenance
:添加松散对象任务签名者:德里克·斯托利
后台维护作业的一个目标是允许用户禁用 auto-gc (
gc.auto=0
),但使其存储库保持干净状态。
如果不进行任何清理,松散的对象会使对象数据库混乱并减慢操作速度。
此外,松散对象将占用额外的空间,因为它们不是与针对类似对象的增量存储的。为"
git maintenance run
">(man)命令创建"松散对象"任务.
这有助于使用以下事件序列清理松散对象,而不会中断并发 Git 命令:
运行 '
git prune-packed
'(man)删除任何存在的松散对象 在包文件中。并发命令将首选打包的 对象的版本到松散版本。(当然,有 是专门关心 对象的位置。这些对于用户来说很少运行 目的,我们希望用户选择了背景 维护不会尝试执行前台维护。在一批松散的物体上运行 '
git pack-objects
'(man).
这些 通过扫描松散对象目录对对象进行分组 词典顺序,直到列出所有松散的对象 -或- 达到 50,000 个对象。如果松散,这绰绰有余 对象仅由执行正常开发的用户创建。 我们注意到用户有数百万个松散的对象,因为VFS 对于 Git 在文件读取操作时按需下载 blob 需要填充虚拟文件。此步骤基于 Scalar 和 VFS for Git 中的类似步骤。
git maintenance
现在在其手册页中包含:
loose-objects
loose-objects
作业清理松散的对象并将其放入 打包文件。为了防止并发 Git 的竞争条件 命令,它遵循两步过程。
- 首先,它会删除任何松散的 已存在于打包文件中的对象;并发 Git 进程 将检查打包文件中的对象数据,而不是松散的 对象。
- 其次,它创建一个新的打包文件(以"
loose-
开头") 包含一批松散的对象。批量大小限制为 50 千个对象,以防止作业在 包含许多松散对象的存储库.
gc
任务写入无法访问 对象作为松散对象,仅在以下情况下由后续步骤清理 它们不会重新添加到包文件中;因此,它不是 建议gc
loose-objects
在 同时。