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.
1

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 HEAD and git checkout feature then moves the HEAD to make it point to the new branch. This is what we get:
2

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!)
3
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:
4
Notice how 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:
5
The 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.
6
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.
7
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!

You may also like...

2 Responses

  1. Sven says:

    Nice! You should add vector versions for your HQ pictures and an appropriate license, then they could e.g. also inserted to an explanatory wikipedia article.

  2. robert says:

    Thanks! I somehow thought that vector graphics in websites were not really supported by all browsers. (At least Wikipedia always rasterizes them.) As it turns out this is not really true anymore, so I replaced the images in this post with SVGs. By the way: It's very easy to make these pictures. There is a LaTeX package based on TikZ that draws graphs of Git repos, see: https://github.com/Jubobs/gitdags/wiki

Leave a Reply

Your email address will not be published. Required fields are marked *