2

我在一个分支工作,需要撤消几个提交。所以我做了

$> git reset --hard bd53134

这产生了预期的效果,除了我现在有一个分离的头:(

$> git status
HEAD detached at bd53134

我怎样才能解决这个问题 ?

4

2 回答 2

12

看起来你可能还在纠结这个,所以这里有一些背景知识。你在评论中提到你开始了一个git rebase并且git rebase --continue总是抱怨。(这真的应该是原始问题的一部分。)

首先注意:HEAD可以是正常的(“不分离”?)或“分离”。“正常”HEAD只包含一个分支名称。如果你“在分支上master”,HEAD就说“在分支上”。一个“分离的 HEAD”,听起来像是法国大革命中的东西,而不是原始的 SHA-1 提交 ID。我喜欢在下图中将附加的 HEAD 写为HEAD=branch,然后显示分支指向的位置,使用箭头指向一个特定的提交。

在你开始一个 rebase 之前,你有一个可能看起来像这样的提交图:

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

在这种情况下,我假设您做了一个git checkout -b branchfrom ,并在(创建and )master上做了一些提交,然后可能做了一个and带来了提交和from “远程”。然后你又回到了(因此)。branchCDgit checkout mastergit pullEForiginbranchgit checkout branchHEAD=branch

然后,您决定应该重新基于CD之上F,提供更线性的提交序列:

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

(我稍后会展示为什么这些是C'andD'而不是Cand D。)所以你跑到git rebase master“移动”提交CDmaster.

Rebase 实际上并不移动提交。它所做的是复制一些现有的提交,制作“做同样的事情”并且“一样好”(我们希望!)的新提交。C所以,rebase 会一直D存在。它使用一个特殊的标签 ,ORIG_HEAD来跟踪提交D(以及一堆额外的.git/rebase-apply/文件来跟踪整个 rebase 操作的所有进度——这些文件实际上是 git 知道 rebase 正在“进行中”的方式)。

Rebase 通过添加这个ORIG_HEAD和“分离 HEAD”来启动这个过程。它将“分离的 HEAD”设置为直接指向rebase 的目标(在本例中为 commit 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

在成功应用CF使C'脱落后F,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,或者
  • 选择Dgit rebase --skip, 或
  • 退出整个事情git rebase --abort

选择最后一个选项告诉 rebase 停止 rebase 尝试,HEAD恢复原来的状态,并删除“rebase in progress”状态/跟踪文件。这也会丢弃整个新提交链(但尚未标记为分支)。(从技术上讲,它们仍然在那里,在 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由于通常不显示由 指向的提交,因此它看起来像CandD已经消失了,并且副本 (C'D') 是唯一剩下的提交。(一如既往,实际上仍然在那里,C并且D会停留一段时间,直到 reflog 条目过期。)


考虑到所有这些,一旦您处于“分离 HEAD”状态,git reset --hard就不会影响分支名称。它将移动HEAD并更改工作目录,但HEAD仍将直接指向提交。因此git reset,您在变基过程中所做的任何事情充其量都是奇怪的。(如果你继续它,他们会摆弄变基会发生什么,因为继续变基只会不断增加HEAD当时的任何点。)

在更正常的情况下(当您“在分支上”时),HEAD具有分支的名称。然后(并且只有那时),git reset可以并且确实更改分支名称指向的提交。

您如何判断git reset将采取哪些行动,以及发生了什么?答案是使用git status. 如果您不确定发生了什么,git status则可以提供帮助。在过去的几年里,它在实际帮助方面也变得更好了!:-)

于 2013-11-12T12:04:57.263 回答
9

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

如果这是正确的,那么通过返回分支来修复它的一种方法:

git checkout <branch>

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

于 2013-11-12T08:17:02.407 回答