git 重置后,未删除无法访问的提交

After a git reset, unreachable commit not removed

我有一个有几个提交的小回购:

* a0fc4f8 (HEAD -> testbranch) added file.txt  
* e6e6a8b (master) hello world now  
* f308f53 Made it echo  
* f705657 Added hello  
* 08a2de3 (tag: initial) initial  

另外:

$ git status  
On branch testbranch  
nothing to commit, working directory clean  

我无法理解以下行为。在这种状态下,我 运行: $ git reset initial
我现在看到:

* e6e6a8b (master) hello world now  
* f308f53 Made it echo  
* f705657 Added hello  
* 08a2de3 (HEAD -> testbranch, tag: initial) initial  

如我所料:提交 a0fc4f8 将被删除,因为它无法访问。
发生了什么:
1)做 git show a0fc4f8 仍然显示提交
2) 执行 git status 显示提交 a0fc4f8 添加的 file.txt 未跟踪,提交 f705657 添加的文件 hello 也显示为未跟踪。
3) 运行 git gcgit gc --prune=all 不会删除 a0fc4f8 尽管它不再可达并且没有与之相关联的 name/tag。
为什么会发生这些?

更新:

$ git fsck  
Checking object directories: 100% (256/256), done.  
Checking objects: 100% (15/15), done.    

更新二:

$ git log --all --decorate --graph --oneline  
* e6e6a8b (master) hello world now  
* f308f53 Made it echo  
* f705657 Added hello  
* 08a2de3 (HEAD -> testbranch, tag: initial) initial  

$ git gc --force  
Counting objects: 15, done.  
Delta compression using up to 4 threads.  
Compressing objects: 100% (8/8), done.  
Writing objects: 100% (15/15), done.   
Total 15 (delta 1), reused 15 (delta 1)   

$ git log --all --decorate --graph --oneline  
* e6e6a8b (master) hello world now  
* f308f53 Made it echo  
* f705657 Added hello  
* 08a2de3 (HEAD -> testbranch, tag: initial) initial  

$ git show a0fc4f8 仍然显示提交

更新 3:

$ git reflog testbranch  
08a2de3 testbranch@{0}: reset: moving to initial  
a0fc4f8 testbranch@{1}: commit: added file.txt  
e6e6a8b testbranch@{2}: branch: Created from HEAD  

1) Doing git show a0fc4f8 still shows the commit

这是设计使然。由于以下几个原因,无法立即删除无法访问的对象:

  • 也许你 运行 错误地执行了最后一个命令(或向其提供了错误的参数),你意识到错误并想返回到之前的状态;
  • 与完成操作所需的工作量相比,删除无法访问的对象(节省一些磁盘空间 space)的收益太小了。

修剪无法访问的对象是不时自动执行的。它也由一些 git 命令执行(fetchpush 是其中的一些)。

2) Doing git status shows the file.txt that was added by commit a0fc4f8 as untracked and file hello that was added by commit f705657 also shows up as untracked.

您 运行 git reset 没有指定 模式 。默认模式是 --mixed,这意味着:

  • b运行ch 被移动到命令中指定的提交(在本例中为 initial);
  • 索引被重置以匹配 b运行ch 指向的新提交;
  • 未修改工作树。

这解释了为什么文件在目录中(第三个项目符号)以及为什么它们未被跟踪(第二个项目符号;索引与 initial 提交匹配,但这些文件在它被创建时甚至不存在已创建)。

3) Running git gc or git gc --prune=all does not delete a0fc4f8 although it is not reachable anymore and has no name/tag associated with it.

git gc 还会检查 b运行ch reflogs 中的引用。如果你的 testbranch b运行ch 有 reflog enabled then the most recent entry in the reflog points to commit a0fc4f8 (this is where the testbranch branch was before you ran git reset). You can check if the reflog is enabled for branch testbranch by running git reflog testbranch. If it prints something you'll find the commit a0fc4f8 on the second line, at position testbranch@{1}. The notation name@{n} 意味着 b运行ch nth 之前的值 name(它指向的提交,n 移动到过去)。

你可以找到更多关于git gc works in the documentation的方法。

Notes 部分显示:

git gc tries very hard to be safe about the garbage it collects. In particular, it will keep not only objects referenced by your current set of branches and tags, but also objects referenced by the index, remote-tracking branches, refs saved by git filter-branch in refs/original/, or reflogs (which may reference commits in branches that were later amended or rewound).

If you are expecting some objects to be collected and they aren’t, check all of those locations and decide whether it makes sense in your case to remove those references.