1. Introduction

Git is a ubiquitous and versatile versioning system. Its workflow manages data between the three Git structures on its way to the final one – the commit tree. Due to the many commits that this main Git tree contains, for convenience, more important ones have special indicators such as a ref (reference) like branch and tag names.

In this tutorial, we explore the functions of and differences between Git HEAD and the primary branch. First, we go over a usual commit tree and understand two of its primary refs (references). After that, we turn to the function and behavior of HEAD. Next, we explain the idea behind Git primary branches. Finally, we outline the differences between the two concepts.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15 and Git 2.39.2. Unless otherwise specified, it should work in most POSIX-compliant environments.

2. Exploring the Commit Tree

The commit tree is the main structure of information that holds each commit. In turn, a commit is a snapshot of the working tree state at a given point in time.

If we navigate around the commit tree, two main refs always exist:

  • primary branch name
  • HEAD

In earlier versions of Git, the primary branch name was master, while more recent versions call it main. Usually, this is more like the root of the repository, where all other commits can trace their roots. However, although that’s rarely the case, the main branch isn’t necessarily the trunk of the tree.

On the other hand, HEAD is like the magnetic head of an older hard disk drive (HDD). In stricter terms, HEAD is a dynamic pointer to the commit that reflects the current working directory (tree) state.

So, in general, master or main is the first and usually primary branch, while HEAD points to the current commit.

Let’s see this in action:

$ git log --all --decorate --oneline --graph
* 8a62fcb (branch1) branch feature
| * 94884cd (branch2) secondary feature WIP
| * 89da3cd secondary branch modifications
|/
| * 02d0395 (branch3) experimental
|/
* 37929d5 branch modifications
| * f5262a4 (HEAD -> master) new structure
| * a403ae7 wipeout
| * bf9d913 major modifications
| * baf6427 minor modifications
|/
* 7340bc4 restructuring
* ef49b0f structuring
* d0c4fb9 init commit

In this case, we can see that the master branch is the longest one, but branch1 is used as the trunk of the graph due to its many forks. This is rare but entirely possible.

Further, HEAD points to the latest master commit, which at times can result in confusion about the difference between the two.

3. HEAD

As already outlined, HEAD is a dynamic pointer.

Specifically, HEAD can change with many activities:

  • checkout: switch branches or restore files (HEAD moves to the latest branch commit)
  • commit: create a new commit (HEAD is now that commit)
  • fetch: get remote objects and refs (HEAD can change if the underlying ref changes)
  • merge: join branches and commits (HEAD can change if the underlying ref changes)
  • rebase: change history (HEAD can change if its underlying ref changes)
  • reset: set HEAD
  • pull: get remote objects and refs, integrate (HEAD can change if the underlying ref changes)
  • revert: undo commits (HEAD changes according to the latest commit of the current branch)
  • switch: switch branches (HEAD moves to the latest branch commit)

In practice, few of these actions move the actual HEAD, instead modifying the ref that it points to. That’s because HEAD is actually a double indirection:

  • HEAD points to ref
  • ref points to commit

So, touching any one of these relations results in a HEAD change.

4. Primary Branch (master, main)

In Git, the primary branch of a repository is the one that gets created upon initialization.

4.1. Basics

Although any branch is a pointer to a single ref, the latter changes with each new commit operation to that branch. Thus, a branch is often seen as a linear structure starting from the commit where the branch was created and ending at the latest branch commit:

$ git log --all --decorate --oneline --graph
* 8a62fcb (branch1) branch feature
| * 94884cd (branch2) secondary feature WIP
| * 89da3cd secondary branch modifications
|/
| * 02d0395 (branch3) experimental
|/
* 37929d5 branch modifications
| * f5262a4 (HEAD -> master) new structure
| * a403ae7 wipeout
| * bf9d913 major modifications
| * baf6427 minor modifications
|/
* 7340bc4 restructuring
* ef49b0f structuring
* d0c4fb9 init commit

Looking at our earlier example, branch1 starts at commit 37929d5 and includes one more commit: 8a62fcb. On the other hand, the primary branch (master) starts at d0c4fb9 and concludes with f5262a4, i.e., the current HEAD. However, this can be temporary.

Consequently, operations that change HEAD can also change any branch, including the primary one. When it comes to the example above, most actions that change master usually affect HEAD equivalently, which can result in confusion. Most, but not all.

4.2. Checkout

Let’s see what happens when we check out branch1:

$ git checkout branch1
Switched to branch 'branch1'
$ git log --all --decorate --oneline --graph
* 8a62fcb (HEAD -> branch1) branch feature
| * 94884cd (branch2) secondary feature WIP
| * 89da3cd secondary branch modifications
|/
| * 02d0395 (branch3) experimental
|/
* 37929d5 branch modifications
| * f5262a4 (master) new structure
| * a403ae7 wipeout
| * bf9d913 major modifications
| * baf6427 minor modifications
|/
* 7340bc4 restructuring
* ef49b0f structuring
* d0c4fb9 init commit

At this point, we can see that HEAD is away from master and points to the latest branch1 commit. Thus, we can see the functional difference between HEAD and the primary branch. Of course, the same happens with a switch or tag checkout.

4.3. Change Default Name

Indeed, the primary branch of a repository is usually called master or main.

However, we can change this name for a single project:

$ git branch --move master main

In this case, we assume the default branch was called master, and we –move (rename) it to main along with its configuration and reflog. Notably, if we have a remote, we usually run two more commands:

$ git push --set-upstream origin main
$ git push origin --delete master

These commands should sync the local and remote branches. Yet, this won’t directly work for platforms like GitHub, which protects the default branch and doesn’t allow its deletion. In those instances, we should perform actions specific to the platform to remove those restrictions and move the new default before deleting the old branch.

On the other hand, we can also initiate all repositories with another primary branch name globally:

$ git config --global init.defaultBranch main

After this command, the default primary branch name for new repositories should be main.

However, due to naming restrictions, we can’t name any branch HEAD:

$ git branch --move master HEAD
fatal: 'HEAD' is not a valid branch name

This way, we demonstrate the terminological difference between HEAD and the primary branch.

5. Differences Between HEAD and Primary Branch

To summarize, let’s enumerate how HEAD and the primary branch (master, main) differ:

+--------------+-----------------------------------------------+------------------------------------------------------+
|              | HEAD                                          | Primary Branch                                       |
+--------------+-----------------------------------------------+------------------------------------------------------+
| Name         | HEAD                                          | master, main, or according to setting and repository |
| Type         | commit pointer                                | commit pointer                                       |
| Indirections | two                                           | one                                                  |
| Function     | track current commit as working tree snapshot | track latest primary branch commit                   |
| Changes      | checkout, commit, fetch, merge                | checkout, commit, fetch, merge                       |
|              | rebase, reset, pull, revert, switch           | rebase, reset, pull, revert, switch                  |
+--------------+-----------------------------------------------+------------------------------------------------------+

Naturally, some behaviors overlap, but most differ.

6. Summary

In this article, we talked about the Git concepts of HEAD and the primary branch.

In conclusion, although HEAD can look and behave like the latest commit of the master or main branch, this is often a temporary state due to the dynamic nature of HEAD.