【问题标题】:Git: Deleted current branch and lost reflogGit:删除了当前分支并丢失了 reflog
【发布时间】:2014-11-11 22:23:26
【问题描述】:

我之前遇到了一个不寻常的 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 ref 的历史吗?

【问题讨论】:

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

标签: macos git version-control git-reflog


【解决方案1】:

在正常情况下,HEAD 要么指向一个 SHA1(在这种情况下,它被称为 detached),要么它指向一个现有的分支引用(在这种情况下,指定的分支被认为是经过检查的)出)。

当您签出new-branchHEAD 指向refs/heads/new-branch)然后以某种方式设法删除new-branch 分支时,Git 只是删除了分支的引用文件(.git/refs/heads/new-branch)和分支的引用日志文件( .git/logs/refs/heads/new-branch)。 Git 确实删除HEAD,也不会更新它以指向其他地方(例如new-branch 曾经指向的SHA1),因为不应该需要——你re 不应该能够删除当前分支。所以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 不知道“来自”SHA1 是什么。它没有出错,而是使用全零 SHA1 (0000000000000000000000000000000000000000)。创建 ref 时也会使用全零 SHA1,因此这个最新的 reflog 条目使它看起来像 HEAD 刚刚创建,而实际上它从未被删除。显然git reflog瓷器命令在遇到全零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 中的一个错误。

旁注:当你删除一个 ref 时,相应的日志也会被删除。我认为这是一个错误,因为除非您有日志备份,否则无法完全撤消意外删除 ref。

【讨论】:

  • 很好的解释。我想知道为什么当分支被删除时,git 不会简单地将您置于“分离的 HEAD”状态,而是似乎完全删除了 HEAD...我怀疑当它看到 HEAD ref 指向一个不存在的分支时,它会删除它而不是更新以指向提交。
  • 啊,你是对的,.git/HEAD 仍然在那里指向ref: refs/heads/new-branch。当然,没有.git/refs/heads/new-branch
【解决方案2】:

删除当前分支并丢失 reflog

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

commit 39ee4c6commit 893dbf5commit de92266commit 755b49a(2017 年 2 月 21 日)Kyle Meyer (kyleam)
(由 Junio C Hamano -- gitster -- 合并到 commit c13c783,2017 年 2 月 27 日)

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

重命名当前分支会在当前分支的日志中添加一个事件 以及 HEAD 的日志。
但是,记录的条目不同。
分支日志中的条目代表整个重命名操作(旧哈希和新哈希相同),而 HEAD 日志中的条目代表 仅删除(新的 sha1 为空)。

扩展replace_each_worktree_head_symref(),其唯一来电者是 branch_rename(),采用 reflog 消息参数。
这允许将新 ref 的创建记录在 HEAD 的日志中。
因此,重命名事件由 HEAD 日志中的两个条目(删除和创建条目)表示

有点遗憾的是,现在分支的日志和 HEAD 的日志以不同的方式表示重命名事件。
鉴于重命名操作不是原子的,因此二项形式是一种更 操作的准确表示,如果在删除和创建事件之间发生故障,则对于调试目的更有用

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-06
    • 2023-03-18
    • 1970-01-01
    • 2011-08-28
    • 2018-11-03
    • 1970-01-01
    • 2018-08-10
    • 2017-03-05
    相关资源
    最近更新 更多