Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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?

+24
−0

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?

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

TL;DR (1 comment)

1 answer

+23
−0

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):

Git repository

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 the switch 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 of git checkout branchname. And for detached HEAD, use git switch commit_hash --detached.


  1. I used Git 2.25.1 in Ubuntu 20. Other versions might give different messages (the concept of "detached HEAD" is the same, though). ↩︎

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »