为什么使用显式的refs / heads /分支的git checkout会分离HEAD? [英] Why does git checkout with explicit refs/heads/branch give detached HEAD?
问题描述
如果我使用分支名称检出分支, HEAD
会更新为指向该分支。
$ git checkout branch
转换到分支'branch'
如果我通过使用 refs / heads / branch
或 heads / branch
, HEAD
变为分离。
$ git checkout refs / heads / branch
注意:签出'refs / heads / branch'。
您处于分离头部状态。你可以环顾四周,做一些实验性的
修改并提交它们,你可以放弃你在这个
状态下进行的任何提交,而不会影响任何分支,通过执行另一个结账。
$ git checkoutrefs / heads / branch
同样的结果
$ git checkout heads / branch
同样的结果
为什么?如果它的版本依赖,我在Ubuntu 12.04.3上有git 1.7.9.5。 命令区分了两种情况(实际上,很多,但是让我们从两个开始:-)):
$ ul
git checkout分支
。
git checkout 6240c5c
。
(个人而言,我认为这些应该使用不同的命令名称,但这只是我。这样可以避免下面所描述的所有奇怪现象。)
现在,让我们假设你想要前者。只需写出分支名称即可,不需要 refs / heads /
部分即可轻松完成编写。
如果您想要后者,您可以通过 gitrevisions ,除之外的任何导致进入分支的方法。
无论出于何种原因,这里 - 它被记录在手册页 ,在< branch>
-is this下:如果你写了一个名字,当添加 refs / heads /
给它,命名一个分支, git checkout
会让你在那个分支上。如果您指定 @ { - N }
或 -
,它会查找< HEAD
reflog(含 -
含义 @ { - 1}
)。否则,它会选择第二种方法,为您提供分离的HEAD。即使名称是在gitrevisions中为避免模糊性而建议的名称,即 heads / xyz
当有另一个 xyz
时。 (但是:您可以添加 - detach
以避免在分支上发生情况,即使它会以其他方式在分支上发生。)
这也与gitrevisions文档中列出的解决规则相矛盾。为了证明这一点(虽然很难看出),我用同样的名字创建了一个标签和分支, derp2
:
$ git checkout derp2
警告:refname'derp2'含糊不清。
上一个HEAD位置是...
转换到分支'derp2'
这使我在分支上,而不是分离和去标记的修订。
$ git show derp2
警告:refname'derp2'不明确。
...
这给我看了标签版本,gitrevisions说它应该这样做。
一面注意:在分支上的意思是将符号引用分支名称放入文件在git目录下名为 HEAD
。符号引用是文本 ref:
(带尾随空格),后跟完整分支名称,例如 refs / heads / derp2
。在> git checkout
要求不带 refs / heads /
部分的名称来添加 ref:refs / heads /
部分,但这是你的git。 :-)这可能有一些历史原因:最初,作为符号引用, HEAD
文件实际上是分支文件的符号链接,该文件始终是文件。现在,部分原因在于Windows,部分原因是通过代码演变,它具有字面值 ref:
字符串,并且引用可能会变成压缩,因此不可用作为
相反,分离HEAD的意思是将原始SHA-1放入 HEAD
文件。除了在这个文件中有一个数字值之外,git的行为与在分支上的行为一样:添加一个新的提交仍然有效,新提交的父目录是当前提交。合并仍然可以完成,合并提交的父项即为当前合并提交和即将合并的提交。 HEAD
文件会在每次新提交时更新。 1 在任何时候,您都可以创建指向当前的新分支或标签标签提交,即使在关闭分离的HEAD之后也会保留新的提交链,以防将来的垃圾收集;或者您可以简单地切换,让新的提交(如果有的话)通过常规垃圾收集取出。 (请注意, HEAD
reflog会阻止这一段时间,我认为这是默认的30天。)
[< sup> 1 如果你在一个分支上,同样的自动更新发生,它只是发生在 HEAD
指向的分支 。也就是说,如果你在分支 B
上,并且添加了一个新的提交, HEAD
仍然表示 ref:refs / heads / B
,但现在使用 git rev-parse B
是刚刚添加的新提交。这是分支增长的方式:在分支上时添加新的提交会导致分支引用自动向前移动。同样,当处于这个分离的HEAD状态时,添加的新提交会导致 HEAD
自动前进。]
为了完整性,这里列出了一些其他的东西 git checkout
可以做的事,如果我有的话我可能会放入不同的命令这样的权力:
git checkout
git checkout -b newbranch
(plus选项为 git branch
)
git checkout --orphan
(这会将你置于一个尚不存在的分支上,即写入 ref:refs / heads / branch-name
转换为 HEAD
但不会创建分支 branch-name
;这也是 master
是新存储库中未出生的分支的方式)
git checkout -m ...
git checkout --ours ,
git checkout - 他们的
git add --patch
的方式在资源库对象和工作树文件之间交互选择补丁: git checkout --patch
If I checkout a branch using just the branch name, HEAD
is updated to point at that branch.
$git checkout branch
Switched to branch 'branch'
If I checkout a branch by using refs/heads/branch
or heads/branch
, HEAD
becomes detached.
$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
Why? If its version dependent, I have git 1.7.9.5 on Ubuntu 12.04.3.
The checkout
command distinguishes between two cases (well, actually "many", but let's start with just the two :-) ):
- "I want to ‘get on a branch’; here's a branch name": e.g.,
git checkout branch
. - "I want to look at some particular revision, and get off any branch; here's a branch-identifier that is not a branch name": e.g.,
git checkout 6240c5c
.
(Personally, I think these should use different command names, but that's just me. On the other hand, it would obviate all the weirdness described below.)
Now, let's say that you want the former. That's easiest to write by just writing out the branch name, without the refs/heads/
part.
If you want the latter, you can specify a revision by any of the methods listed in gitrevisions, except for any method that results in "getting on the branch".
For whatever reason, the algorithm chosen here—it is documented in the manual page, under <branch>
—is this: If you've written a name that, when adding refs/heads/
to it, names a branch, git checkout
will put you "on that branch". If you specify @{-N}
or -
, it will look up the N-th older branch in the HEAD
reflog (with -
meaning @{-1}
). Otherwise it chooses the second method, giving you the "detached HEAD". This is true even if the name is the one suggested in gitrevisions for avoiding ambiguity, i.e., heads/xyz
when there's another xyz
. (But: you can add --detach
to avoid the "get on a branch" case even if it would otherwise get on the branch.)
This also contradicts the resolving rules listed in the gitrevisions document. To demonstrate this (although it's hard to see), I made a tag and branch with the same name, derp2
:
$ git checkout derp2
warning: refname 'derp2' is ambiguous.
Previous HEAD position was ...
Switched to branch 'derp2'
This put me on the branch, rather than detaching and going to the tagged revision.
$ git show derp2
warning: refname 'derp2' is ambiguous.
...
This showed me the tagged version, the way gitrevisions says it should.
One side note: "getting on a branch" really means "putting a symbolic reference to a branch name into the file named HEAD
in the git directory". The symbolic reference is the literal text ref:
(with trailing space) followed by the full branch name, e.g., refs/heads/derp2
. It seems kind of inconsistent that git checkout
demands the name without the refs/heads/
part in order to add the ref: refs/heads/
part, but that's git for you. :-) There may be some historic reason for this: originally, to be a symbolic reference, the HEAD
file was actually a symbolic link to the branch file, which was always a file. These days, in part because of Windows and in part just through code evolution, it has that literal ref:
string, and references may become "packed" and hence not available as a separate file anyway.
Contrariwise, a "detached HEAD" really means "putting a raw SHA-1 into the HEAD
file". Other than having a numeric value in this file, git continues to behave the same way as when "on a branch": adding a new commit still works, with the new commit's parent being the current commit. Merges can still be done as well, with the merge commit's parents being the current and to-be-merged commits. The HEAD
file is updated with each new commit as it happens.1 At any point you can create a new branch or tag label pointing to the current commit, to cause the new chain of commits to be preserved against future garbage collection even after you switch off the "detached HEAD"; or you can simply switch away and let the new commits, if any, get taken out with the usual garbage-collection. (Note that the HEAD
reflog will prevent this for some time, default 30 days I think.)
[1 If you're "on a branch", the same auto-update happens, it just happens to the branch that HEAD
refers to. That is, if you're on branch B
and you add a new commit, HEAD
still says ref: refs/heads/B
, but now the commit-ID that you get with git rev-parse B
is the new commit you just added. This is how branches "grow": new commits added while "on the branch" cause the branch reference to move forward automatically. Likewise, when in this "detached HEAD" state, new commits added cause HEAD
to move forward automatically.]
For completeness, here's a list of other things git checkout
can do, that I might have put in various separate commands if I had such powers:
- check out a specific version of some path(s), writing through the index:
git checkout revspec -- path ...
- create a new branch:
git checkout -b newbranch
(plus options forgit branch
) - create a new branch that, if and when you do a commit on it, will be a root commit:
git checkout --orphan
(this puts you "on a branch" that does not yet exist, i.e., writesref: refs/heads/branch-name
intoHEAD
but does not create the branchbranch-name
; this is also howmaster
is an unborn branch in a new repository) - create or re-create a merge or merge conflict:
git checkout -m ...
- resolve a merge conflict by picking one or the other "side" of the merge:
git checkout --ours
,git checkout --theirs
- interactively select patches between repository objects and work-tree files, similar to
git add --patch
:git checkout --patch
这篇关于为什么使用显式的refs / heads /分支的git checkout会分离HEAD?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!