Image of What is Git HEAD? | The Concept of HEAD in Git - Explained

ADVERTISEMENT

Table of Contents

Introduction

If you have used Git to work on a project before, you have likely noticed the term HEAD appear in response to commits, checkouts, and pulls. You may have even run into issues with a Git detached HEAD a few times. However, this seemingly complex feature is actually simple and helpful.

In this article, we will answer the question What is Git HEAD? After you learn Git HEAD, you will have added to your knowledge and understanding of Git's version control capabilities and increased your confidence as a software developer.

Git Refs and Heads

Before jumping right into our main question, it will be useful to provide some background information on two Git concepts, Git refs and Git heads.

In Git, a ref is a human readable name that references a Git commit ID. A ref is essentially a pointer to a commit. Examples of refs are Git branch names such as master and dev. Another example of refs are Git tags such as v0.1 or v0.2. You can think of each of these as a variable name that points to a commit ID. The commit ID that a ref points to is dynamic so it can change over time.

When representing a branch name, a ref such as master represents the tip (most recent commit ID) on that branch. Refs are stored in a special hidden location in your Git repository at the path .git/refs/.

In Git, a head is a ref that points to the tip (latest commit) of a branch. You can view your repository’s heads in the path .git/refs/heads/. In this path you will find one file for each branch, and the content in each file will be the commit ID of the tip (most recent commit) of that branch.

For example, there is literally a file called master in that path that contains the commit ID of the tip of the master branch. When you make a new commit on a branch or pull commits from a remote, the head file for that branch is always updated to reflect the commit ID of the tip of the branch. In this way, your branch name ref always stays in sync with the most recent commit at the tip of the branch.

A general understanding of refs and heads is important because they tell us what branch names and tags really are in Git. They are not complicated or special.

Git refs and Git heads are simply pointers to commits, in the form of text files where the file name represents the name of the ref/head and the content is the commit ID that the ref points to.

I strongly recommend you open up terminal, browse to the root of your Git repository, and use your favorite text editor to peek at some of the files in the .git/refs/heads/ directory and their content.

Note: Refs can actually point to other refs, which will resolve to the commit ID of the destination ref. For example, if you create a tag called v2.0 pointing to the master branch, the v2.0 tag will resolve to the commit ID of the tip of the master branch at that point in time. If you are interested in looking at the tags in your Git repository, find them in .git/refs/tags/ directory.

What is Git HEAD?

Now that we know the basics of refs and heads, let talk about the more specific concept of Git HEAD.

HEAD is a special ref that points to the commit you are currently working on - the currently checked out commit in your Git working directory. You can think of it as a global variable or environment variable within your Git repo that can change depending on which commit you've checked out in your working directory. It is stored in a file called .git/HEAD, which I recommend you peek at in your text editor. HEAD usually points to the tip/head of the currently active branch, which is represented in the .git/HEAD file as follows:

> cat .git/HEAD
ref: refs/heads/master

This enables Git to know that the user's working directory currently corresponds to the tip of the master branch. When you use the git checkout command, HEAD is changed to point to the head of the newly checked out branch. So if you run the command git checkout dev, the HEAD file will be updated as:

> git checkout dev
Switched to branch 'dev'
Your branch is up to date with 'origin/dev'.

> cat .git/HEAD
ref: refs/heads/dev

This enables Git to understand that you are currently working on the tip of the dev branch.

Git HEAD vs head

So what is the difference between capitalized Git HEAD and lowercase Git head?

In lowercase, "head" is a general term that means any commit that represents a branch tip. In uppercase, "HEAD" is a specific Git ref that always points to the commit currently checked out in the working directory.

What is a Detached HEAD in Git?

The git checkout command can be used to checkout a specific commit into the working directory using its commit ID. This can be useful if you want to view the state of your project files in a previous commit:

> git log --oneline
367f155 (HEAD -> master, origin/master, origin/HEAD) Do something cool.
32e5fd6 Do something sweet.
946abcb Do something special.

> git checkout 32e5fd6
Note: switching to '32e5fd6'.

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 -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 32e5fd6 Do something sweet.

In the scenario above, we used git log --oneline to list the shortened commit IDs for several commits. We then used git checkout <commit ID> to checkout a previous commit into the working directory.

This places Git into a detached HEAD state, which means that HEAD is not currently pointing to a branch head (branch tip). In this state, you can view and edit any files in your working directory, exactly as they were in that commit.

How Do I Fix a Detached HEAD in Git?

There are two ways recommended in the output above to fix a detached HEAD in Git. You can either switch back to an existing branch, or create a new branch from the detached HEAD commit location. Let's discuss both options below.

Fixing Detached HEAD in Git by Switching Branches

In most situations, you simply wanted to view a file in a previous commit and go back to the tip of your dev or main branch. In this case, you aren't worried about losing any changes associated with the detached HEAD, (since you didn't make any changes).

You can simply checkout an existing branch using something like git checkout master. This will checkout the master branch into your working directory and set HEAD to point to that branch's head.

Note that you can also use the command git switch - to take you back to automatically take you to the previous branch you were on.

Fixing Detached HEAD in Git by Creating a New Branch

In some cases, you might want use a previous commit as the starting point for a new line of development work. In that case, you can make changes to files in the detached HEAD state, and even commit them using git commit.

The catch is that if you make changes and then just switch back to a normal branch, you'll lose any changes and commits you made in the detached HEAD state. If you want to keep any changes or Git commits you may have made starting from the detached HEAD, you can simply create a new branch like: git branch tmp.

This will store your changes in the tmp branch and set HEAD to the tip of that new branch. After doing that, you can either keep working on your new branch, or checkout the original branch and merge your new branch into it (e.g. git checkout master followed by git merge tmp).

Note that you can use the command git switch -c <new-branch-name> while in a detached HEAD to achieve a similar effect.

When Should I Use Git HEAD?

The ability to return to a previous commit is immensely useful during development. For example, if a commit introduces a breaking change to a file, you can use git log -p <file> to view all of the commits that have affected that file. In this interface, you can use the arrow keys to scroll up/down and press Q to exit.

What is Git HEAD?

Once you find the commit in question, you can revert the full branch state to it using git checkout <commit ID>. This detaches HEAD back to that commit so that you can view that file's old version in context (e.g. to run and test your application). Alternatively, if you only want to revert that specific file, you can run git checkout <commit ID> <file> to restore it.

Git show HEAD

The git show command is a quick way to peek at a commit, including the commit ID, commit message, and a textual diff representation of the changes in that commit. The syntax is git show <commit-id>.

This can be applied to HEAD in order to easily show the details of the commit pointed to by HEAD:

git show HEAD

This command will display the commit ID that HEAD is currently pointing to (usually the tip of the currently checked out branch), the commit message of this commit, and the changes that were implemented by this commit.

Summary

In this article, we learned about what Git refs and heads are. We also discussed what Git HEAD is and how to manipulate it to view previous commits. Overall, a basic understanding what Git HEAD is will clarify some of the Git tasks that you work with every day. It will also help you better troubleshoot issues and feel more confident about your skills as a software developer.

Next Steps

If you're interested in learning more about how Git works under the hood, check out our Baby Git Guidebook for Developers, which dives into Git's code in an accessible way. We wrote it for curious developers to learn how Git works at the code level. To do this we documented the first version of Git's code and discuss it in detail.

We hope you enjoyed this post! Feel free to shoot me an email at jacob@initialcommit.io with any questions or comments.

Final Notes

Recommended product: Git Guidebook for Developers