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/HEADcontainsref: refs/heads/maingit statussaysOn 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/HEADcontains the full commit ID775b2b399fb8b13ee3341e819f2aaa024a37fa92git statussaysHEAD 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/HEADcontainsca182053c7710a286d72102f4576cf32e0dafcfbgit statussaysHEAD 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/HEADcontains the commit ID of the rebase target, e.g.c694cf8aabe2148b2299a988406f3395c0461742git statussaysinteractive 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:
HEADcontainsref: refs/heads/maingit statusfails withfatal: this operation must be run in a work treegit checkoutdoesn’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.



