9

启动时后台发生了什么,

  • 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.

这两个选项有什么区别?

谢谢

4

2 回答 2

20

TL;博士

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:删除旧的rerere记录

还有一些单独的文件活动git gc可以自己完成,但以上是主要顺序。请注意,这git prune发生(1) 过期 reflog 和 (2) running之后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-deflate。或者,可以打包对象,这意味着许多对象存储在一个打包文件中。在这种情况下,内容不仅仅是放气,它们首先是delta-compressed。Git 挑选出一个基础对象——通常是某个 blob(文件)的最新版本——然后找到可以表示为一系列命令的其他对象:获取基础文件,删除该偏移处的一些文本,在另一个处添加其他文本偏移量,等等。包文件的实际格式记录在这里,如果有点轻。请注意,与大多数版本控制系统不同,增量压缩发生在存储对象抽象以下的级别:Git 存储整个快照,然后在底层对象上进行增量压缩。Git 仍然通过其哈希 ID 名称访问对象;只是读取该对象涉及读取包文件,查找对象及其底层增量基础,并即时重建完整的对象。

关于包文件有一条通用规则,即包文件中的任何 delta 压缩对象都必须一个包文件中具有所有基础。这意味着一个包文件是自包含的:永远不需要打开多个额外的包文件来从包含该对象的包中取出一个对象。(可以故意违反此特定规则,产生 Git 所谓的瘦包,但这些规则仅用于通过网络连接将对象发送到已经拥有基本对象的另一个 Git。另一个 Git 必须“修复”或在将其留给 Git 的其余部分之前,“增肥”瘦包以制作一个普通的包文件。)

对象可达性有点棘手。让我们先看看提交可达性

请注意,当我们有一个提交对象时,该提交对象本身包含多个哈希 ID。它有一个用于保存与该提交相关的快照的树的哈希 ID。它还具有一个或多个父提交的哈希 ID ,除非此特定提交是提交。根提交被定义为没有父提交的提交,所以这有点循环:提交有父提交,除非它没有父提交。不过很清楚:给定一些提交,我们可以将该提交绘制为图中的一个节点,箭头从节点出来,每个父节点一个:

<--o
   |
   v

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

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

这些提交之一必须是链的开始:那是提交。其中之一必须是end,这就是提示提交。所有内部箭头都指向后(向左),所以我们可以在没有箭头的情况下绘制它,知道根在左边,尖端在右边:

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

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

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

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

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

该名称master现在只存储 commit 的提交哈希E。如果我们向 中添加一个新的提交master,我们会写出一个提交,它的父E节点是我们的快照,它的树是我们的快照,给我们一个全新的哈希,我们可以调用它F。将点提交FE. 我们将 GitF的哈希 ID 写入master其中,现在我们有:

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

我们添加了一个提交并更改了一个名称,master. 从 name 开始可以访问所有以前的提交master。我们读出了 hash IDF并读取了 commit F。它的哈希 ID 为E,所以我们已经提交了E。我们读取E以获取 的哈希 ID D,从而达到D。我们重复,直到我们阅读A,发现它没有父母,并完成。

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

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

名称develop位于 commit 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有 <name, ID> 对:

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

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

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

因为引用只引用一个对象,但有时我们使用分支名称做某事后想要撤消,Git 会记录引用所具有的每个值以及时间。这些参考日志或reflogs让我们知道昨天master有什么,或者上周有什么。最终,这些 reflog 条目是旧的和陈旧的,不太可能再有用了,并将丢弃它们。developgit 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 天的时间可以完成将这些对象连接到图表中。如果它认为这些对象不值得连接,它可以离开它们;从那时起的 14 天,未来git prune将删除它们。

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


1树对象实际上包含三元组:名称、模式、哈希。模式是100644100755用于 blob 对象、004000子树、120000符号链接等等。

2对于 Linux 上的查找速度,哈希在前两个字符后拆分:哈希名称ab34ef56...变为目录中ab/34e567....git/objects这将每个子目录的大小保持在.git/objects较小的范围内,从而驯服了某些目录操作的 O(n 2 ) 行为。这与git gc --auto当一个对象目录变得足够大时自动重新打包有关。Git 假设每个子目录的大小与哈希值大致相同,应该是均匀分布的,因此它只需要计算一个子目录。

于 2018-05-02T18:11:20.590 回答
2

由于最近添加了git maintenance命令(Git 2.29 (Q4 2020)),替换为git gc -prune

git maintenance pack-refs
# for
git pack-refs --all --prune

在 Git 2.31(2021 年第一季度)中,“ git maintenanceman工具学习了一项新的pack-refs维护任务。

请参阅Derrick Stolee ( ) 的commit acc1c4dcommit 41abfe1(2021 年 2 月 9 日(由Junio C Hamano 合并 -- --提交 d494433中,2021 年 2 月 17 日)derrickstolee
gitster

maintenance: 添加 pack-refs 任务

签字人:Derrick Stolee
审核人:Taylor Blau

将松散的 refs 收集成更压缩的形式是很有价值的。
这通常是 packed-refs 文件,尽管这可能是未来的 reftable。
打包 refs 在带有许多标签或远程分支的仓库中非常有价值,这些标签或远程分支未被本地用户修改,但对于其他查询仍然是必需的。

例如,对于许多分解的引用,诸如

git describe --tags --exact-match HEAD

可能非常慢(几秒钟)。
终端提示特别使用此命令来显示分离的 HEAD 何时指向现有标签,因此让它变慢会导致用户显着延迟。

添加新的 ' pack-refs' 维护任务。
它运行 ' git pack-refs --all --prune' ( man )将松散的 refs 移动到打包的形式中。
目前,这是打包的参考文件,但将来可能会调整为其他文件格式。

这是“gc”任务的几个子任务中的第一个,可以提取到他们自己的任务中。
在这个过程中,我们不应该改变“gc”任务的行为,因为这仍然是保持存储库维护的默认方式。
为这些子任务之一创建新任务只会为那些选择不使用“gc”任务的人提供更多自定义选项。
当然可以同时启用“gc”和“pack-refs”任务并定期运行。
虽然他们可能会重复努力,但他们不会以破坏性的方式发生冲突。

' auto_condition' 函数指针暂时保留NULL
我们可以在未来扩展它来检查是否应该在 ' git maintenance run --auto' ( man )期间运行 pack-refs 。

git maintenance现在在其手册页中包含:

pack-refs

pack-refs任务收集松散的参考文件并将它们收集到一个文件中。这加快了需要遍历许多引用的操作。

作为新的 pack-refs 任务的一部分,它可以按计划运行:

maintenance: 增量策略每周运行 pack-refs

签字人:Derrick Stolee
审核人:Taylor Blau

当“ maintenance.strategy”配置选项设置为“ incremental”时,启用默认维护计划。
以每周的节奏将“pack-refs”任务添加到该策略中。

git config现在在其手册页中包含:

任务,但每小时 运行prefetch和任务,每天运行和任务, 每周运行任务。commit-graphloose-objectsincremental-repackpack-refs


git maintenance register( man )命令在注册裸存储库时遇到问题,该问题已在 Git 2.31(2021 年第一季度)中得到纠正。

请参阅Eric Sunshine ( ) 的提交 26c7974(2021 年 2 月 23 日(由Junio C Hamano 合并 -- --d166e8c 提交中,2021 年 2 月 25 日)sunshineco
gitster

maintenance:使用裸存储库修复不正确maintenance.repo的路径

报告人:Clement Moyroud
签字人:Eric Sunshine

由( man )配置的定期维护任务调用( man )在多值全局配置变量指定的每个路径上运行( man )。 因为可能会在需要定期维护的存储库之外运行,所以指定的存储库路径必须是绝对的。git maintenance startgit for-each-repogit maintenance runmaintenance.repo
git for-each-repomaintenance.repo

然而,不幸的是,( man )没有做任何事情来确保它分配给的路径确实是绝对的,并且实际上可能——尤其是在裸存储库的情况下——分配一个相对路径来代替。 通过在将所有路径分配给.git maintenance registermaintenance.repomaintenance.repo
maintenance.repo

同时,还修复man将路径转换为绝对路径,以确保它可以正确地从通过分配的路径中删除。git maintenance unregistermaintenance.repogit maintenance register


随着 Git 2.30(2020 年第四季度), " git maintenance" ( man )git gc , " " ( man )的扩展老大哥,继续发展,用新命令代替git gc and git prune

请参阅Derrick Stolee ( ) 的提交e841a79提交 a13e3d0提交 52fe41f提交 efdd2f0提交 18e449f提交 3e220e6提交 252cfb7提交 28cb5e6(2020 年 9 月 25 日(由Junio C Hamano 合并 -- --提交 52b8c8c中,2020 年 10 月 27 日)derrickstolee
gitster

maintenance: 添加松散对象任务

签字人:Derrick Stolee

后台维护作业的一个目标是允许用户禁用 auto-gc ( gc.auto=0) 但将其存储库保持在干净状态。
如果不进行任何清理,松散的对象会使对象数据库变得混乱并降低操作速度。
此外,松散的对象将占用额外的空间,因为它们没有与类似对象的增量一起存储。

git maintenance run为 ' ' ( man )命令创建一个 'loose-objects' 任务。
这有助于清理松散的对象,而不会使用以下事件序列中断并发 Git 命令:

  1. 运行 ' git prune-packed' ( man )删除包文件中存在的任何松散对象。并发命令将更喜欢对象的打包版本而不是松散版本。(当然,特别关心对象位置的命令也有例外。这些命令很少被用户故意运行,我们希望选择了后台维护的用户不要尝试进行前台维护。)

  2. 在一批松散的物体上运行 ' git pack-objects' ( man ) 。
    这些对象通过按字典顺序扫描松散对象目录进行分组,直到列出所有松散对象 - 或 - 达到 50,000 个对象。如果松散对象仅由进行正常开发的用户创建,这就绰绰有余了。我们注意到用户拥有数百万个松散对象,因为当文件读取操作需要填充虚拟文件时,VFS for Git 会按需下载 blob。

此步骤基于Scalar and VFS for Git中的类似步骤。

git maintenance现在在其手册页中包含:

loose-objects

loose-objects作业清理松散的对象并将它们放入包文件中。

为了防止并发 Git 命令的竞争条件,它遵循一个两步过程。

  • 首先,它删除包文件中已经存在的任何松散对象;并发 Git 进程将检查包文件中的对象数据而不是松散对象。
  • loose-其次,它创建了一个包含一批松散对象的新包文件(以“”开头)。

批量大小限制为 50,000 个对象,以防止作业在具有许多松散对象的存储库上花费太长时间。
gc任务将无法访问的对象作为松散对象写入,仅当它们没有被重新添加到包文件时才会被后续步骤清理;因此,不建议同时启用loose-objectsgc任务。

于 2021-02-21T00:45:40.940 回答