Git’s HEAD Is More Than a Pointer

A recent Mastodon poll asked developers how confident they were in their understanding of HEAD in Git. Roughly 54% of the 1,700 respondents admitted to being only “somewhat confident” or having “literally no idea.” That hesitation hints at hidden complexity: HEAD isn’t one concept but several tightly related ones.

What .git/HEAD Actually Stores

At its core, Git maintains a file named .git/HEAD. Its contents determine your current branch and can be one of two things:

  1. A branch name, written as ref: refs/heads/main
  2. A raw commit ID, such as 96fa6899ea34697257e84865fefc56beb42d6390

When you run git status and see output like “On branch main,” that’s a direct reflection of .git/HEAD containing ref: refs/heads/main. Conversely, if that file holds a commit ID instead, Git reports a “detached HEAD state.” Note that while you technically can edit .git/HEAD to point to a non-branch reference, no standard Git command does this — it’s always a branch or a commit.

$ git status
On branch main

HEAD as a Revision Parameter

In commands like git show HEAD or git diff main..HEAD, HEAD functions as a “revision parameter” (documented in man gitrevisions). Git resolves it to a commit ID in one of two ways:

  1. If .git/HEAD holds a branch name, it resolves to the latest commit on that branch.
  2. If .git/HEAD holds a commit ID, it resolves to that exact commit.

Decoding Git’s Output

Beyond the file itself, HEAD appears in various command outputs, each with slightly different implications.

git status: Branch or Detached

The first line of git status is always one of two forms:

  1. on branch main.git/HEAD points to a branch.
  2. HEAD detached at 90c81c72.git/HEAD points directly to a commit.

A detached HEAD means you have no current branch. Commits made in this state aren’t attached to any branch, making them harder to find and vulnerable to eventual garbage collection. To escape, you can check out an existing branch, create a new one with git checkout -b newbranch, or if you’re mid-rebase, finish or abort it with git rebase --abort.

git log: Reading the Reference List

In git log, the first line may show different parenthetical reference lists. Here’s how to interpret them:

  • HEAD -> main means .git/HEAD contains ref: refs/heads/main, and that branch points to the displayed commit.
  • HEAD, main (with a comma) indicates detached HEAD state — .git/HEAD contains a commit ID, and the branch main also points to that same commit.
  • Just (HEAD) also means detached HEAD, but no branch points to that commit.

Merge Conflicts: A Different Beast

During conflict resolution, markers like <<<<<<< HEAD can be misleading. Their meaning shifts depending on the operation:

<<<<<<< HEAD
def parse(input):
    return input.split("\n")
=======
def parse(text):
    return text.split("\n\n")
>>>>>>> somebranch
  • In a merge, HEAD is simply the branch you were on when you ran git merge.
  • In a rebase, HEAD represents the other commit you’re rebasing onto, not your original branch. This is because rebase checks out the target commit first, then cherry-picks your commits onto it.

Given this context-dependent behavior, many developers find it simpler to ignore HEAD in conflict markers entirely and rely on other cues to identify which side of the conflict is which.

Terminology Gaps Add Confusion

Part of the challenge is Git’s inconsistent language. Git never mentions an “attached HEAD state,” nor does it say you’re “not on” a branch — it just says “HEAD detached.” Without clear opposites, it’s hard to intuit that “on branch main” and “HEAD detached” are two sides of the same coin, or that either one has anything to do with .git/HEAD.

Understanding these layers — the file, the revision parameter, and the output conventions — clarifies what HEAD is doing under the hood, whether you’re on a clean branch, mid-rebase, or deep in conflict resolution.