为什么使用显式refs / heads / branch的git checkout会给出分离的HEAD?

时间:2014-01-10 20:48:39

标签: git git-checkout

如果我仅使用分支名称签出分支,HEAD将更新为指向该分支。

$git checkout branch
Switched to branch 'branch'

如果我使用refs/heads/branchheads/branch签出分支,则HEAD会分离。

$git checkout refs/heads/branch
Note: checking out 'refs/heads/branch'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

$git checkout "refs/heads/branch"
Same result

$git checkout heads/branch
Same result

为什么呢?如果它的版本依赖,我在Ubuntu 12.04.3上有git 1.7.9.5。

2 个答案:

答案 0 :(得分:16)

checkout命令区分两种情况(好吧,实际上是“很多”,但让我们从两种情况开始:-)):

  • “我想'加入一个分支';这是一个分支名称”:例如,git checkout branch
  • “我想查看一些特定的修订版本,并获取 off 任何分支;这里是一个不是分支名称的分支标识符”:例如,git checkout 6240c5c

(就个人而言,我认为这些应该使用不同的命令名称,但这只是我。另一方面,它可以消除下面描述的所有奇怪。)

现在,让我们说你想要前者。只需写出分支名称,而不是refs/heads/部分,这是最容易编写的。

如果你想要后者,你可以通过gitrevisions中除之外的任何方法为任何导致“进入分支”的方法指定修订。

无论出于何种原因,这里选择的算法 - <branch>refs/heads/下记录的算法 - 是这样的:如果你写了一个名字,那么在添加{{1对它来说,命名一个分支,git checkout会把你“放在那个分支上”。如果您指定@{-N}-,它将在HEAD reflog中查找 N - 更早的分支(-含义{{ 1}})。否则它会选择第二种方法,给你“分离的HEAD”。即使名称是gitrevisions中建议的用于避免歧义的名称,也是如此,即@{-1}当另一个heads/xyz时}。 (但是:你可以添加xyz来避免“上一个分支”的情况,即使它会进入分支机构。)

这也与gitrevisions文档中列出的解析规则相矛盾。为了证明这一点(虽然很难看到),我创建了一个同名的标记和分支,--detach

derp2

这使我进入分支,而不是分离并转到标记的修订版。

$ git checkout derp2
warning: refname 'derp2' is ambiguous.
Previous HEAD position was ...
Switched to branch 'derp2'

这向我展示了标记版本,gitrevisions表示应该这样做。


一方面注意:“进入分支”实际上意味着“将分支名称的符号引用放入git目录中名为$ git show derp2 warning: refname 'derp2' is ambiguous. ... 的文件”。符号引用是文字文本HEAD(带尾随空格),后跟完整的分支名称,例如ref:refs/heads/derp2要求没有git checkout部分的名称以添加refs/heads/部分似乎有点不一致,但这对你来说是git。 :-)可能有一些历史原因:最初,作为一个符号引用,ref: refs/heads/文件实际上是分支文件的符号链接,它始终是一个文件。这些天,部分是因为Windows,部分是因为代码演变,它有文字HEAD字符串,引用可能会“打包”,因此无论如何都不能作为单独的文件提供。

相反,“分离的HEAD”实际上意味着“将原始SHA-1放入ref:文件”。除了在此文件中具有数值之外,git的行为与“在分支上”时的行为方式相同:添加新提交仍然有效,新提交的父级是当前提交。合并仍然可以完成,合并提交的父项是当前和将要合并的提交。 HEAD文件在发生时随每个新提交一起更新。 1 在任何时候你都可以创建一个指向当前提交的新分支或标签标签,以引起新的提交链即使你关掉了“独立的HEAD”,也可以防止未来的垃圾收集;或者你可以简单地切换掉,让新的提交,如果有的话,用通常的垃圾收集取出。 (请注意,HEAD reflog会在一段时间内阻止这种情况,我认为默认为30天。)

[ 1 如果您“在某个分支上”,则会发生相同的自动更新,它只会发生HEAD引用的分支 。也就是说,如果您在分支 HEAD 上并添加新提交,B仍然会显示HEAD,但现在您获得的提交ID使用ref: refs/heads/B是您刚刚添加的新提交。这就是分支“增长”的方式:在“分支”上添加新提交会导致分支引用自动向前移动。同样,当处于此“分离的HEAD”状态时,添加的新提交会导致git rev-parse B自动向前移动。]


为了完整起见,这里列出了HEAD可以做的其他事情,如果我有这样的权力,我可能会把它放在各种单独的命令中:

  • 查看某些路径的特定版本,通过索引编写:git checkout
  • 创建一个新分支:git checkout revspec -- path ...(加上git checkout -b newbranch的选项)
  • 创建一个新的分支,当你对它进行提交时,它将是一个根提交:git branch(这会使你“在一个尚未存在的分支上”,即写入{{ 1}}进入git checkout --orphan但不创建分支 ref: refs/heads/branch-name ;这也是HEAD在新存储库中未出生的分支的方式)
  • 创建或重新创建合并或合并冲突:branch-name
  • 通过选择合并的一个或另一个“边”来解决合并冲突:mastergit checkout -m ...
  • 以交互方式选择存储库对象和工作树文件之间的补丁,类似于git checkout --oursgit checkout --theirs

答案 1 :(得分:2)

你没有检查出一个分支;你只是检查一个恰好是分支机构负责人的提交。分支是单向指针:给定一个分支,您可以确定该分支的头部的确切提交,但是您不能进行任意提交并确定它是哪个分支的头部。因此,如果您要进行新的提交,Git将不知道要更新的分支(如果有)。