当我们这样做时 git 会做什么:git gc - git prune

What does git do when we do : 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;DR

git prune 仅删除 松散,无法访问,陈旧 objects(objects 必须具有所有三个属性才能获得 p运行编)。无法访问的打包 objects 保留在它们的打包文件中。可达松散 objects 保持可达和松散。 Object无法访问但尚未过时的文件也保持不变。 stale 的定义有点棘手(详见下文)。

git gc 做的更多:它打包引用,打包有用的 objects,使 reflog 条目过期,p运行es loose objects,p运行es 删除了工作树,p运行es / gc 的旧 git rerere 数据。

我不确定上面的 "in the background" 是什么意思(背景 在 shell 和所有 activity 这里发生在 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:将松散的 object 打包成 打包的 object 格式
  • git prune: 删除不需要的松动 objects
  • git worktree prune:删除用户已删除的已添加工作树的工作树数据
  • git rerere gc: 删除旧的 rerere 记录

还有几个单独的文件活动 git gc 自己做,但以上是主要序列。请注意,git prune 发生在 after (1) reflogs 过期和 (2) 运行ning git repack:这是因为过期的 reflog 条目被删除可能会导致 object 变得未被引用,因此不会被打包,然后被 p运行ed 以使其完全消失。

在我们查看重新包装和 p运行e

之前需要了解的内容

在进入更多细节之前,最好先定义 object 在 Git 中的含义,以及它对object 是 宽松的 紧凑的 。我们还需要了解 object 是什么意思 可达 .

每个 object 都有一个哈希 ID——例如,你在 git log 中看到的那些又大又丑的 ID 之一——也就是 object 的名字,用于检索目的. Git 将所有 object 存储在 key-value 数据库中,其中名称是键,object 本身是值。因此,Git 的 object 是 Git 存储文件和提交的方式,事实上,有四种 object 类型: commit object 持有实际提交。 tree object 包含对的集合,1 一个 human-readable 名称,如 READMEsubdir 以及另一个 object 的哈希 ID。如果树中的名称是文件名,则另一个 object 是 blob object,如果名称是,则它是另一棵树 object一个子目录。 blob objects 包含实际的文件内容(但请注意文件的 name 在树中 linking 到 blob!)。最后一个object类型是annotated tag,用于annotated标签,这里不是特别感兴趣

一旦创建,object 将永远无法更改。这是因为 object 的名称(它的哈希 ID)是通过查看 object 的内容的每一位来计算的。将任何一位从零更改为一,反之亦然,哈希 ID 也会更改:您现在有一个 不同的 object,具有 不同的名称。这就是 Git 检查没有文件曾经被 messed-with 的方式:如果文件内容被改变,object 的散列 ID 也会改变。 object ID 存储在树条目中,如果树 object 被更改,树的 ID 也会更改。树的 ID 存储在提交中,如果树 ID 更改,提交的哈希值也会更改。因此,如果您知道提交的散列是 a234b67... 并且提交的内容仍然散列为 a234b67...,则提交中没有任何更改,并且树 ID 仍然有效。如果树仍然散列到它自己的名字,它的内容仍然有效,所以 blob ID 是正确的;所以只要 blob 内容散列到它自己的名称,blob 也是正确的。

Objects 可以是 loose,这意味着它们存储为文件。文件名就是散列ID。2松散object的内容是zlib-deflated。或者,object 可以 打包,这意味着许多 object 存储在一个 pack-file 中。在这种情况下,内容不仅仅是放气,它们是第一个 delta-compressed. Git picks out a base object—often the latest version of some blob (file)—and then finds additional objects that can be represented as a series of commands: take the base file, remove some text at this offset, add other text at another offset, and so on. The actual format of pack files is documented here,如果稍微轻一点的话。请注意,与大多数版本控制系统不同,delta-compression 发生在 低于 抽象的 stored-object 级别:Git 存储整个快照,然后 delta-compression 稍后 在基础 object 上存储。 Git 仍然通过其 hash-ID 名称访问 object;只是读取 object 涉及读取包文件,找到 object 及其底层 delta 基础,并即时重建完整的 object。

有关打包文件的一般规则规定,任何 delta-compressed object 中的打包文件必须包含其所有基础 打包文件。这意味着一个包文件是 self-contained:永远不需要打开多个额外的包文件来从具有 object 的包中获取 object。 (可以故意违反此特定规则,生成 Git 所谓的 thin pack,但这些仅用于通过网络发送 object连接到另一个已经有基础 object 的 Git。另一个 Git 必须 "fix" 或 "fatten" 精简包才能制作正常的包文件,然后将其留在 Git 的其余部分。)

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

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

<--o
   |
   v

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

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

其中一个提交必须是链的 start:即 root 提交。其中之一必须是 end,那就是 tip 提交。所有内部箭头都指向后方(向左),因此我们可以在没有 arrow-heads 的情况下绘制它,知道根在左边,尖端在右边:

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

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

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

None 嵌入 within 的箭头可以更改,因为 any object 中没有任何内容可以永远改变。然而,分支名称中的箭头 master 实际上只是某个提交的哈希 ID,而这个 可以 更改。让我们用字母来表示提交哈希值:

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

名称master 现在只存储提交E 的提交散列。如果我们向 master 添加一个新提交,我们通过写出一个 parent 是 E 并且其树是我们的快照的提交来做到这一点,给我们一个 all-new 哈希,我们可以称之为 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,发现它有没有parent,就完成了。

如果有分支,那只意味着我们找到了另一个名称的提交,其 parent 是也由名称 master:

找到的提交之一
A--B--C--D--E--F   <-- master
             \
              G--H   <-- develop

名字develop定位commitHH 找到 GG 指回 E。所以所有这些提交都是可达.

提交多个 parent——即,合并提交——如果提交本身是可达的,则使它们的所有 parent 都可达。因此,一旦您进行了合并提交,您就可以(但不必)删除标识提交的分支名称 merged-in:现在可以从您执行时所在的分支的尖端访问它合并操作。即:

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

此处底行的提交可通过合并从 name 访问,就像顶行的提交始终可从 name 访问一样。删除名称 delete-able 仍然可以访问它们。如果合并提交是not,在这种情况下:

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

然后删除 not-delete-able 有效地 放弃 底部行的两个提交:它们变得不可访问,因此符合 garbage-collection.

同样的可达性 属性 适用于树和 blob object。例如,提交 G 中有一个 tree,而这个 tree 对:

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

因此从提交 G 开始,tree object d097... 是可访问的;从那棵树上,blob object 9fa3... 是可达的,blob object 0b41... 也是可达的。提交 H 可能有完全相同的 README object,在相同的名称下(虽然不同的树):没关系,这只是使 9fa3 可以双重访问,这不是Git 感兴趣:Git 只关心它是否可达。

外部引用——分支和标签名称,以及在 Git 存储库中找到的其他引用(包括 Git 的 index 中的条目以及通过linked 添加 work-trees),提供进入 object 图表的入口点。从这些入口点,任何 object 要么是可达的——有一个或多个可以通向它的名字——要么是 不可达,这意味着 [=391] 没有名字=] 本身可以找到。我从这个描述中省略了带注释的标签,但它们通常是通过标签名称找到的,带注释的标签 object 有一个 object 引用(任意 object 类型),它找到了,如果标签 object 本身是可达的,则使那个 object 可达。

因为引用只引用一个 object,但有时我们用一个分支名称做一些事后我们想撤消,Git保留一个log 引用的每个值以及时间。这些参考日志或 reflogs 让我们知道 master 昨天 中有什么,或者在 develop 上周有什么.最终,这些 reflog 条目陈旧过时,不太可能再有用了,git reflog expire 将丢弃它们。

重新打包并 p运行e

git repack 的作用,在高层次上,现在应该相当清楚了:它将许多松散的 object 的集合变成一个包含所有这些 object 的包文件秒。不过,它可以做的更多:它可以包含前一个包中的所有 object。之前的包变得多余,之后可以移除。它还可以省略任何无法访问object,将它们变成松散 objects。当 git gc 运行s git repack 它使用依赖于 git gc 选项的选项来执行此操作,因此这里的确切语义有所不同,但前景的默认值 git gc是使用git repack -d -l,其中有git repack删除冗余包和运行git prune-packedprune-packed 程序删除了也出现在包文件中的松散 object 文件,因此这删除了进入包的松散 object 文件。 repack 程序将 -l 选项传递给 git pack-objects(这是构建包文件的实际主力),这意味着省略从其他程序借来的 objects存储库。 (最后一个选项对于大多数正常 Git 用法并不重要。)

无论如何,它是 git repack——或者从技术上讲,git pack-objects——打印计数、压缩和写入消息。完成后,您将获得一个新的打包文件,而旧的打包文件将消失。新的包文件包含所有可达的 objects,包括旧的可达打包的 objects 和旧的可达的​​松散的 objects。如果松散的 objects 从旧的(现在 torn-down 和删除的)包文件之一中弹出,它们会与其他松散的(且无法访问的)objects 一起使您的存储库混乱。如果它们在 tear-down 期间被摧毁,则只剩下现有的 loose-and-unreachable object。

现在是 git prune 的时候了:这会找到松散的、无法访问的 objects 并将它们删除。但是,它有一个安全开关,--expire 2.weeks.ago:默认情况下,如 git gc 运行,它 不会 删除这样的 object如果他们不是至少两周大。这意味着任何正在 创建新的 objects 的 Git 程序,还没有连接它们,都有一个宽限期。新的 object 在 git prune 删除它们之前的十四天(默认情况下)可能是松散的和不可访问的。因此,忙于创建 objects 的 Git 程序有十四天的时间可以将那些 objects 的 hooking-up 完成到图中。如果它认为那些 object 不值得 hooking-up,它可以离开它们;从那时起 14 天,未来 git prune 将删除它们。

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


1树 objects 实际上包含三元组:名称、模式、散列。对于 blob object,模式是 100644100755,对于 sub-tree,模式是 004000,对于符号 link,模式是 120000,等等。

2为了 Linux 上的查找速度,散列在前两个字符后拆分:散列名称 ab34ef56... 变为 ab/34e567....git/objects 目录中。这将每个子目录的大小保持在 .git/objects small-ish 以内,这会抑制某些目录操作的 O(n2) 行为。这与 git gc --auto 相关,当一个 object 目录变得足够大时,它会自动重新打包。 Git 假设每个 sudirectory 的大小差不多,因为 hashes 应该大部分是均匀分布的,所以只需要计算 一个 个子目录。

由于最近添加了 (Git 2.29(2020 年第 4 季度)),git gc -prune 的替换为:

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

使用 Git 2.31(2021 年第一季度),“git maintenance"(man) 工具学会了一项新的 pack-refs 维护任务。

参见 commit acc1c4d, commit 41abfe1 (09 Feb 2021) by Derrick Stolee (derrickstolee)
(由 Junio C Hamano -- gitster -- in commit d494433 合并,2021 年 2 月 17 日)

maintenance: add pack-refs task

Signed-off-by: Derrick Stolee
Reviewed-by: Taylor Blau

It is valuable to collect loose refs into a more compressed form.
This is typically the packed-refs file, although this could be the reftable in the future.
Having packed refs can be extremely valuable in repos with many tags or remote branches that are not modified by the local user, but still are necessary for other queries.

For instance, with many exploded refs, commands such as

git describe --tags --exact-match HEAD

can be very slow (multiple seconds).
This command in particular is used by terminal prompts to show when a detatched HEAD is pointing to an existing tag, so having it be slow causes significant delays for users.

Add a new 'pack-refs' maintenance task.
It runs 'git pack-refs --all --prune'(man) to move loose refs into a packed form.
For now, that is the packed-refs file, but could adjust to other file formats in the future.

This is the first of several sub-tasks of the 'gc' task that could be extracted to their own tasks.
In this process, we should not change the behavior of the 'gc' task since that remains the default way to keep repositories maintained.
Creating a new task for one of these sub-tasks only provides more customization options for those choosing to not use the 'gc' task.
It is certainly possible to have both the 'gc' and 'pack-refs' tasks enabled and run regularly.
While they may repeat effort, they do not conflict in a destructive way.

The 'auto_condition' function pointer is left NULL for now.
We could extend this in the future to have a condition check if pack-refs should be run during 'git maintenance run --auto'(man).

git maintenance 现在包含在其 man page 中:

pack-refs

The pack-refs task collects the loose reference files and collects them into a single file. This speeds up operations that need to iterate across many references.

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

maintenance: incremental strategy runs pack-refs weekly

Signed-off-by: Derrick Stolee
Reviewed-by: Taylor Blau

When the 'maintenance.strategy' config option is set to 'incremental', a default maintenance schedule is enabled.
Add the 'pack-refs' task to that strategy at the weekly cadence.

git config 现在包含在其 man page 中:

task, but runs the prefetch and commit-graph tasks hourly, the loose-objects and incremental-repack tasks daily, and the pack-refs task weekly.


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

参见 commit 26c7974 (23 Feb 2021) by Eric Sunshine (sunshineco)
(由 Junio C Hamano -- gitster -- in commit d166e8c 合并,2021 年 2 月 25 日)

maintenance: fix incorrect maintenance.repo path with bare repository

Reported-by: Clement Moyroud
Signed-off-by: Eric Sunshine

The periodic maintenance tasks configured by git maintenance start(man) invoke git for-each-repo(man) to run git maintenance run(man) on each path specified by the multi-value global configuration variable maintenance.repo.
Because git for-each-repo will likely be run outside of the repositories which require periodic maintenance, it is mandatory that the repository paths specified by maintenance.repo are absolute.

Unfortunately, however, git maintenance register(man) does nothing to ensure that the paths it assigns to maintenance.repo are indeed absolute, and may in fact -- especially in the case of a bare repository -- assign a relative path to maintenance.repo instead.
Fix this problem by converting all paths to absolute before assigning them to maintenance.repo.

While at it, also fix git maintenance unregister(man) to convert paths to absolute, as well, in order to ensure that it can correctly remove from maintenance.repo a path assigned via git maintenance register.


随着 Git 2.30(2020 年第 4 季度),“git maintenance"(man), an extended big brother of "git gc"(man) 继续发展,用新命令代替 git gcgit prune:

参见 commit e841a79, commit a13e3d0, commit 52fe41f, commit efdd2f0, commit 18e449f, commit 3e220e6, commit 252cfb7, commit 28cb5e6 (25 Sep 2020) by Derrick Stolee (derrickstolee)
(由 Junio C Hamano -- gitster -- in commit 52b8c8c 合并,2020 年 10 月 27 日)

maintenance: add loose-objects task

Signed-off-by: Derrick Stolee

One goal of background maintenance jobs is to allow a user to disable auto-gc (gc.auto=0) but keep their repository in a clean state.
Without any cleanup, loose objects will clutter the object database and slow operations.
In addition, the loose objects will take up extra space because they are not stored with deltas against similar objects.

Create a 'loose-objects' task for the 'git maintenance run'(man) command.
This helps clean up loose objects without disrupting concurrent Git commands using the following sequence of events:

  1. Run 'git prune-packed'(man) to delete any loose objects that exist in a pack-file. Concurrent commands will prefer the packed version of the object to the loose version. (Of course, there are exceptions for commands that specifically care about the location of an object. These are rare for a user to run on purpose, and we hope a user that has selected background maintenance will not be trying to do foreground maintenance.)

  2. Run 'git pack-objects'(man) on a batch of loose objects.
    These objects are grouped by scanning the loose object directories in lexicographic order until listing all loose objects -or- reaching 50,000 objects. This is more than enough if the loose objects are created only by a user doing normal development. We noticed users with millions of loose objects because VFS for Git downloads blobs on-demand when a file read operation requires populating a virtual file.

This step is based on a similar step in Scalar and VFS for Git.

git maintenance 现在包含在其 man page 中:

loose-objects

The loose-objects job cleans up loose objects and places them into pack-files.

In order to prevent race conditions with concurrent Git commands, it follows a two-step process.

  • First, it deletes any loose objects that already exist in a pack-file; concurrent Git processes will examine the pack-file for the object data instead of the loose object.
  • Second, it creates a new pack-file (starting with "loose-") containing a batch of loose objects.

The batch size is limited to 50 thousand objects to prevent the job from taking too long on a repository with many loose objects.
The gc task writes unreachable objects as loose objects to be cleaned up by a later step only if they are not re-added to a pack-file; for this reason it is not advisable to enable both the loose-objects and gc tasks at the same time.