为什么使用显式的refs / heads /分支的git checkout会分离HEAD? [英] Why does git checkout with explicit refs/heads/branch give detached HEAD?

查看:1057
本文介绍了为什么使用显式的refs / heads /分支的git checkout会分离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 for git 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., writes ref: refs/heads/branch-name into HEAD but does not create the branch branch-name; this is also how master 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屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆