Git:删除当前分支并丢失引用日志

7

我之前遇到了一个不寻常的git问题,虽然现在已经解决了,但我仍然很好奇为什么会发生这种情况。

问题出现在我意外删除了我正在工作的分支时。通常情况下,git不允许这样做,但由于OSX上的大小写不敏感,我陷入了一种情况,认为我有两个分支,一个名为feature/ONE,另一个名为feature/one。认为这是两个不同的分支(来自大多数Linux /大小写敏感的背景)并且我正在使用feature/ONE,我尝试使用git branch -D删除feature/one。

我很快注意到我所做的事情,并尝试从git reflog中检索我的丢失的工作,但给我了错误fatal: bad default revision 'HEAD'。我尝试使用git checkout -f develop回到正常状态,这起作用了。然而,在此之后查看git reflog时,它只有一个条目,说明checkout: moving from feature/ONE to develop。之前的操作都没有出现在日志中。

我编写了一些步骤来复制这种情况(可能仅适用于大小写不敏感的文件系统):

mkdir test
cd test
git init
echo 'hi' > file1
git add file1
git commit -m 'test commit 1'
git checkout -b new-branch
echo 'test2' > file2
git add file2
git commit -m 'test commit 2'
git branch -D NEW-branch
git checkout -f master
git reflog

我通过检查git-fsck找到了我的丢失提交,但是我的问题是:

为什么这一系列的操作会破坏reflog?即使分支已被删除,reflog难道不应该仍然知道HEAD引用的历史记录吗?


+1 我希望所有的问题都像你的一样有一个可重现的例子。 - jub0bs
我还没有一个令人满意的答案,但请注意,git reflog 输出中缺失的条目仍然存在于 .git/logs/HEAD 中。我猜测 git reflog 只打印从 .git/logs/HEAD 中以 0000000000000000000000000000000000000000 开头的最后一个条目开始的条目。 - jub0bs
有趣的是,reflog除了显示日志之外,肯定还有一些逻辑... - user2221343
2
我刚刚运行了一个简单的测试:我替换了所有(除了第一个)在.git/logs/HEAD中出现的 0000000000000000000000000000000000000000的实例,用其他SHA代替。然后,git reflog打印.git/logs/HEAD中的所有条目。正如我之前猜测的那样:git reflog仅从以“零SHA”开头的第一个条目开始打印。我仍然需要进一步研究,但我认为,每当您处于“错误的默认版本'HEAD'”状态时,相应的reflog条目以“零SHA”开头。如果你看到你所在的分支,你肯定会陷入“bad default revision 'HEAD'”领域... - jub0bs
2
在Linux上,似乎可以通过将“git branch -D NEW-branch”替换为“git update-ref -d refs/heads/new-branch”来模拟该行为。 - Andrew C
2个回答

8
在正常情况下,HEAD 要么指向一个 SHA1(在这种情况下它被称为“分离的”),要么指向一个现有的分支引用(在这种情况下,命名的分支被认为是已经检出的)。
当你检出 new-branchHEAD 指向 refs/heads/new-branch), 然后成功地删除 new-branch 分支,Git 会简单地删除该分支的引用文件(.git/refs/heads/new-branch)和该分支的 reflog 文件(.git/logs/refs/heads/new-branch)。Git 不会删除 HEAD,也不会将其更新为指向其他位置(例如 new-branch 曾经指向的 SHA1), 因为不应该需要这样做--你不应该能够删除当前分支。因此,HEAD 仍然引用已删除的分支,这意味着 HEAD 不再指向有效的提交。
如果你接下来执行 git checkout -f master,Git 将更新 HEAD 指向 refs/heads/master,在 HEAD 的 reflog 文件中添加一个新条目(.git/logs/HEAD),文件被检出,并更新索引。所有这些都是正常的--当你检出另一个分支时,Git 总是会这样做。
你遇到的问题源于 reflog 文件如何更新以及 git reflog 如何处理更新后的 reflog 文件。每个 reflog 条目都包含一个“from”和“to” SHA1。当你从不存在的 new-branch 分支切换到 master 时,Git 不知道“from”的 SHA1 是什么。而不是出现错误,它使用全零 SHA1(0000000000000000000000000000000000000000)。当创建 ref 时也使用全零 SHA1,因此,这个最近的 reflog 条目使得它看起来像是刚刚创建了 HEAD,而实际上它从未被删除。显然,尽管还有更多条目,git reflog porcelain 命令在遇到全零 SHA1 时停止遍历 reflog,这就是为什么 git reflog 只打印一个条目。
下面是示例:
$ git init test
Initialized empty Git repository in /home/example/test/.git/
$ cd test
$ echo hi >file1
$ git add file1
$ git commit -m "test commit 1"
[master (root-commit) 3c79ff8] test commit 1
 1 file changed, 1 insertion(+)
 create mode 100644 file1
$ git checkout -b new-branch
Switched to a new branch 'new-branch'
$ echo test2 >file2
$ git add file2
$ git commit -m "test commit 2"
[new-branch f828d50] test commit 2
 1 file changed, 1 insertion(+)
 create mode 100644 file2
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1
$ git update-ref -d refs/heads/new-branch
$ cat .git/HEAD
ref: refs/heads/new-branch
$ cat .git/refs/heads/new-branch
cat: .git/refs/heads/new-branch: No such file or directory
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400        commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400        checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email@example.com> 1411018898 -0400        commit: test commit 2
$ git checkout -f master
Switched to branch 'master'
$ cat .git/logs/HEAD
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400        commit (initial): test commit 1
3c79ff8fc5a55d7c143765b7f749db4dd8526266 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400        checkout: moving from master to new-branch
3c79ff8fc5a55d7c143765b7f749db4dd8526266 f828d50ce633918f2fcaaaad5a52ac1ffa1c81b1 Your Name <email@example.com> 1411018898 -0400        commit: test commit 2
0000000000000000000000000000000000000000 3c79ff8fc5a55d7c143765b7f749db4dd8526266 Your Name <email@example.com> 1411018898 -0400        checkout: moving from new-branch to master
$ git reflog
3c79ff8 HEAD@{0}: checkout: moving from new-branch to master

正如您所看到的那样,HEAD的reflog仍然保留着所有旧记录 - 只是在git reflog中不显示。我认为这是Git的一个缺陷。

顺便说一下:当您删除参照时,相应的日志也会被删除。我认为这是一个缺陷,因为没有办法完全撤销意外的参照删除,除非您有日志的备份。


非常好的解释。我想知道为什么当分支被删除时,git不会简单地将您置于“分离的HEAD”状态,而似乎完全删除HEAD...我怀疑当它看到HEAD引用指向不存在的分支时,它会将其删除而不是更新以指向提交。 - user2221343
啊,你说得对,.git/HEAD仍然指向ref: refs/heads/new-branch。当然,没有.git/refs/heads/new-branch - user2221343

1

删除了当前分支并且丢失了引用日志

两年后,这个问题应该在Git 2.13(2017年第二季度)中得到缓解。

查看 提交 39ee4c6, 提交 893dbf5, 提交 de92266, 提交 755b49a (2017年2月21日) 由 Kyle Meyer (kyleam) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 c13c783, 2017年2月27日)

branch: 记录重命名分支在HEAD日志中的创建

重命名当前分支会向当前分支的日志和HEAD的日志添加一个事件。
但是,这两个日志条目不同。
分支日志中的条目表示整个重命名操作(旧哈希和新哈希相同),而HEAD日志中的条目仅表示删除操作(新sha1为null)。

扩展replace_each_worktree_head_symref(),其唯一调用者是branch_rename(),以接受一个reflog消息参数。
这允许记录新引用的创建到HEAD的日志中。
结果,在HEAD的日志中,重命名事件由两个条目(一个删除条目和一个创建条目)表示

很不幸,现在分支日志和HEAD日志以不同的方式表示重命名事件。
鉴于重命名操作不是原子性的,两个条目形式更准确地表示操作,并且如果在删除和创建事件之间发生故障,则更有利于调试

将分支日志移动到两个条目形式可能是有意义的,但这将涉及更改如何执行重命名以及如何处理删除的更新标志和reflogs,因此可能不值得努力。


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