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:
- A branch name, written as
ref: refs/heads/main - 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:
- If
.git/HEADholds a branch name, it resolves to the latest commit on that branch. - If
.git/HEADholds 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:
on branch main—.git/HEADpoints to a branch.HEAD detached at 90c81c72—.git/HEADpoints 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 -> mainmeans.git/HEADcontainsref: refs/heads/main, and that branch points to the displayed commit.HEAD, main(with a comma) indicates detached HEAD state —.git/HEADcontains a commit ID, and the branchmainalso 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,
HEADis simply the branch you were on when you rangit merge. - In a rebase,
HEADrepresents 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.



