我之前遇到了一个不寻常的 git 问题,我已经解决了,但我仍然很好奇为什么会发生这种情况。
当我不小心删除了当前正在处理的分支时,就出现了问题。通常 git 不允许这样做,但由于 OSX 上不区分大小写,我让自己陷入了我认为我有两个分支的情况,一个名为 feature/ONE
,另一个名为 feature/one
。认为这是两个独立的分支(来自大多数 linux/区分大小写的背景(,并且我正在研究功能/一个,我试图使用 git branch -D
删除功能/一个。
很快注意到我做了什么,试图从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 引用的历史吗?
在正常情况下,HEAD
指向 SHA1(在这种情况下称为分离(,要么指向现有的分支引用(在这种情况下,命名分支被视为已签出(。
当你签出new-branch
(HEAD
指向refs/heads/new-branch
(,然后以某种方式设法删除new-branch
分支时,Git 只是删除分支的 ref 文件 ( .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 文件。 每个引用日志条目都包含一个"from"和"to"SHA1。 当你从不存在的new-branch
分支切换到master
时,Git 不知道"来自"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 2.13(2017 年第 2 季度(中得到缓解。
参见 Kyle Meyer 的 commit 39ee4c6、commit 893dbf5、commit de92266、commit 755b49a ( kyleam
( 提交 755b49a (2017 年 2 月 21 日(。
(由Junio C Hamano -- gitster
-- 在提交c13c783中合并,2017年2月27日(
重命名当前分支
branch
:在HEAD的日志中记录重命名分支的创建会将事件添加到当前分支的日志中 和 HEAD 的日志。
但是,记录的条目不同。
分支日志中的条目表示整个重命名操作(旧哈希和新哈希相同(,而 HEAD 日志中的条目表示 仅删除(新的 SHA1 为空(。扩展
replace_each_worktree_head_symref()
,其唯一的调用方是branch_rename()
,取一个 reflog 消息参数。
这允许在 HEAD 的日志中记录新引用的创建。
因此,重命名事件由 HEAD 日志中的两个条目(删除条目和创建条目(表示。有点不幸的是,分支的日志和 HEAD 的日志现在以不同的方式表示重命名事件。
鉴于重命名操作不是原子的,则两个条目形式是一个 准确表示操作,如果删除和创建事件之间发生故障,则对于调试目的更有用。将分支的日志移动到双条目表单是有意义的,但这将涉及更改重命名的执行方式以及处理更新标志和 reflog 以进行删除的方式,因此可能不值得付出努力。