1. Overview
It’s not uncommon to come across a fairly mysterious-sounding state while working with Git. In particular, a detached HEAD looks like a bad or incorrect repository status.
In this tutorial, we discuss what a detached HEAD is and how it works. Further, we walk through ways to navigate around a detached HEAD in Git.
2. What Is HEAD in Git
Git stores a record of the state of all the files in the repository when we create a commit. HEAD is an important type of reference that keeps track of the current point in a Git repo. In other words, HEAD preserves our current location in the repository in terms of a commit:
$ git log --oneline
a795255 (HEAD -> master) create 2nd file
5282c7c appending more info
b0e1887 create first file
For instance, when we use the log command without a commit identifier as the argument, how does Git know which commit it should start displaying results from? HEAD provides the answer. When we create a new commit, its parent is indicated by where HEAD currently points to.
Because Git has such advanced version tracking features, we can view the repository’s contents at any point in time.
Being able to review past commits also lets us see how a particular file or set of files has evolved over time. When we check out a commit that’s not the latest of a given branch, we enter a detached HEAD state. This means HEAD refers to a commit that’s not the most recent commit in a repository branch.
3. Example of Detached HEAD
Most of the time, HEAD points to a branch name.
When we add a new commit, branch references get updated to point to it, but HEAD remains the same. When we change branches, HEAD is updated to point to the branch we’ve switched to. Thus, HEAD remains synonymous with the last commit in the current branch.
Let’s look at an example state in which HEAD is attached to a branch:
As we can see, HEAD points to the master branch reference, which points to the last commit. Everything looks as expected.
However, after performing a checkout over a previous commit in the current branch that’s not at the top of another branch, the repo has a detached HEAD state:
$ git checkout 5282c7c
Note: switching to '5282c7c'.
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 switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
HEAD is now at 5282c7c appending more info
Below is the graphical representation of the current Git HEAD. Since we’ve checkout to a previous commit, HEAD is now pointing to 5282c7c, and the master branch is still referring to the same commit as before:
Thus, we have a mismatch between the master and checked-out branch top commit and the current HEAD.
4. Benefits of a Git Detached HEAD
By detaching the HEAD by checking out a particular (5282c7c) commit, we go to a previous point in the project’s history.
Let’s say we want to check if a given bug already existed last Tuesday. We can use the log command, filtering by date, to start the relevant commit hash. Then, we can check out the commit and test the application however we see fit.
What if we could not only take a look at the past but also change it? That’s another thing a detached HEAD enables us to do. Let’s see how:
$ echo "understanding git detached head scenarios" > sample-file.txt
$ git add .
$ git commit -m "Create new sample file"
$ echo "Another line" >> sample-file.txt
$ git commit -a -m "Add a new line to the file"
Thus, we now have two additional commits that descend from our second commit. Let’s run git log –oneline and see the result:
$ git log --oneline
7a367ef (HEAD) Add a new line to the file
d423c8c create new sample file
5282c7c appending more info
b0e1887 creating first file
Before HEAD pointed to the 5282c7c commit. After that, we added two more commits, d423c8c and 7a367ef.** Below is the graphical representation of the commits done on top of HEAD. It shows that now HEAD is pointing to the latest commit 7a367ef:
What should we do if we want to keep these changes or go back to the previous state? We’ll see in the next point.
5. Scenarios
Since the detached HEAD scenario can become fairly complex, let’s look at it from different angles to understand it better.
5.1. Detached by Accident
If we’ve reached the detached HEAD state by accident – that is to say, we didn’t mean to check out a separate commit – going back is fairly easy:
$ git switch <last-branch-name>
$ git checkout <last-branch-name>
Essentially, we just check out the branch we were in before.
5.2. Want to Discard Changes After Detachment
In some scenarios, if we made changes after detaching HEAD to test some functionality or identify bugs but we don’t want to merge these changes to the original branch, we can simply discard it using the same commands as the previous scenario and go to back our original branch:
$ git checkout <last-branch-name>
Another way to achieve the same is to perform a reset or revert:
$ git reset --hard
This way, we lose everything committed after the HEAD commit.
5.3. Discard Some Changes or Restore Files After Detachment
In addition, we might want to restore just some files to their state within the index. To do so, we can again checkout:
$ git checkout -- ./path/to/file
This way, we can mix or discard modifications. In the end, we might also decide to keep them.
5.4. Keep All Non-Committed Changes After Detachment
To only preserve staged but not committed changes and lose all commits, we can leverage the stash:
$ git stash && git checkout master && git stash pop
This way, we use the stash to keep and reapply the working tree modifications while transitioning to master, thereby dropping any detached HEAD commit.
5.5. Keep Committed Changes After Detachment Permanently
If we want to keep changes made with a detached HEAD, we just create a new branch and switch to it. We can create it right after arriving at a detached HEAD or after creating one or more commits. The result is the same. The only restriction is that we should do it before returning to another branch.
For instance, let’s create a branch after the two commits:
$ git branch experimental
$ git checkout experimental
We can notice how the result of git log –oneline is exactly the same as before, with the only difference being the name of the branch indicated in the last commit:
$ git log --oneline
7a367ef (HEAD -> experimental) Add a new line to the file
d423c8c create new sample file
5282c7c appending more info
b0e1887 creating first file
This is critical because it enables git to return to the branch at will and treat it like any other normal branch.
Further, since detached HEAD changes usually mean patches, we often perform a merge with the primary master branch:
$ git checkout master
$ git merge experimental
Thus, we merge the new experimental branch with the primary one.
6. Conclusion
In this article, we saw that a detached HEAD doesn’t mean something is wrong with a repo. Detached HEAD is just a less usual state the repository can be in. In addition to not being an error, it can actually be quite useful, enabling us to run experiments that we can then choose to keep or discard.