丢掉的 commit 什么时候真的从磁盘上消失?这篇造出不可达对象,用改文件时间的办法验证 2 周宽限期,再看暂存区、reflog 和 cruft pack 对它们各自的保护,以及 --prune=now 能删到什么程度。
$ git reset -q --hard HEAD~1; git reflog expire --expire=now --all; git log --oneline
dc41a9c 第一次
$ git fsck --unreachable --no-reflogs 2>&1 | sed "s/ [0-9a-f]\{40\}/ <sha>/"
unreachable blob <sha>
unreachable commit <sha>
unreachable tree <sha>
$ git prune -n | sed "s/^[0-9a-f]\{40\}/<sha>/"
<sha> blob
<sha> commit
<sha> tree
不可达的提交 30f6be9c4f… 此刻是否存在: Truecommit 被 reset 掉、reflog 也清空之后,对象仍留在 .git/objects 里,git fsck --unreachable 能列出它们,git prune -n 能预览会删什么。只有 prune 才真正删除。
A(13 天前): 1e193c82ba… mtime 距今 13.0 天
B(15 天前): 5737b388ab… mtime 距今 15.0 天
$ git prune --expire=2.weeks.ago -n -v | sed "s/^[0-9a-f]\{40\}/<sha>/"
<sha> blob
对照:不带过期时间的 git prune -n 会列出所有不可达对象(包括当前的提交 c2 及它的 tree、blob):
$ git prune -n | wc -l
5
$ git gc -q
git gc 之后(默认 gc.pruneExpire):A 存在 True,B 存在 False,c2 存在 Truegit gc 调用 prune 时带 gc.pruneExpire(默认 2 周),只删修改时间早于它的不可达松散对象。把文件时间改成 13 天前和 15 天前,gc 之后只有后者消失。
$ git prune --expire=now -v | sed "s/^[0-9a-f]\{40\}/<sha>/"
<sha> blob
git prune --expire=now 之后:刚写入的 blob 存在 False;暂存区的 blob 存在 True别的进程可能刚写入一个对象、还没挂上引用。prune --expire=now 会立刻删掉这种对象,但暂存区里的对象算可达,不会被删。宽限期给并发操作留出时间。
$ git reset -q --hard HEAD~1; git reflog | cat | sed -E "s/^([0-9a-f]{7}) /<sha> /"
<sha> HEAD@{0}: reset: moving to HEAD~1
<sha> HEAD@{1}: commit: 第二次
<sha> HEAD@{2}: commit (initial): 第一次
$ git gc -q --prune=now
gc --prune=now 之后,被 reset 丢掉的提交存在: True(reflog 还引用着它)
$ git reflog expire --expire=now --expire-unreachable=now --all; git gc -q --prune=now
清空 reflog 再 gc --prune=now 之后,这个提交存在: Falsereflog 也是引用:reset --hard 之后旧提交仍被 reflog 引用,gc --prune=now 也不会删它。想彻底丢掉要先 reflog expire --expire=now --expire-unreachable=now --all。实验里的提交要用真实时间,否则 reflog 条目一开始就已经过期。
$ git reset -q --hard HEAD~1; git reflog expire --expire=now --all
$ git gc -q; ls .git/objects/pack | sed -E "s/pack-[0-9a-f]{40}/pack-<hash>/"
pack-<hash>.idx
pack-<hash>.pack
pack-<hash>.rev
pack-<hash>.idx
pack-<hash>.mtimes
pack-<hash>.pack
pack-<hash>.rev
gc 之后(默认过期 2 周):不可达的提交存在 True
cruft pack 的 .mtimes 文件: 有
$ git gc -q --prune=now; ls .git/objects/pack | sed -E "s/pack-[0-9a-f]{40}/pack-<hash>/"
pack-<hash>.idx
pack-<hash>.pack
pack-<hash>.rev
gc --prune=now 之后:不可达的提交存在 False已经进了 pack 的不可达对象,git gc 会把它们放进 cruft pack,.mtimes 文件记录每个对象变成不可达的时间,过期后才删除;--prune=now 则立即清掉。