I'm a newer git user so this maybe a dumb question, but all the sudden whenever I checkout any previous commit with something like git checkout 050aa9f
in my Development
branch, git immediately detaches the head:
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.
If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example:
git checkout -b
HEAD is now at 050aa9f [Code commit title here]
But when I checkout a commit from another branch such as master
it doesn't detach the head.
Have I done something to corrupt my tree in some way? How can I find where this started, and how do I fix it?
whenever I [use] ...
git checkout 050aa9f
... git immediately detaches the head
That's because this kind of git checkout
is specifically a request to detach HEAD
.
Any time you use something that is not a branch name, but can be resolved to a commit hash ID, git checkout
will put you in detached-HEAD mode. But any time you use something that is a branch name, git checkout
will put you in the normal mode. (Git does not call this "attached-HEAD" mode but that's the obvious right name for the mode.)
There are several tricky bits here, some of which are partly helped out by using the new (in Git 2.23 and later) git switch
command as VonC recommends. I'll go through them here, but remember that some of this is Advanced Git and you're not expected to know all of it right away. 😀
Git can create a new branch, then check that branch out by name, resulting in an attached HEAD.
You can use --detach
with a branch name to force Git to enter detached HEAD mode even if you provide a branch name.
Using the -b
option, Git will always try to create a new branch name (and then attach to it). This can fail in several cases, though I won't go into any details here.
Using the --track
option, you can name a remote-tracking name like origin/develop
and Git will use that name to figure out which branch name to create. The name Git picks here is formed by stripping off the remote part, so running git checkout --track origin/develop
is roughly the same as running git checkout -b develop --track origin/develop
. I say roughly the same because additional options can modify this behavior.
The git checkout
command itself implements what has, in Git 2.23 and later, been split into two separate commands: git switch
, and git restore
. In some cases, when you expect git checkout
to do what I am about to describe, Git will discover that you have a file or folder named dev
and implement what is now split out as git restore
, instead of what is now git switch
. This is ... not a good thing, let's just say, and since Git 2.23, git checkout
now tells you that it wasn't sure what you meant here, and doesn't just do the wrong thing.
Giving the name of a branch that does not exist, but that can be created, sometimes results in creating that branch. For instance, if you do not yet have a branch named dev
, but do have an origin/dev
, and you run git checkout dev
, you might expect Git to say: Huh ... there's no branch named dev
and no file-or-folder named dev
. I can't turn that into a branch name, so I'll just quit with an error. But that's not what happens. Instead, Git says to itself: Huh, there's no branch named dev
, and no file-or-folder named that either. But there is an origin/dev
. I'll bet you wanted me to create a branch named dev
, as if you had run git checkout --track origin/dev
. And then it does that.
It's worth describing exactly what goes wrong with the old git checkout
here, that doesn't with the new git switch
/ git restore
split. (And, as I mentioned, git checkout
itself has been made smarter so that it doesn't just blindly do the wrong thing now—but with Git versions older than 2.23, watch out!) The two "kinds" of git checkout
are:
The one that switches branches. This is a non-destructive command: if you have uncommitted work, git checkout
may let you switch branches, but it will only do that if none of your uncommitted work will be destroyed in the process. (This is complicated. Don't look at it yet, but this question is all about that.)
In Git 2.23, you can use git switch
to do this command. You can still use git checkout
, too.
The one that clobbers your uncommitted work. This is a destructive command! Suppose you've been editing some files and you have decided that your attempt to do something useful has been fruitless, and should now be utterly, irrevocably destroyed. You'd like to put things back to the way they were—at least, for one specific file, and maybe for several files.
In Git 2.23, you can use git restore
to do this command, but in every version of Git, you can use git checkout
here too.
This means one kind of git checkout
is entirely safe: it never wrecks in-progress work. The other kind of git checkout
is quite dangerous: you're telling Git please wipe out my in-progress work, irretrievably.
This is the danger I mentioned above. Suppose you have a bunch of files in a folder named dev
, and a remote-tracking name origin/dev
, but you do not yet have a branch named dev
. If you run:
git checkout dev
expecting Git to create a branch named dev
now based on origin/dev
, you get a nasty surprise: Git (before 2.23) wipes out any work you've done on the dev/*
files instead.
(There are even more things that git checkout
can do, all of which are now part of the split-out commands. I've left these out to keep this answer short. Well, shorter, anyway.)