如何修复Git的分离头(detached head)问题?

2075

我在我的代码库中进行工作时发现一个文件有本地更改。我不想要它们了,所以我删除了这个文件,认为可以检出一个新的副本。我想要做 Git 版本的相当于

svn up .

使用git pull似乎没有起作用。在一些随机搜索后,我找到了一个网站,有人建议这样做:

git checkout HEAD^ src/

(src是包含已删除文件的目录)。

现在我发现自己处于分离的HEAD状态。我不知道这是什么意思。如何撤销?


140
git checkout master 这条命令会让你回到主分支。如果你想清除所有的工作副本更改,你应该运行git reset --hard - Abe Voelker
如果你还没有提交,你可以使用 git checkout -- src/ 命令。 - geekay
1
尝试这个:链接。简而言之,创建临时分支 - 检出临时分支 - 检出主分支 - 删除临时分支。 - fidev
2
如果您的分支名为“main”而不是“master”,请使用“git checkout main”返回该分支上的最新提交。 - neuhaus
显示剩余2条评论
30个回答

2960

分离头状态意味着您不再位于一个分支上,而是已经检出了历史中的单个提交(在此情况下是 HEAD 之前的提交,即 HEAD^)。

如果您想保留与分离头相关的更改

  1. 运行 git branch tmp - 这将把您的更改保存在名为 tmp 的新分支中。
  2. 运行 git checkout master
  3. 如果您想将所做的更改合并到 master 中,请从 master 分支运行 git merge tmp。运行完 git checkout master 后,您应该位于 master 分支上。

如果您想删除与分离头相关的更改

您只需要检出您原来所在的分支,例如:

git checkout master

下次你修改了一个文件并且想要将其恢复到索引中的状态时,不要先删除该文件,只需执行以下操作即可。
git checkout -- path/to/foo

这将把文件 foo 恢复到索引中的状态。


6
这将使文件“foo”恢复到在您对其进行任何更改之前的状态。--> 它将恢复到在“索引”中的状态 - 请编辑。 - Mr_and_Mrs_D
138
为什么会出现这个错误?这是我讨厌 Git 的原因之一——有时候行为完全随机。在使用 Mercurial 时从未遇到过这样的问题。 - Violet Giraffe
138
@VioletGiraffe 当你检出先前的提交时,这不是错误或随机事件 -- 它仅仅是你的代码库进入的状态。"Detached Head" 作为一个警告,提醒你如果打算从此处开始工作,你可能需要创建或指向分支。但如果你只想查看那个标签或提交,处于游离状态并没有问题。请注意保留原意,使翻译更加通俗易懂。 - Neil Neyman
27
如果你已经进入了“分离头指针”的状态,请不要这样做,参考其他答案。如果你这样做了,可以通过检出之前的 git 头指针来恢复,其在Previous HEAD position was 7426948...中被提到。 - KCD
29
@VioletGiraffe: 你对正在发生的事情有一个基于Mercurial的心理模型,但你正在使用Git。如果你不愿意调整自己的心理模型以适应Git的模型,那么事情将继续出现随机的情况。这就像你戴着VR眼镜在外面走来走去,认为自己在飞行飞机,但实际上你只是在穿过马路。你会被汽车撞到。 - iconoclast
显示剩余31条评论

572

如果您更改了不想丢失的文件,可以推送它们。我已经在分离模式下提交了它们,之后您可以切换到一个临时分支,稍后将其集成到主分支中。

git commit -m "....."
git branch my-temporary-work
git checkout master
git merge my-temporary-work

来源:

如何处理在分离的 HEAD 中进行的提交


30
我认为这是首选的解决方案 - 特别是如果您想保留您在检出个别版本时所做的更改。 - adswebwork
11
我同意。所有其他答案都建议恢复到以前的状态,这会导致在分离头状态下进行的本地更改丢失。 - Sk8erPeter
10
为什么不使用 git stash 呢?因为这是我首先想到的方法。创建一个新分支可能有些过分了。 - geekay
3
你也可以使用 git rebase my-temporary-work 并删除分支 git branch -d my-temporary-work,这样看起来就像你一开始就提交到正确的分支一样。请注意,此操作会重写 Git 历史记录,因此请确保在进行此操作之前已经备份代码。 - Zoltán
@geekay,“git stash”听起来像是这种情况下的完美工具。您能否写一篇回答,介绍实现这个步骤的建议步骤? - Zoltán
显示剩余4条评论

208

无临时分支或合并提交的解决方案

如何退出(“修复”)分离 HEAD 状态,当您已经在此模式下更改了某些内容,并且可选择要保存您的更改:

  1. 提交您想要保留的更改。 如果您想接管在分离 HEAD 状态下所做的任何更改,请将它们提交。例如:

    git commit -a -m "您的提交信息"
    
  2. 放弃您不想保留的更改。 硬重置将放弃在分离 HEAD 状态下所做的任何未提交更改:

    git reset --hard
    

    (如果没有这个步骤,第 3 步将失败,并抱怨分离 HEAD 中修改了未提交的文件。)

  3. 检出您的分支。 通过检出您之前工作的分支退出分离 HEAD 状态,例如:

    git checkout master
    
  4. 接管您的提交。 您现在可以通过 cherry-pick 接管在分离 HEAD 状态下所做的提交,如 我回答另一个问题时所示

    git reflog
    git cherry-pick <hash1> <hash2> <hash3> ...
    

1
git reset --hard 正是我所需要的,因为我希望上游成为源,并且本地更改应该被删除。 - Markus Zeller
1
我差点就要给这个解决方案点个踩,直到我看到第四步。真是太惊险了(我一直跟到第三步然后……)。感谢提供这个解决方案。 - fchen
1
这应该是最优先的答案。分支历史中不应包含任何额外的合并信息。 - lord5et

164

HEAD是一个指针,它直接或间接地指向特定的提交:

附加 HEAD表示它附加到某个分支(即它指向一个分支)。
分离 HEAD表示它没有附加到任何分支,即它直接指向某个提交。

enter image description here

换句话说:

  • 如果它直接指向一个提交,那么HEAD是分离的。
  • 如果它间接地指向一个提交(即它指向一个分支,该分支又指向一个提交),那么HEAD是附加的。

为了更好地理解附加/分离 HEAD 的情况,让我们展示导致上述四张图片的步骤。
我们从仓库的相同状态开始(所有象限中的图片都相同):

enter image description here


现在我们想执行git checkout,针对每个图片都有不同的目标(上面的命令被调暗以强调我们只是应用这些命令):

enter image description here


这是执行那些命令{{之后}}的情况:

enter image description here

如您所见,HEAD指向git checkout命令的目标——一个分支(四元组的前三个图像),或者(直接)指向一个提交(四元组的最后一个图像)。工作目录的内容也会改变,以符合适当的提交(快照),即与HEAD直接或间接指向的提交相符。

所以现在我们处于与这个答案开头相同的情况:

enter image description here


2
缺失的是:“当我检出一个数字提交,它也是某个分支的顶部时,会导致分离头还是将使用相关联的分支?” 我的猜测是:“那么不会有分离的头”。 - U. Windl
我知道可以直接检出修订版本而无需检出指向它的分支。从逻辑上讲,两个或更多分支可以指向同一修订版本。如果您按其哈希检出修订版本,则该命令会选择哪个分支? - Mike Rosoft
@Mike,不会选择任何分支,所有分支(作为提交的指针)都将保持不变-您可以在我回答的所有图片中看到它们(棕色框)。只有HEAD将不指向一个分支,而是直接指向提交,因此您将以“Detached HEAD”状态结束-请参见最后一张(右下角)图片。尽管2个分支指向同一个提交,但如果您按哈希选择此提交,则HEAD将指向其中没有这2个分支之一,而是直接指向提交。 - MarianD
@MarianD 我觉得有一点误解 - 我是在解释为什么你不能期望 Git 在你通过哈希值选择一个修订版本时切换到一个分支。 - Mike Rosoft
@Mike,你可以选择检出一个分支 - HEAD 移动到该分支 - 或者检出一个提交 - HEAD 移动到一个提交。这些指针中没有其他更改。对于你的问题“如果你按哈希值检出一个修订版本,那么命令会选择哪个分支?”的答案是:“没有分支。你选择了一个提交,所以命令会选择一个提交,完全忽略分支。 - MarianD
显示剩余2条评论

158

分离头指的是:

  1. 你不再处于一个分支上,
  2. 你已经检出历史记录中的单个提交。

如果你没有进行任何更改: 你可以通过执行以下命令切换到主分支

  git checkout master

如果您有要保留的更改:

在脱离 HEAD 的情况下,提交的工作方式与正常情况相同,只是没有命名分支得到更新。为了让主分支获得您已在脱离 HEAD 中进行的提交更改,请在当前位置创建一个临时分支(这样临时分支将拥有您在脱离 HEAD 中所做的所有提交更改),然后切换到主分支并将临时分支与主分支合并。

git branch  temp
git checkout master
git merge temp

2
完美,然后删除分支temp。 - Davi Menezes
为了从一个分支切换到另一个分支,git现在接受动词switch: https://git-scm.com/docs/git-switch。除了你可能更喜欢的动词之外,checkout的缺点是它用于各种其他目的https://git-scm.com/docs/git-checkout。 - Francis

105

如果您进行了更改,然后意识到您处于分离的状态,您可以执行以下操作:stash -> checkout master -> stash pop:

git stash
git checkout master   # Fix the detached head state
git stash pop         # Or for extra safety use 'stash apply' then later 
                      #   after fixing everything do 'stash drop'

您将保留未提交的更改和正常的“附加”HEAD,就像什么也没有发生一样。


4
已经收藏了这个"坏男孩" - 节省了创建临时分支的时间。非常好用。 - Tim Tyler
3
我经常在检出git子模块后,对其进行更改后陷入分离的HEAD状态。我发现这是最好、最简单的解决方案,以便我可以保留我的更改。 - user5359531
3
如果您已经以分离状态提交了更改,那么这将无法工作。 - Danijel
1
救命稻草!!!之前的解决方案都没用,我还是陷入了同样的循环中。这个方法真的太简单了!!谢谢! - testing_22

84

在我意识到我处于分离头状态并已经进行了一些更改后,我刚刚做了以下操作。

我提交了更改。

$ git commit -m "..."
[detached HEAD 1fe56ad] ...

我记得这个提交的哈希(1fe56ad)。然后我切换到了我应该在的分支。

$ git checkout master
Switched to branch 'master'

最后,我将提交的更改应用到了分支上。

$ git cherry-pick 1fe56ad
[master 0b05f1e] ...

我认为这比创建临时分支要容易一些。


3
这应该是答案。它可以找回您被删除的文件。 - BlindWanderer
2
是的,这确实是最简单的事情 - 简单到下次发生时无需搜索网络即可记住。提交,记录哈希值,返回您想要提交的分支,并使用 git cherry-pick <hash> - Mason
谢谢解决方案。这真的帮了我很多。我想补充一下,我还需要执行“git push origin master”命令,这样我的主分支和远程主分支才能指向相同的提交。 - turnip424
1
这本质上是tanius的回答(发布于一年前)。 - Peter Mortensen
感谢这个愉快的挑选,撤销了最后一个分离头更改。 - Abdullah Khan
这也可以避免您必须进行额外的“合并”提交来清理,如果您选择使用临时分支路线。 - RGD2

47

当你在 git 中检出一个特定的提交时,你会进入到一个 分离头指针 的状态...也就是说,你的工作副本不再反映某个命名引用(例如 "master")的状态。这对于检查存储库之前的状态很有用,但如果你想要还原更改,则不是你想要的。

如果你对特定文件进行了更改并且只想要丢弃它们,可以使用 checkout 命令,方法如下:

git checkout myfile

这将丢弃任何未提交的更改并将文件还原为其在当前分支头部状态下的状态。如果您想放弃已经提交的更改,您可能需要使用reset命令。例如,这将重置存储库以回到上一个提交的状态,丢弃任何随后的更改:

git reset --hard HEAD^

如果您要与其他人共享代码库,git reset可能会造成破坏(因为它会擦除部分代码库历史记录)。如果您已经与其他人分享了更改内容,通常需要考虑使用git revert,它会生成一个“反提交”--也就是创建一个新的提交,用于“撤消”相关更改。

《Git Book》提供了更多详细信息。


2
正如我在@ralphtheninja的回答中所说,git checkout path/to/foo可能会与git checkout some-branch冲突,因此最好使用git checkout -- path/to/foo来避免这些冲突。 - Diego

33

既然"detached head state"让你处于临时分支上,只需使用git checkout -命令,它将把你切换到最后一个你所在的分支。


2
小心,当你处于分离头状态时,你将失去任何提交。 - Ajak6
2
@Ajak6 你并没有真正丢失这些提交。它们仍然可以通过 git reflog 访问,并且可以被接管到一个新分支中,或者通过 git cherry-pick 接入到一个现有分支中。参见这个问题 - tanius
1
然而,经过一段时间后,任何未被分支或标签指向(或不是当前工作版本),或不是这样的修订版本的祖先的修订版本都有可能被永久删除。 - Mike Rosoft
@tanius。你真是个救星。你刚刚帮我们省了很多工作。 - kv1dr

17

你可能执行了 git reset --hard origin/your-branch

尝试只使用 git checkout your-branch


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