在正常情况下,
HEAD
要么指向一个 SHA1(在这种情况下它被称为“分离的”),要么指向一个现有的分支引用(在这种情况下,命名的分支被认为是已经检出的)。
当你检出
new-branch
(
HEAD
指向
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 reflog
输出中缺失的条目仍然存在于.git/logs/HEAD
中。我猜测git reflog
只打印从.git/logs/HEAD
中以0000000000000000000000000000000000000000
开头的最后一个条目开始的条目。 - jub0bs.git/logs/HEAD
中出现的0000000000000000000000000000000000000000
的实例,用其他SHA代替。然后,git reflog
打印.git/logs/HEAD
中的所有条目。正如我之前猜测的那样:git reflog
仅从以“零SHA”开头的第一个条目开始打印。我仍然需要进一步研究,但我认为,每当您处于“错误的默认版本'HEAD'”状态时,相应的reflog条目以“零SHA”开头。如果你看到你所在的分支,你肯定会陷入“bad default revision 'HEAD'”领域... - jub0bs