git 究竟什么时候修剪对象:为什么"git gc"不删除提交?



我正在学习一门git课程,我想提到的是,在运行git gc之前,丢失的refs并不会真正丢失。但经过验证,我发现事实并非如此。即使在运行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何时删除对象?

对于要修剪的对象,它必须满足两个标准。一个是与日期/时间相关的:它必须是在1足够长的时间前创建的,才适合收集。"足够长的时间"部分是您用--prune=all设置的:您正在覆盖正常的"至少两周前"设置。

第二个标准是你的实验哪里出了问题。要进行修剪,对象必须不可访问。正如twalberg在一条评论中指出的那样,你表面上放弃的每一个提交(以及相应的树和Blob)实际上都是通过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-y文件系统根本不存储文件创建("出生")时间:stat结构的st_ctime字段是索引节点更改时间,而不是创建时间。如果有创建时间,则在st_birthtimest_birthtimespec3但是,每个Git对象都是只读的,所以文件的创建时间也是它的修改时间。因此,始终可用的st_mtime提供了对象的创建时间。

2git gc文档中描述了确切的规则,但我认为默认情况下,30天用于不可达的提交,90天用于可达的提交是一个不错的总结。这里reach的定义是不寻常的:它意味着可以从这个reflog保存旧值的引用的当前值访问也就是说,如果我们查看master的reflog,我们会找到master标识的提交(例如,1234567),然后查看master(例如,master@{27})的每个reflog条目是否可以从该特定提交访问(再次为1234567)。

3这个特殊的名称混淆是由POSIX标准化人员给您带来的。:-)st_birthtimespec字段是一个struct timespec,它记录秒和纳秒。

最新更新