为什么在签出最近的提交后我有一个分离的 HEAD?

Why do I have a detached HEAD after checking out the most recent commit?

最近,在 git 存储库中工作时,我想查看旧提交 (68cce45) 的代码,所以我做了

git checkout 68cce45

查看更改后,我想 return 到存储库的当前版本并继续工作。因为 2bcfd11 是最近的提交,所以我做了

git checkout 2bcfd11

然后我做了一些修改并做了

git add *

然后

git status

这给了我警告:HEAD detached at 2bcfd11

我很困惑。如果我签出的最后一次提交是在几个版本之前,我可以理解为什么我会在 "detached HEAD state" 中。但是由于我检出的最后一次提交是存储库的最新版本,那么为什么我会处于分离的 HEAD 状态? HEAD 现在不是指向版本库的 "top" 吗?

why would I be in a detached HEAD state?

因为您检出的是提交而不是分支。签出任何提交 - 您处于分离的 HEAD 状态。

Isn't HEAD now pointing to the "top" of the repository?

git真不知道是不是顶了。您必须通过查看分支向 git 解释这一点:

git checkout master

现在 git 知道它是已知分支的负责人。分离式 HEAD 问题结束。

HEAD 是您当前签出的任何提交。可能有也可能没有指向 HEAD 或不指向 HEAD 的分支(可能像 master)。当您执行 git checkout 2bcfd11 时,您更新了您的 HEAD,但保持分离 - 也就是说,您没有向 git 表明您想要与之关联的一些符号名称。如果你有一个指向 2bcfd11 的分支,你可以 git checkout 该分支并且没问题。如果您不这样做,git branch 将让您在 2bcfd11 处创建一个分支,名称任意。

稍微扩展一下:在Git、HEAD中全部大写,1是一个非常特殊名称。 HEAD 可以 附加 (到分支名称),或 分离 。在这两种情况下,Git 将能够告诉您您正在使用哪个 commit

git rev-parse HEAD

将打印一些哈希 ID。但只有当 HEAD 附加到分支名称时,才能 Git 告诉您正在使用哪个 分支名称 :

git rev-parse --symbolic-full-name HEAD
git symbolic-ref HEAD

两者都会为您提供 当前分支的名称 (前缀为 refs/heads/)(如果您在分支上)。如果您处于分离的 HEAD 模式,前者只会打印 HEAD 而后者会产生错误:

$ git checkout --detach master
HEAD is now at 7c20df84bd Git 2.23-rc1
Your branch is up to date with 'origin/master'.
$ git rev-parse --symbolic-full-name HEAD
HEAD
$ git symbolic-ref HEAD
fatal: ref HEAD is not a symbolic ref

许多形式的 git checkout分离 HEAD。一些表格将 附加 它。使用 git checkout <em>branch-name</em> 附加它,而如上所示,您可以添加 --detach 以确保它变得或保持分离。

使用原始哈希 ID,例如 7c20df84bd 总是会导致分离 HEAD,即使有一个或多个分支名称标识这一特定提交。

请注意,您可以有任意多个分支名称,所有 标识相同 提交:

$ for i in m1 m2 m3; do git branch $i master; done
$ git checkout m1
Switched to branch 'm1'
$ git rev-parse HEAD
7c20df84bd21ec0215358381844274fa10515017
$ git checkout m2
Switched to branch 'm2'
$ git rev-parse HEAD
7c20df84bd21ec0215358381844274fa10515017

如果我明确查看 7c20df84bd21ec0215358381844274fa10515017,您会喜欢 m1m2m3master 这四个名字中的哪一个Git要用吗?但是它使用了其中的 none 个:如果你想让它使用一个名字,你必须自己提供一个名字:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

之后我们可以删除额外的名称,这样提交 7c20df84bd21ec0215358381844274fa10515017 仅在 上,并且在 master 的顶端,而不是被同时在四个分支的顶端。

$ for i in m1 m2 m3; do git branch -d $i; done
Deleted branch m1 (was 7c20df84bd).
Deleted branch m2 (was 7c20df84bd).
Deleted branch m3 (was 7c20df84bd).

记住,HEAD两个函数。它找到当前的 branch (name),或者如果 HEAD 被分离则找不到;它会找到当前的 commit.2 你从 Git 得到的答案取决于你问的问题:你想知道吗分支名称,或者您想知道当前提交哈希 ID?


1在某些系统上,您有时可以将其拼写为小写 head,并获得相同的效果。然而,这在添加的工作树中开始神秘地失败。最好坚持使用全部大写 HEAD,或者如果打字太麻烦,单个字符 @ 具有相同的特殊含义。

2这个也有可能会失败,只是在特殊情况下。您在一个新的、完全空的存储库中处于这种状态,其中您当前的分支 namemaster,但分支 master 本身尚不存在。这是因为分支名称 必须 包含一些现有的有效提交对象的哈希 ID。在一个新的、完全空的存储库中,根本没有提交。因此不允许存在任何分支名称。尽管如此,HEAD 附加到名称 master.

当你处于这种状态时——Git 的某些部分将其称为 孤儿分支 ,如 git checkout --orphan,而其他部分将其称为unborn branch,正如 git status 所说的那样——您所做的 next 提交导致分支名称出现。该名称已经存在于某个地方——具体来说,存储在 HEAD 中——但是提交将名称创建为有效的 branch 名称,首先创建一个有效的提交,其哈希 ID 名称可以坚持。

使用 Git 2.23(昨天发布,2019 年 8 月),执行

git restore -s <SHA1> -- .

那么您将不会有分离的 HEAD(例如,您保留在当前分支上,master,但内容不同)。

完成后,您可以使用以下方法恢复正确的工作树:

git restore -s master -- .