What “current branch” really means in Git

Git’s glossary defines the “current branch” as whatever is in .git/HEAD. That sounds precise, but in practice the term is used for several related ideas that don’t always line up. Depending on context, “current branch” might mean the contents of .git/HEAD, what git status reports, the branch you last checked out, or what your shell prompt shows. These four interpretations disagree more often than you’d expect.

The experiments below were run with git version 2.39.2 (Apple Git-143).

The easy case: normal branch checkout

Right after git checkout main, all four definitions agree:

  • .git/HEAD contains ref: refs/heads/main
  • git status says On branch main
  • The most recent checkout was main
  • The shell prompt shows (main)

That’s the common case. Things get stranger with detached HEAD states and in-progress operations.

Detached HEAD by commit ID

Checking out a specific commit with git checkout 775b2b399 produces near-unanimity again:

  • .git/HEAD contains the full commit ID 775b2b399fb8b13ee3341e819f2aaa024a37fa92
  • git status says HEAD detached at 775b2b39
  • The most recent checkout was 775b2b399
  • The shell prompt shows ((775b2b39))

All sources agree on the commit; they differ only in truncation.

Detached HEAD by tag

Checking out a tag like v1.0.13 makes .git/HEAD and the other indicators diverge. Git stores the commit ID in .git/HEAD, but both git status and a shell prompt will show the tag name:

  • .git/HEAD contains ca182053c7710a286d72102f4576cf32e0dafcfb
  • git status says HEAD detached at v1.0.13
  • The most recent checkout was v1.0.13
  • The shell prompt shows ((v1.0.13))

Git is being helpful: commit IDs are opaque, so when a tag points at the current commit, git status shows the tag instead. This tag look-up is done via git describe HEAD --tags --exact-match, a feature added to git-prompt.sh in 2008 (commit 27c578885).

Multiple tags on the same commit cause even the two user-facing indicators to disagree. If both v1.0.12 and v1.0.13 point at the same commit, the prompt and git status can display different tag names:

bork@grapefruit ~/w/int-exposed ((v1.0.12))> git status
HEAD detached at v1.0.13

Here the prompt shows v1.0.12 while git status shows v1.0.13. Git doesn’t actually remember that you checked out the tag rather than the commit — both path produce identical behavior.

Mid-rebase state

Starting a rebase on main and stopping at a merge conflict creates a state that looks like a detached HEAD but behaves differently:

  • .git/HEAD contains the commit ID of the rebase target, e.g. c694cf8aabe2148b2299a988406f3395c0461742
  • git status says interactive rebase in progress; onto c694cf8
  • The most recent checkout was main
  • The shell prompt shows (main|REBASE-i 1/1)

There’s a good argument that main is the current branch: it’s what you checked out, where you’ll return after the rebase completes, and where git rebase --abort takes you. But technically HEAD is a detached commit. The usual consequence of being detached — getting an orphaned commit if you make one — doesn’t apply here; a new commit becomes part of the rebase instead. The original branch name appears to live in .git/rebase-merge/head-name during the operation.

Empty and bare repositories

In a fresh git init repository, .git/HEAD points at refs/heads/main and git status says On branch main (plus No commits yet), even though no checkout ever happened. Git implicitly switched to the branch configured in init.defaultBranch.

Bare repositories are the oddest case. Cloning one with git clone --bare shows:

  • HEAD contains ref: refs/heads/main
  • git status fails with fatal: this operation must be run in a work tree
  • git checkout doesn’t work at all
  • The shell prompt shows (BARE:main)

The HEAD in a bare repo matters mainly for one thing: determining which branch a fresh clone checks out. If needed, HEAD can be repointed with git symbolic-ref HEAD refs/heads/whatever, though this command doesn’t verify the target branch exists.

The results

.git/HEAD git status checked out prompt
1. checkout main ref: refs/heads/main On branch main main (main)
2. checkout 775b2b 775b2b399... HEAD detached at 775b2b39 775b2b399 ((775b2b39))
3. checkout v1.0.13 ca182053c... HEAD detached at v1.0.13 v1.0.13 ((v1.0.13))
4. inside rebase c694cf8aa... interactive rebase in progress; onto c694cf8 main (main|REBASE-i 1/1)
5. after git init ref: refs/heads/main On branch main n/a (main)
6. bare repository ref: refs/heads/main fatal: this operation must be run in a work tree n/a (BARE:main)

A fuzzy concept, not a single value

The glossary’s equivalence of HEAD and “current branch” isn’t as ironclad as it sounds. Of all the indicators, .git/HEAD has the most consistent format — it is always a branch reference or a commit ID. The others are deliberately messier because they carry context.

“The branch you last checked out” deserves more respect as a definition. Git works hard to remember it through bisects, merges, and other temporary detours of HEAD. Meanwhile, git status is less a raw indicator than an interpreter — it reports on branch main, HEAD detached at 775b2b39, HEAD detached at v1.0.13, or interactive rebase in progress; onto c694cf8, each telling you more than HEAD’s contents alone.

What the term doesn’t capture is whether the current commit is orphaned — the git status message looks the same whether or not a branch contains HEAD. Checking with git branch --contains HEAD can be slow in large repositories (roughly 500ms at 70,000 commits), so Git defers the orphan warning until you switch branches.