Git: Detached HEAD State For Dummies
Alright, let's call things as they are: Git can be intimidating. Or, well, downright scary. Zillions of flags and oh-my-goodness-look-how-many configuration options! Consider git push
- you can't even tell what it does unless you look at your git settings. For experienced developers, people with PhD in rocket surgery or power users: nirvana. For others: muddy pool full of crocodiles.
To top this up even though Git's warning messages are actually very informative if you know what they're trying to tell you, they can also be overwhelmingly technical and confusing.
One of the well known scary moments is the first time you land in the infamous detached HEAD
state. You know you're in it when Git tells you:
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 <new-branch-name>
And then it doesn't let you push
or pull
because fatal: You are not currently on a branch.
Which can be super-confusing. Particularly if you were on master
and you landed in the detached HEAD state by running git checkout <previous_commit>
. What is Git talking about?! That doesn't make any sense, I'm still on the master
branch, just one commit earlier! Right?
Well, not quite, no. But rest assured that by being such a stubborn A-hole, Git is actually trying to prevent you from shooting your kneecaps off. Let's have a look at what's going on.
Git branches for dummies
In some version control systems - think Mercurial - branch name is embedded into each commit's metadata. So you can always tell which branch a commit belongs to simply by looking at it.
In Git you can't. You see, Git is simplistic. Think of it as a graph - directed acyclic graph, to be precise - where each node is a commit with a unique hash id and one or more pointers to the parent commits (usually one). Et voilá. Git 101 complete. Well done!
Now - branches. As you can imagine, hashes (say bf53a513aaa2d7db13a3baf2d5cc9a4dba8d2428
) are somewhat hard to remember or - god forbid! - even type. Ergo, we need a more human-friendly way of referring to them (think IP addresses and DNS - pretty much the same thing). This is when branches and tags come in handy.
Think of branches and tags as of post-it notes you write a name on and slap them onto a commit. So then you can say either commit bf53a513aaa2d7db13a3baf2d5cc9a4dba8d2428
or feature/make-everything-alright-button
. Much nicer, eh?
Now, tags are just that - named commits. They never change. That makes a lot of sense because you usually use them to refer to releases ("release 1.0.1 corresponds to commit u329fv974bw...
").
Branches, on the other hand, are automatically moved and re-attached by Git to new commits as you add them. This makes sense too because you usually use branches to develop a feature in isolation from the main branch (think master
) to merge it in later when the code is ready. So you want the branch to refer to the latest commit (you usually don't want to merge half of your branch, do you?). Git is nice and takes care of that for you and moves the post-it note whenever you commit.
Now, the important thing is that a branch always points to a single commit. It's not a magic thread that is interwoven through the Git graph (like in Mercurial). No. If you move away from a commit with a post-it label - say master
- to a previous commit, you are not "on a branch" anymore. You're just somewhere. Which means you don't have any other way to refer to where you are other than the commit hash. You're detached.
Back to detached HEAD state
That's what Git is trying to warn you about. Detached HEAD
means you're on a commit that does not have a branch or tag post-it label attached. So if you do some coding, commit and then you git checkout master
, you will not be able to go back unless you wrote down or remember the commit hash. In other words it's Git trying to tell you: "If you move away, you won't be able to go back."
(Of course, the golden rule of Git is "if you committed you're most probably fine". So there's a command to help you - git reflog
. But that's considered advanced Git. Hence the warning.)
In the detached head state, Git also gives you a hint on what to do: git checkout -b <new-branch-name>
. This command creates a new branch - i.e., a new post-it label - and attaches it to the current commit. Then, if you go away, you will always have a human-friendly name to come back to.
And that's all there is to it! Now next time you end up with detached head, you'll know exactly what's happened and what to do.
Or, well, you know. Hopefully. Anyhow, happy version controlling!
I'm @tomas_brambora.