Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
What is HEAD in Git?
In Git documentation, there are lots of references to the term "HEAD".
But what exactly is it? Some places refer to it as "a pointer to the current branch". So it's a branch? What is it used for?
1 answer
First, we need to understand what a Git repository actually is. For that, refer to this article: it explains that a Git repository is actually a DAG (Directed Acyclic Graph). I'm not going into the mathematical details (which can be checked here), but basically, we can think of a Git repository as a series of nodes pointing to each other.
Some nodes are commits, and they point to other nodes, which can be another commit (each commit points to its parent(s) - yes, some merges can generate commits with more than one parent), or trees (structures that represent a directory/folder structure, which in turn can point to files or another trees):
All nodes are identified by their hash (those "big hex numbers" such as 618ce9936d015012ae55b95ac6afcc286d02682d
). But some commits can also have a "name"/label, called branches (as mentioned here, a branch is just a pointer to a specific commit).
For example, in the following diagram, the master branch points to "second commit", and new_branch points to "third commit". And "first commit" has no branches pointing to it:
+--------------+ +---------------+
| first commit | <-- | second commit | <-- master
+--------------+ +---------------+
↑
+---------------+
| third commit | <-- new_branch
+---------------+
The HEAD is just a pointer to a specific commit (which, in turn, might or might not have a branch pointing to it). As a simplification, many people say that HEAD points to the "current branch" (probably because it's the most common use case), but it can actually point to any commit, regardless of having a branch pointing to it (more details below, in the "HEAD is not always the current branch" section).
Anyway, HEAD is used by Git to know where to add new commits. If my repository was like the diagram above, what would happen when I add a new commit?
If HEAD is pointing to master, it'll be added after "second commit":
Add new commit to branch master
+--------------+ +---------------+ +---------------+
| first commit | <-- | second commit | <-- | fourth commit | <-- master (HEAD)
+--------------+ +---------------+ +---------------+
↑
+---------------+
| third commit | <-- new_branch
+---------------+
But if HEAD is pointing to new_branch, it'll be added after "third commit":
Add new commit to branch new_branch
+--------------+ +---------------+
| first commit | <-- | second commit | <-- master
+--------------+ +---------------+
↑
+---------------+
| third commit | <-- new_branch
+---------------+
↑
+---------------+
| fourth commit | <-- new_branch (HEAD)
+---------------+
To change the HEAD (AKA "to change where HEAD points to"), we do git checkout [branch or commit]
. Therefore, git checkout master
makes HEAD point to master, while git checkout new_branch
makes HEAD point to new_branch.
There are many ways to check where HEAD is pointing to. One is to check the .git/HEAD
file (the .git
folder is in the repository's root). For example, I've created a repository in my machine, made a commit, and this file now contains:
ref: refs/heads/master
This means that HEAD is pointing to the master branch. To be more precise, it's pointing to another file: .git/refs/heads/master
. And this file contains the hash of the respective commit:
618ce9936d015012ae55b95ac6afcc286d02682d
Of course, there are other ways to check this information, such as git status
(at the first line it'll say "On branch master"), or git show HEAD
, that returns lots of info, including the hash of the commit HEAD is pointing to, or git log -1
, etc.
When switching to another branch (git checkout new_branch
), the contents of .git/HEAD
file changed to:
ref: refs/heads/new_branch
Which means that now HEAD is pointing to new_branch, and in the respective file (.git/refs/heads/new_branch
) we can see the hash of the commit it points to.
HEAD is not always the "current branch"
As already said, HEAD doesn't necessarily point to the "current branch". That's a simplification for the most common use case, when HEAD is pointing to a branch where new commits will be added.
But we can also make HEAD point to a commit that has no branches pointing to it. For example, suppose my repository is like this:
+--------------+ +---------------+
| 618ce99 | | 4dc1fe0 |
| first commit | <-- | second commit | <-- master (HEAD)
+--------------+ +---------------+
HEAD is pointing to master (hash 4dc1fe0 - I'm using the "short hash" for the sake of simplicity). If I run git checkout 618ce99
, a message like this will appear:[1]
Note: switching to '618ce99'.
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 618ce99 first
This means that we are in detached HEAD state: HEAD is pointing to a commit that has no branches pointing to it.
In that case, the contents of .git/HEAD
file is no longer "refs: etc", but the hash itself. Therefore, HEAD is not pointing to a branch.
As the message above says, it's still possible to add commits and create a new branch (which will make HEAD not be detached anymore), or you can simply discard everything, without affecting the other existing branches.
Note: The message above contains instructions to use
git switch
, because I'm using Git >= 2.23.0. For older versions, the message will be different because theswitch
command didn't exist.
Therefore, HEAD doesn't necessarily point to a branch. "Pointing to a branch" is the most common use case, but it's not the only possible one. Well, maybe I'm being too pedantic, after all everybody understands when you say that HEAD points to the "current branch" (and a detached HEAD would be a "special case" that you won't need in "most cases").
The thing is, HEAD always points to a specific commit, either directly (when it's detached), or indirectly (when it points to a branch).
Yeah, when HEAD points to a branch, it's indirectly pointing to a commit. Let's take the same repository as an example:
+--------------+ +---------------+
| 618ce99 | | 4dc1fe0 |
| first commit | <-- | second commit | <-- master (HEAD)
+--------------+ +---------------+
The contents of .git/HEAD
file is ref: refs/heads/master
, and the file .git/refs/heads/master
contains the hash 4dc1fe0.
After adding a new commit, the repository will be:
+--------------+ +---------------+ +--------------+
| 618ce99 | | 4dc1fe0 | | 630d06b |
| first commit | <-- | second commit | <-- | third commit | <-- master (HEAD)
+--------------+ +---------------+ +--------------+
The contents of .git/HEAD
file will remain the same (ref: refs/heads/master
), and only the contents of the branch file (.git/refs/heads/master
) will change, to contain the new commit's hash (630d06b). Hence, the branch is changed (it points to another commit), but the HEAD stays the same (it still points to the same branch).
Again, maybe that's too pedantic, because "everybody understands" if we say that HEAD is pointing to the "last commit of the current branch" (or just to the "current branch"). But IMO that's an important detail: HEAD (when it's non-detached) points to a branch, and the branch points to a commit. After adding a new commit, the branch points to this new commit, but HEAD keeps pointing to the same branch (we could say that "branch changes, HEAD doesn't").
And this is true for any operation that makes the branch point to a different commit. For example, if I do git reset --hard 618ce99
, master will point to "first commit" and the repository will be like this:
+--------------+
| 618ce99 |
| first commit | <-- master (HEAD)
+--------------+
And again, the contents of .git/HEAD
file remains the same (ref: refs/heads/master
- HEAD keeps pointing to the same branch), and only the contents of .git/refs/heads/master
file is changed (now it contains the hash 618ce99).
Note: for Git >= 2.23.0, you can use
git switch branchname
instead ofgit checkout branchname
. And for detached HEAD, usegit switch commit_hash --detached
.
-
I used Git 2.25.1 in Ubuntu 20. Other versions might give different messages (the concept of "detached HEAD" is the same, though). ↩︎
1 comment thread