我在一个分支工作,需要撤消几个提交。所以我做了
$> git reset --hard bd53134
这产生了预期的效果,除了我现在有一个分离的头:(
$> git status
HEAD detached at bd53134
我怎样才能解决这个问题 ?
看起来你可能还在纠结这个,所以这里有一些背景知识。你在评论中提到你开始了一个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 branch
from ,并在(创建and )master
上做了一些提交,然后可能做了一个and带来了提交和from “远程”。然后你又回到了(因此)。branch
C
D
git checkout master
git pull
E
F
origin
branch
git checkout branch
HEAD=branch
然后,您决定应该重新基于C
和D
之上F
,提供更线性的提交序列:
...- A - B - E - F <-- master, origin/master
\
C' - D' <-- HEAD=branch
(我稍后会展示为什么这些是C'
andD'
而不是C
and D
。)所以你跑到git rebase master
“移动”提交C
并D
到master
.
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
然后,对于每个提交(C
和D
此处),它会获取提交中所做的更改,并尝试将这些相同的更改应用于HEAD
提交。如果一切顺利——通常是这样——它会使用与旧提交相同的消息进行新提交。那是提交C'
:
...- A - B - E - F <-- master, origin/master
\ \
\ C'..... HEAD [detached]
\
C - D <-- ORIG_HEAD, branch
在成功应用C
到F
使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
,或者D
用git 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
由于通常不显示由 指向的提交,因此它看起来像C
andD
已经消失了,并且副本 (C'
和D'
) 是唯一剩下的提交。(一如既往,实际上仍然在那里,C
并且D
会停留一段时间,直到 reflog 条目过期。)
考虑到所有这些,一旦您处于“分离 HEAD”状态,git reset --hard
就不会影响分支名称。它将移动HEAD
并更改工作目录,但HEAD
仍将直接指向提交。因此git reset
,您在变基过程中所做的任何事情充其量都是奇怪的。(如果你继续它,他们会摆弄变基会发生什么,因为继续变基只会不断增加HEAD
当时的任何点。)
在更正常的情况下(当您“在分支上”时),HEAD
具有分支的名称。然后(并且只有那时),git reset
可以并且确实更改分支名称指向的提交。
您如何判断git reset
将采取哪些行动,以及发生了什么?答案是使用git status
. 如果您不确定发生了什么,git status
则可以提供帮助。在过去的几年里,它在实际帮助方面也变得更好了!:-)
如果您在分支上,git reset --hard bd53134
则应该更新该分支,并将您留在该分支上。因此,我怀疑您实际上不在分支上。
如果这是正确的,那么通过返回分支来修复它的一种方法:
git checkout <branch>
然后git reset
再次运行您已经使用过的相同命令。