Friday, December 7, 2012

Git: Beware of detached head

In git, every commit is recognized by a 40-character hash known as commit hash. A git repository is essentially a tree of commits, where each commit points to its ancestor(s). In git, a branch head is the last commit in the branch. A Git branch is simply a pointer to this commit, using its commit hash, in the repository. These pointers are kept in .git/refs/heads directory as files. If you look at one of these files you will find that each branch corresponds to a single file which contains nothing but a 40-character commit hash of the commit representing branch's head. So when you have a master branch, there is a file .git/refs/heads/master pointing to head commit of this branch.

There is a difference between a branch head and HEAD. As described earlier, a branch head is the last commit in the branch, whereas HEAD represents your currently checked-out commit. It is also maintained as a pointer in .git/HEAD file. Normally this pointer will point to the head of currently checked-out branch, thus the name HEAD, but it is quite possible for this pointer to point to a commit which is not the branch head. So , for example, if you are working on branch master and you have checked out the head of this branch, the contents of the .git/HEAD file will look like following:
adnan@adnan-laptop: test/> cat .git/HEAD
ref: refs/heads/master
So, in this case, the HEAD points to head of branch master, which then points to a commit. This means that you have currently checked-out head of the branch master. Now if you move one step backward and checkout a commit just before the head commit, you will get a detached HEAD.
adnan@adnan-laptop: test/> git checkout HEAD^
Note: checking out '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 new_branch_name
HEAD is now at 123fa7b1... my 1st commit comment
A detached HEAD is a situation where the HEAD pointer is no longer pointing to a branch head. In this case the file .git/HEAD doesn't contain a reference to a branch head but instead directly points to the checked-out commit using its commit hash.

If you now check the contents of .git/HEAD file, you will see it contains the hash of the commit you just checked-out.
adnan@adnan-laptop: test/> cat .git/HEAD 
It is totally okay to work with detached heads but it is very important to understand the situation as any commits made in this situation will not be part of any branch, as you are not at the branch head, and the only way to refer to it later on will be using its commit hash. Also git garbage collection will run occasionally and remove any commits which are not referenced, directly or indirectly, by a branch or a tag. 

No comments: