Git: Demystifying the detached HEAD
Today a colleague told me that he had recently cloned a Git repository in order to check out the code of an old release version, and that he was quite confused when Git printed a warning and told him that his
HEAD was detached. Now a detached head sounds like a rather severe condition and the internet is full of people asking how to 'fix a detached
HEAD'. The truth, however, is that there is nothing to fix and that a detached
HEAD is not bad at all. So what is all this detached
HEAD business about? Let's have an example!
Let's consider the following very simple repository with the main development branch called
trunk and a
release tag somewhere in the history which points to an old release of the project.
The currently checked out version is the
trunk, which we can see from the fact that the
HEAD is pointing to it. We would say that the
HEAD is currently at
trunk and the working directory contains the latest version on the
trunk branch. So the concept of the
HEAD is super simple:
HEAD is just a pointer to the branch that is currently checked out! Now lets make a new feature branch with the command
git checkout -b feature. This is actually just a short hand for
git branch feature && git checkout feature, where
git branch feature makes a new branch at the position of the current
git checkout feature then moves the
HEAD to make it point to the new branch. This is what we get:
If we now do some work and commit it, it will go the
feature branch since this is the branch we have currently checked out. (After all, this is the branch the
HEAD is pointing to!)
Notice how the
feature branch has moved, but the
HEAD is still pointing to it. This is the normal case: A branch is checked out and moves forward when we commit. One can also make the branch move backwards and do other weird things, but the point is that the branch and
HEAD move together. This is why it is called an 'attached
HEAD'. Now let's detach some heads!
Let's go back to an old version of the code, say the last release which is conveniently tagged
release. We do this by typing
git checkout release to which Git will respond with the following output:
Note: checking out '3fi2a6f'. 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 HEAD is now at 3fi2a6f... *commit message of 3fi2a6f here*
Ok, so somehow we managed to detach our head. Luckily that is not a problem at all and the above is not a warning or error message. We actually have exactly what we wanted: The files in the working directory have been switched to the last release version and we are free to do with them whatever we want. Looking at the graph representing our repository, things also don't look too bad:
HEAD is now pointing directly to the commit
3fi now and not to the
release tag. Why is that? It does not make any sense to attach the
HEAD to a tag because tags (unlike branches) should not move around if we commit something. This situation where the
HEAD is not pointing to a branch but directly to a commit is called a 'detached
HEAD'. This is not a problem at all, especially if one just wants to look at the old code and not edit it. The reason Git considers it necessary to inform the user about the detached
HEAD is the following: Suppose we are in the detached
HEAD state, make changes to the code, and commit them. This works and we end up with this:
release tag is still where it should be, but our
HEAD is now at the new commit which has the release version as its parent. Unfortunately our newly crafted commit is 'unreachable'. Now what does that mean? Git's internal datastructure is a directed (acyclic) graph in which each commit knows its parent(s), but not its children. Our new commit is 'unreachable' because it is not an ancestor of any of our branches and can hence not be reached by starting from a branch and then walking through the directed graph back in time, which is exactly what for example
git log would do. If we were to check out a branch while the (detached)
HEAD is at an unreachable commit, we would have trouble going back to the unreachable commit as it would not be visible in the repository history. It is technically possible to go back if you still know the hash of the commit (one can use
git reflog to find out), but one should in general avoid having useful work in unreachable parts of the graph, especially since Git's garbage collector will delete unreachable objects older than 2 month.
Luckily this problem is very easy to avoid: We just make a branch before we leave! The work we commited during the detached
HEAD will be an ancestor of our new branch and will hence not be unreachable anymore.
It's as easy as running
git checkout -b newbranch before we leave, which is exactly what Git told us to do the moment our
HEAD got detached. Once we have the new branch we can safely do
git checkout feature to go back to where we were before and everything will be fine.
In summary: Don't freak out over a detached
HEAD, it's not a problem. You don't have to do anything if you just want to look at the code, but if you commit something you should make a branch before you leave. Luckily Git will remind you to do this, so it's very hard to forget. If you still manage to forget you have
git reflog and ~2 month to fix things.
Disclaimer: This article deals with the version control software Git. A 'detached
HEAD' in Git is not a problem, but any other form of (even partially) detached heads is a serious condition and requires immediate medical attention!