Understanding HEAD in Git
HEAD is Git’s answer to “where am I?” It is a single file, .git/HEAD, and almost always it
contains not a commit ID but a pointer to a branch:
ref: refs/heads/mainThat indirection — HEAD points at a branch, the branch points at a commit — is the mechanism behind
the current branch, and understanding it explains detached HEAD, HEAD~2, and most of what reset
and switch do.
The short answer
Section titled “The short answer”HEAD names the commit your working tree was checked out from, and the commit your next commit will have as its parent.
It does this indirectly. HEAD holds a symbolic reference to a branch; the branch holds a commit ID. Read one, then the other, and you have the commit.
A three-step chain. The file .git/HEAD contains the text ref colon refs slash heads slash main. That points to the file .git/refs/heads/main, which contains a forty-character commit ID beginning ff3c99c. That points to the commit itself, which contains a tree, a parent, an author and a message. A note reads: HEAD points at a branch, the branch points at a commit.
Seeing it for yourself
Section titled “Seeing it for yourself”The file is plain text. You can read it, though Git provides proper commands:
git symbolic-ref HEADWhat it doesPrints the reference that HEAD points to, if HEAD is symbolic.
Why we run itIt answers “which branch am I on?” using Git's own mechanism rather than reading a file directly.
Expected resultA full ref name such as refs/heads/main. In detached HEAD state it fails, because HEAD is not symbolic.
refs/heads/mainThree related commands cover the common questions:
| Command | Output | Answers |
|---|---|---|
git symbolic-ref HEAD | refs/heads/main | Which ref does HEAD point at? |
git rev-parse --abbrev-ref HEAD | main | What is the branch name? |
git rev-parse HEAD | ff3c99c8a1… | Which commit does that resolve to? |
git rev-parse is the general resolver: give it anything that names a commit, and it returns the full
object ID.
Why the indirection matters
Section titled “Why the indirection matters”Because HEAD points at a branch rather than a commit, committing works the way you expect.
When you run git commit, Git:
- Reads HEAD to find the current branch.
- Creates the commit, with the branch’s current commit as its parent.
- Writes the new commit’s ID into that branch’s ref file.
Step 3 is what moves your branch forward. HEAD itself does not change — it still says
ref: refs/heads/main. The branch it points at now names a different commit.
Before and after a commit. Before: commits A, B and C in a row, with main pointing at C and HEAD pointing at main. After: a new commit D has been added after C. Main now points at D, and HEAD still points at main. The caption notes that HEAD itself never changed.
Switching branches works the other way: git switch feature rewrites .git/HEAD to
ref: refs/heads/feature, then updates the index and working tree to match that branch’s commit.
HEAD notation: ~ and ^
Section titled “HEAD notation: ~ and ^”You rarely want only the current commit. Git provides suffixes for walking backwards through history.
~ — go back N generations
Section titled “~ — go back N generations”HEAD~N means “N commits back, following the first parent each time.”
ff3c99c commit 4 ← HEAD83a2328 commit 3 ← HEAD~17f1af9c commit 2 ← HEAD~2317c186 commit 1 ← HEAD~3HEAD~ with no number means HEAD~1. HEAD~0 is HEAD itself.
^ — select which parent
Section titled “^ — select which parent”HEAD^N means “the Nth parent of HEAD” — not N steps back, but which parent to follow at this commit.
On a normal commit with one parent, HEAD^ and HEAD~1 are identical, which is why they seem
interchangeable. They diverge at merge commits, which have two or more parents:
* 3b350af Merge feature ← HEAD|\| * 6ba07dd feature work ← HEAD^2 (second parent: the branch merged in)|/* ff3c99c commit 4 ← HEAD^1 and HEAD~1 (first parent: the branch merged into)| Expression | Resolves to | Meaning |
|---|---|---|
HEAD | 3b350af | The merge commit |
HEAD^1 | ff3c99c | First parent — the branch you were on |
HEAD^2 | 6ba07dd | Second parent — the branch you merged in |
HEAD~1 | ff3c99c | One step back, following the first parent |
HEAD^ | ff3c99c | Shorthand for HEAD^1 |
Detached HEAD
Section titled “Detached HEAD”Sometimes HEAD holds a commit ID directly instead of a symbolic ref:
83a232882cf16f99552b6c02632b2e65e39b3219This is detached HEAD. It is not an error, not corruption, and not something to panic about. It simply means you have checked out a commit rather than a branch.
Git tells you clearly:
git switch --detach HEAD~2HEAD is now at 83a2328 commit 3And git status opens with it:
HEAD detached at 83a2328git rev-parse --abbrev-ref HEAD returns the literal string HEAD rather than a branch name — a
useful check in scripts.
How you get there
Section titled “How you get there”git checkout <commit-id>— checking out a commit rather than a branch.git switch --detach <commit>— the explicit, modern way to do it deliberately.git checkout <tag>— tags name commits, not branches.- During an interactive rebase, which replays commits with HEAD detached.
Why it exists
Section titled “Why it exists”It is genuinely useful: it lets you inspect, build or test an arbitrary historical state without creating a branch. “What did the code look like when this bug was introduced?” is a detached-HEAD question.
The one real hazard
Section titled “The one real hazard”Commits made while detached belong to no branch. HEAD moves forward to each new commit, but no branch ref follows. Switch away and nothing points at that work.
Git warns you clearly:
Warning: you are leaving 1 commit behind, not connected toany of your branches:
1a43e1f work made while detached
If you want to keep it by creating a new branch, this may be a good timeto do so with:
git branch <new-branch-name> 1a43e1fWorking deliberately in detached HEAD
Section titled “Working deliberately in detached HEAD”To look around and change nothing, detach, inspect, and switch back:
git switch --detach v1.2.0# … build, test, read …git switch -git switch - returns to the previous branch, like cd -.
To turn detached work into a branch:
git switch -c my-new-branchThis creates a branch at the current commit and attaches HEAD to it. Everything you committed is now safely referenced.
Recovering from a lost HEAD
Section titled “Recovering from a lost HEAD”Git keeps a local log of everywhere HEAD has pointed. This is the reflog, and it is the reason most Git mistakes are recoverable.
git reflogWhat it doesLists recent positions of HEAD, newest first, with the operation that moved it.
Why we run itIt is the fastest way to find a commit you can no longer reach from any branch — after a detached-HEAD session, a bad reset, or a deleted branch.
Expected resultOne line per movement, showing the commit, a HEAD@{N} selector, and a description such as commit, checkout or merge.
3b350af HEAD@{0}: checkout: moving from 1a43e1f04e32… to main1a43e1f HEAD@{1}: commit: work made while detached83a2328 HEAD@{2}: checkout: moving from main to HEAD~23b350af HEAD@{3}: merge feature: Merge made by the 'ort' strategy.ff3c99c HEAD@{4}: checkout: moving from feature to mainHEAD@{1} is the commit made while detached. To rescue it:
git branch recovered 1a43e1fNow a branch points at it, and it is as safe as any other commit.
Other HEAD-like names
Section titled “Other HEAD-like names”Git creates several sibling references that follow the same pattern — a file in .git holding a commit
ID. You will see them in documentation and error messages.
| Name | Set by | Holds |
|---|---|---|
@ | Always available | A pure alias for HEAD — git show @ and git show HEAD are identical |
ORIG_HEAD | reset, merge, rebase | Where HEAD was before the operation |
MERGE_HEAD | A merge in progress | The commit being merged in; exists only until the merge completes or is aborted |
FETCH_HEAD | git fetch | What the last fetch retrieved |
HEAD@{N} | The reflog | Where HEAD pointed N movements ago |
@{-1} | Branch switching | The previously checked-out branch — what git switch - uses |
ORIG_HEAD is the useful one to remember. After an operation that moved HEAD unexpectedly, it names
where you were:
git reset --hard ORIG_HEADHEAD and reset — a beginner-safe introduction
Section titled “HEAD and reset — a beginner-safe introduction”git reset moves the current branch to point at a different commit. Because HEAD points at that
branch, HEAD effectively moves with it.
It has three modes, and they differ in how much they touch beyond the branch pointer:
| Mode | Branch ref | Index | Working tree | Risk |
|---|---|---|---|---|
--soft | Moves | Untouched | Untouched | Safe — changes become staged |
--mixed (default) | Moves | Reset | Untouched | Safe — changes become unstaged |
--hard | Moves | Reset | Overwritten | Destroys uncommitted work |
The genuinely useful, low-risk case is undoing a commit you just made while keeping the work:
git reset --soft HEAD~1The commit is no longer on the branch, and everything it contained is sitting staged, ready to recommit differently.
Reset gets full treatment in a later cluster on undoing changes. For now: know that it moves the
branch under HEAD, and that --hard is the mode to respect.
Common mistakes and messages
Section titled “Common mistakes and messages”“HEAD is a commit.” HEAD is a reference. It usually points at a branch, which points at a commit. The distinction is what makes commits move branches.
Thinking detached HEAD is broken. It is a normal state. git switch - or git switch main returns
you to a branch.
Losing detached commits. Create a branch before switching away. If you already switched, the reflog still has the ID.
Confusing HEAD^2 with HEAD~2. ^2 is the second parent (merges only); ~2 is two commits back.
fatal: ambiguous argument 'HEAD': unknown revision. Usually means the repository has no commits
yet, so HEAD points at a branch that does not exist. Make a commit first.
fatal: you are on a branch yet to be born. The same situation, seen from a different command.
Normal in a freshly initialised repository.
error: Your local changes … would be overwritten by checkout. Git is refusing to move HEAD
because doing so would destroy uncommitted work. Commit, stash, or discard first.
Using ^ in PowerShell or cmd. On Windows, ^ is an escape character in cmd.exe and needs
quoting in some shells. HEAD~1 avoids the problem entirely, as does quoting: git show "HEAD^".
Mental Model
Section titled “Mental Model”HEAD is a you-are-here marker.
Normally it is pinned to a branch label, and the label is pinned to a commit. Make a commit and the label slides forward, carrying the marker with it.
In detached HEAD, the marker is stuck directly to a commit with no label. Work you do there has nothing holding it once you move the marker away.
What You Learned
Section titled “What You Learned”- HEAD is
.git/HEAD, normally containing a symbolic reference such asref: refs/heads/main. - Committing moves the branch ref; HEAD keeps pointing at the same branch.
git symbolic-ref HEAD,git rev-parse --abbrev-ref HEADandgit rev-parse HEADanswer three different questions about it.HEAD~Nwalks back N commits via first parents;HEAD^Nselects the Nth parent at one commit.- They are identical on single-parent commits and differ at merges.
- Detached HEAD means HEAD holds a commit ID directly — a normal, useful state.
- Commits made while detached are referenced by nothing; create a branch to keep them.
git reflogrecords where HEAD has been and is the primary recovery tool.git resetmoves the branch under HEAD;--hardalso overwrites the working tree.
Try It Yourself
Section titled “Try It Yourself”Use a disposable repository with at least four commits.
- Run
cat .git/HEADand confirm it contains aref:line. - Run
git rev-parse HEADandgit rev-parse HEAD~2. Compare againstgit log --oneline. - Run
git switch --detach HEAD~2, thencat .git/HEADagain. What changed? - Run
git statusand read the first line. - Create a file, commit it. Note the abbreviated ID.
- Run
git switch -and read the warning carefully. - Run
git reflogand find the commit from step 5. - Rescue it:
git branch rescued <id>, thengit log --oneline rescued.
Step 6 is the one to slow down for. Seeing the warning in a repository you do not care about is far better than seeing it for the first time in one you do.
Next Lesson
Section titled “Next Lesson”You now know how Git names commits. The next lesson opens the commits themselves — and the blobs and trees they point at — using plumbing commands to read the object database directly.