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.
Any checkout of a commit that is not the name of one of your branches will get you a detached HEAD. A SHA1 which represents the tip of a branch still gives a detached HEAD.
If you want to keep changes made with a detached HEAD, just create a new branch and switch to it. You can create it right after arriving at a detached HEAD or after creating one or more commits. The result is the same. The only restriction is that you should do it before returning to your normal branch.
Internally, the git checkout command simply updates the HEAD to point to either the specified branch or commit. When it points to a branch, Git doesn't complain, but when you check out a commit, it switches into a “detached HEAD” state.
Oct 1, 2020. A detached HEAD occurs when you check out a commit that is not a branch. The term detached HEAD tells you that you are not viewing the HEAD of any repository. The HEAD is the most recent version of a branch. This is sometimes called the “tip of a branch”.
The checkout
command distinguishes between two cases (well, actually "many", but let's start with just the two :-) ):
git checkout branch
.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. Edit, Jun 2020: In Git 2.23 or later, these are in a separate command now: git switch
is the branch-changer, and it requires --detach
to go to a detached HEAD; git restore
implements the file-restorer mentioned later in this posting. You can still use the existing git checkout
command the same way as in pre-2.23 Git, though.)
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:
git checkout revspec -- path ...
git checkout -b newbranch
(plus options for git branch
)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)git checkout -m ...
git checkout --ours
, git checkout --theirs
git add --patch
: git checkout --patch
You're not checking out a branch; you're merely checking out a commit that happens to be the head of a branch. Branches are one-way pointers: given a branch, you can determine the exact commit which is the head of that branch, but you cannot take an arbitrary commit and determine which branch(es) it is the head of. Thus, if you were to make a new commit, Git would not know which, if any, branch to update.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With