git:'git reset --hard bd53134'后脱离HEAD

4

我在一个分支上工作,需要撤销几个提交。所以我执行了以下操作:

$> git reset --hard bd53134

这产生了预期的效果,但我现在有一个分离的头 :(
$> git status
HEAD detached at bd53134

我该如何修复这个问题?
2个回答

12

看起来您可能还在摸索中,这里给出一些背景。您在评论中提到,您启动了一个git rebase并卡住了,git rebase --continue始终在抱怨。(这真的应该是原始问题的一部分)。

首先要注意的是:HEAD可以是正常的(“未分离”?)或“分离”的。一个“正常的”HEAD仅由一个分支名称组成。如果您“在分支master上”,HEAD只说“在分支master上”。一个“分离的HEAD”,听起来像法国大革命中的某件事,代替它的是原始的SHA-1提交ID。在下面的图示中,我喜欢将已连接的HEAD写成HEAD=branch,然后使用箭头指向特定提交的分支点。

在开始rebase之前,您可能具有类似于以下内容的提交图:

...- A - B - E - F         <-- master, origin/master
           \
             C - D         <-- HEAD=branch

在这种情况下,我假设您从master分支执行了 git checkout -b branch 命令,并在branch上进行了几次提交(创建了 CD),然后可能执行了 git checkout mastergit pull 命令,从 "remoteorigin" 拉取了提交记录 EF。接着,您使用 git checkout branch 命令返回到了 branch 分支(因此HEAD=branch)。

然后,您决定将CD两个提交记录在F之上重新应用,以获得更线性的提交序列:

...- A - B - E - F         <-- master, origin/master
                  \
                   C' - D' <-- HEAD=branch

(我马上会展示为什么它们是C'D',而不是CD。) 所以你运行git rebase master去“移动”提交记录CDmaster的顶部。

Rebase实际上并没有移动提交记录。它所做的是复制一些已经存在的提交记录,创建新的提交记录来“做同样的事情”并且是“同样好的”(我们希望如此!)。所以,rebase保留了提交记录CD。它使用一个特殊的标签ORIG_HEAD来跟踪提交记录D(还有一堆额外的.git/rebase-apply/文件来跟踪整个rebase操作的所有进度——实际上这些文件是git知道rebase正在“进行中”的方法)。

Rebase通过添加这个ORIG_HEAD和“分离HEAD”来开始这个过程。它将“分离的HEAD”设置为直接指向rebase目标(在本例中是提交记录F)。(通过探索,似乎文档稍微说错了关于重置分支的部分。但这在较早版本的git中可能会有所不同;我认为文档在某个时刻是准确的。)因此:

...- A - B - E - F         <-- master, origin/master
          \       \
           \       \...... HEAD [detached]
            \
             C - D         <-- ORIG_HEAD, branch

然后,针对每个提交(这里是CD),获取所做的更改并尝试将这些更改应用于HEAD提交。 如果一切顺利——通常如此——它将创建一个与旧提交相同消息的新提交。 这就是提交C'

...- A - B - E - F         <-- master, origin/master
          \       \
           \       C'..... HEAD [detached]
            \
             C - D         <-- ORIG_HEAD, branch

成功将C应用于F,生成新的代码 C' 之后,rebase 命令尝试将代码 D 应用于最新的(但是仍然是分离的)HEAD,即 C'。但是这一次出现了问题:补丁无法应用:

CONFLICT ...
Failed to merge in the changes.
Patch failed at ...
The copy of the patch that failed is found in:
    ...

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

此时,您的提交图看起来像我上面画的那个(有点凌乱)。

如rebase所述,您可以:

  • 手动解决问题并使用git rebase --continue,或者
  • 选择使用git rebase --skip删除提交(在本例中为D),或者
  • 使用git rebase --abort退出整个过程。

选择最后一种选项会告诉rebase停止rebase尝试,将HEAD恢复到原来的位置,并删除“正在进行的rebase”状态/跟踪文件。 这也放弃了所有新的链式提交(但尚未用分支标记)。 (从技术上讲,它们仍然在reflog中。通常情况下,您看不到它们。)

进行第一个选项(“手动解决”),或选择中间选项(“跳过”)会让rebase继续向前。 假设您解决了问题并使用了git rebase --continue。 一旦rebase用完提交,并且D是最后一个提交,它将将分支名称移动到最终提交,并再次将HEAD设置为分支名称。 所以在这种情况下,您会得到这个:

...- A - B - E - F         <-- master, origin/master
          \       \
           \       C' - D' <-- HEAD=branch
            \
             C - D         <-- ORIG_HEAD

由于 ORIG_HEAD 指向的提交通常不会显示出来,因此看起来像 CD 已经不存在了,而副本 (C'D') 是唯一留下的提交。(但是需要注意的是,CD 实际上仍然存在,并且将保留一段时间,直到 reflog 条目过期。)


考虑到一切情况,一旦进入“分离头指针”状态,git reset --hard 就无法影响到分支名称。它将移动 HEAD 并更改工作目录,但 HEAD 仍然直接指向提交。 因此,在重新设置基础过程中进行任何 git reset 操作都不太正常。(如果继续执行重新设置,它们将干扰重新设置的操作,因为继续重新设置只会添加到当前 HEAD 指向的位置。)

在更正常的情况下(当你在“分支上”时),HEAD 当前指向的就是这个分支的名称。然后(仅在此情况下),git reset 可以并确实更改分支名称所指向的提交。

如何确定 git reset 将采取哪些操作以及发生了什么?答案是使用 git status。如果不确定正在发生什么,git status可以提供帮助。它在过去几年中变得更加有用! :-)


1
非常棒的回答。Git的答案如果有这种背景解释,而不是简单的回答,那么会更好。 - cosjav

11

如果您在一个分支上,git reset --hard bd53134 应该会更新该分支,并让您留在该分支上。因此,我怀疑您实际上并不在一个分支上。

如果是这样的话,一种解决方法是返回到该分支:

git checkout <branch>

然后再次运行您已经使用过的相同的git reset命令。


可能是这样。在重置之前,我尝试了rebase操作。我的问题是,在每次执行'git rebase --continue'后,它都会显示相同的冲突文件。这可能是问题的原因吗? - Jeanluca Scaljeri
不幸的是,如果我再次检出该分支,它会缺少一些提交记录,所以我想在变基之前没有推送所有提交记录 :( 有没有办法告诉Git我的当前状态是HEAD? - Jeanluca Scaljeri
2
当你处于rebase的过程中,HEAD总是分离状态。使用git rebase --abort来终止rebase操作,保持原始分支不变(git会将HEAD放回未经rebase的分支末端)。 - torek
你当前的状态始终是HEAD。当你分离时,你可能已经提交了。你可以使用git reflog来查找丢失的提交。检出你想要工作的分支,并在那里挑选它们(或重置分支指针)。 - iveqy
奇怪的是,我执行了中止操作,但这并没有解决问题。除了针对分离头部的变基之外,还有其他情况吗? - Jeanluca Scaljeri
显示剩余2条评论

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