git checkout branch causes detached head

Viewed 497

Trying to checkout a remote branch that does not yet exist on my system. For the purposes of this question, none of the 3 branches exist on my system.

git branch -r shows a list of relevant branches on the remote:

  origin/V2.0005B2
  origin/V2.0005B3
  origin/V2.0005B4

I can run git checkout V2.0005B2 or git checkout V2.0005B4 fine and it creates a local copy and sets it up to track.

But when I try to run git checkout V2.0005B3 it puts me in a detached head state every time. It happens on every machine I've tried it on.

SourceTree can check out this branch just fine as well as git fork. But the Git bash command line has this problem.

I am only hoping there is nothing wrong with this branch, otherwise it seems to work fine as long as it is originally checked out using one of those other utilities. The branch lives in BitBucket and I've logged in there to see if I could see anything unusual, but it looks the same as the others.

Thanks, Dave

2 Answers

LeGEC's answer is correct as far as it goes, but I'll add two points. The first is about how to get what you want, and the second, longer point, describes another failure case and yet another trick for working around it.

First, there is one more way to create a local branch from a remote-tracking name that is fairly short, and that is to use git checkout (or git switch) with the --track option and the remote-tracking name:

git checkout --track origin/V2.0005B3

This particular form locates the remote-tracking name (full spelling refs/remotes/origin/V2.0005B3), discovers that it is indeed a remote-tracking name (begins with refs/remotes/), applies the --track option to strip the refs/remotes/origin part to come up with V2.0005B3, and then uses the branch-creation option (-b for git checkout, -c for git switch) to create V2.0005B3 with upstream origin/V2.0005B3 already set.

Second: the usual reason for this problem is that Git finds the tag first, because there is such a tag:

  • The git checkout code tries the name you give it first as the name of an existing branch—i.e., tries adding refs/heads/ in front to see if the result will resolve to a commit hash ID. If so, this is an existing branch name, and hence a request to switch to that existing branch.

  • If that fails, however, it then turns to the usual six-step process described in the gitrevisions documentation, which tries a tag name before a remote-tracking name. Note that "try the name as a tag name" is step 3 (which comes before step 4, "try the name as a branch name": but git checkout did its own private step-4 before even going into the six-step process).

  • Had there not been a tag, Git would have moved on to steps 5 and 6, and step 5 would have found the remote-tracking name. This would invoke the "DWIM" code later to create the branch, unless you used the --no-guess option. (This option was new in Git 2.21.)

But there is one more way for this automatic-creation --guess mode to fail, and that is if there are two or more remote-tracking-name candidates. So if, say, there are both origin/V2.0005B3 and upstream/V2.0005B3 remote-tracking names, the guess mode does not know which one to use.

The --track option tells Git which one to use and thus neatly sidesteps this dilemma. There's another trick, new in Git 2.19, as well: checkout.defaultRemote tells the --guess code in both git checkout and git switch which remote is preferred, if there are multiple matches. So besides --track, if the issue is multiple remotes with identical branch names, you can configure a preferred remote so as to choose the right remote-tracking name.

Check your local tags :

git tag --list

If you have a tag named V2.0005B3, running git checkout V2.0005B3 will result in your situation : a detached HEAD state, on the commit pointed at by that tag.

  • If you want to create a branch there, run :
git checkout -b V2.0005B3
git branch -u origin/V2.0005B3

# or shorter suggested by @torek :
git checkout --track origin/V2.0005B3
  • If you want to delete the tag :
# delete your local tag :
git tag -d V2.0005B3

# delete the remote tag :
git push -d origin refs/tags/V2.0005B3

If this is a shared repo, tell all of your colleagues to also delete their local tag ; this will avoid surprising behaviors on their machine, and mitigate the possible reappearance of that tag on the central repo.

Related