何时会进行git对象修剪:为什么“git gc”不删除提交?

18

我正在制作一门Git课程,想要提到失去的引用在运行git gc之前并不是真正的丢失。但事实并非如此。即使在运行git gc --prune=all --aggressive之后,这些失去的引用仍然存在。

显然,我理解错了什么。在课程中说错话之前,我想搞清楚事实!下面是一个示例脚本,说明了这种效应:

 #!/bin/bash

 git init

 # add 10 dummy commits
 for i in {1..10}; do
     date > foo.txt
     git add foo.txt
     git commit -m "bump" foo.txt
     sleep 1
 done;

 CURRENT=$(git rev-parse HEAD)
 echo HEAD before reset: ${CURRENT}

 # rewind
 git reset --hard HEAD~5

 # add another 10 commits
 for i in {1..10}; do
     date > foo.txt
     git add foo.txt
     git commit -m "bump" foo.txt
     sleep 1
 done;
这个脚本将添加10个虚拟提交,回退到5个提交之前并再次添加10个提交。在重置之前,它会打印出当前HEAD的哈希值。
我期望运行git gc --prune=all后丢失CURRENT中的对象。但是,我仍然可以对该哈希运行git show命令。
我知道,在运行git reset和添加新提交后,我实际上创建了一个新分支。但是我的原始分支已经没有任何引用了,因此它不会显示在git log --all中。它也不会被推送到任何远程分支。
我理解git gc的作用是删除这些对象。但事实并非如此。
为什么?那么git gc什么时候会删除对象呢?

2
你的 reflog 仍然包含对你“删除”的提交的引用。除非它们超时或你明确地将它们过期,否则它们不会被修剪。 - twalberg
有趣。我看了一下 https://git-scm.com/docs/git-reflog 并运行了 git reflog --expire=all 命令。然后这个对象仍然存在。接下来我又运行了另一个 gc 命令,但它仍然存在。即使运行了另一个 git gc --aggressive --prune=all 命令也没有帮助。 - exhuma
您需要使用 --expire=all --all,或在 HEAD(默认)和 master 上运行它。或者您可以手动删除特定条目(或参见下面的答案)。 - torek
1个回答

27

为了使对象被修剪,它必须符合两个条件。一个是日期/时间相关的:它必须已经创建1足够久以便被收集。 "足够久"部分是您使用--prune=all设置的:您正在覆盖正常的“至少两周前”的设置。

第二个标准是您的实验出了问题。为了被修剪,该对象还必须是不可访问的。正如twalberg在评论中指出的那样,每个被遗弃的提交(以及其对应的树和块)都通过Git的"reflog"条目引用。

每个这样的提交有两个reflog条目:一个是HEAD,另一个是HEAD在提交时所引用的分支名称的条目(在本例中,即refs/heads/master的reflog,即分支master)。每个reflog条目都有自己的时间戳,而git gc也会过期处理reflog条目,尽管其规则比默认的简单的“14天”对象到期规则更为复杂。2

因此,git gc 可以首先删除保留旧对象的所有reflog条目,然后对对象进行修剪。只是这里没有发生。

要手动查看或删除reflog条目,请使用git reflog命令。请注意,git reflog通过使用-g / --walk-reflogs选项(加上一些附加的显示格式选项)运行git log显示这些条目。您可以运行git reflog --all --expire=all清除所有内容,但在需要更精细操作时,这可能是一个过度的方法。使用--expire-unreachable可以实现更多的选择性。有关更多信息,请参见git log文档git reflog文档
1一些Unix文件系统根本不会存储文件的创建(“生日”)时间:一个stat结构的st_ctime字段是inode更改时间,而不是创建时间。如果存在创建时间,则在st_birthtimest_birthtimespec中。然而,每个Git对象都是只读的,因此文件的创建时间也是其修改时间。因此,st_mtime始终可用,可为该对象提供创建时间。

2确切的规则在git gc文档中有详细说明,但我认为对于不可达提交,默认为30天;对于可达提交,默认为90天是一个不错的总结。然而,这里“可达”的定义比较特殊:它意味着从此reflog持有旧值的参考当前值开始可达。也就是说,如果我们查看master的reflog,我们会找到master标识的提交(例如1234567),然后查看每个master的reflog条目(例如master@{27})是否从该特定提交(再次是1234567)可达。

3这个名字混淆问题是由POSIX标准化组织带来的 :-)。 st_birthtimespec字段是一个struct timespec,记录了秒和纳秒。


请注意,reflog 条目最终也会被垃圾回收。正如 git gc 文档 所述,可选的配置变量 gc.reflogExpire 默认为 90 天,而 gc.reflogExpireUnreachable 默认为 30 天。当运行 git gc 时,如果 reflog 中的可达和不可达条目的年龄超过这些变量,则它们将被删除。 - Rory O'Kane
@RoryO'Kane:没错;我把这个放在了文档链接里,但也许我应该直接在答案中提到它? - torek
是的,我认为直接回答问题标题可能会更有用,例如说“git gc”有时会删除提交。这也可以避免暗示“git reflog”是唯一一个删除reflog条目的命令。不过,将其写入您的答案并不是那么重要,因为读者可以从这些评论中获得相同的信息。 - Rory O'Kane
你知道prune是否会删除在提交消息中被引用的已分离提交吗?或者这个引用(如果被解释为一个)会使它们变得可达吗? - Kamafeather
@Kamafeather:提交信息文本不会被扫描哈希 ID。如果哈希 ID 出现在被扫描的内容中,Git 对象将被保留,但如果哈希 ID 仅出现在提交消息中,则该对象将有资格进行垃圾回收。 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接