The HEAD Pointer
HEAD is the single most important ref in Git. It answers the question "where am I right now?" — which branch you are on, or which commit you are looking at. Almost every Git operation reads HEAD to know what to base new work on, and many operations update HEAD when they complete. Understanding HEAD makes git checkout, git reset, git rebase, and detached HEAD state all make intuitive sense.
Normal HEAD: A Symbolic Ref
In normal operation, HEAD is a symbolic ref — it does not contain a commit hash directly. Instead, it contains the name of the current branch. When you commit, Git updates the branch ref, and HEAD follows automatically because it points to the branch.
Inspect HEAD content
# HEAD is a file in .git/ cat .git/HEAD # ref: refs/heads/main # Git resolves it: HEAD → refs/heads/main → SHA hash git symbolic-ref HEAD # refs/heads/main # Get the actual commit hash HEAD resolves to git rev-parse HEAD # a3f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b
Normal HEAD diagram
.git/HEAD
│
└── ref: refs/heads/main
│
└── refs/heads/main (file)
│
└── a3f1c2d... (commit hash)
HEAD → main → a3f1c2dSwitching Branches Updates HEAD
What git checkout/switch does to HEAD
# On main branch cat .git/HEAD # ref: refs/heads/main # Switch to feature branch git switch feature/auth cat .git/HEAD # ref: refs/heads/feature/auth # HEAD now follows feature/auth git rev-parse HEAD # 9e2b0f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a
Making a Commit Advances HEAD via the Branch
How committing moves HEAD
# Before commit cat .git/refs/heads/main # a3f1c2d... (old commit) # Make a commit echo "change" >> file.txt git add file.txt git commit -m "New change" # After commit: branch ref was updated cat .git/refs/heads/main # b5e3f4c... (new commit, a3f1c2d is its parent) # HEAD still points to main — it moved along with main git rev-parse HEAD # b5e3f4c...
Detached HEAD State
Detached HEAD means HEAD contains a commit hash directly instead of a branch name. This happens when you check out a commit by hash, a tag, or a remote-tracking branch directly. You are "looking at" a commit that no branch points to.
Entering detached HEAD state
# Checkout by commit hash → detached HEAD git checkout a3f1c2d # HEAD is now at a3f1c2d Add authentication module # Checkout a tag → also detached HEAD git checkout v1.0.0 # HEAD is now at a3f1c2d (tag: v1.0.0) # Checkout a remote branch directly → detached HEAD git checkout origin/main # HEAD is now at a3f1c2d...
Confirm you are in detached HEAD state
cat .git/HEAD # a3f1c2d83e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b ← raw hash, not a ref name git symbolic-ref HEAD # fatal: ref HEAD is not a symbolic ref ← confirms detached state git status # HEAD detached at a3f1c2d
Detached HEAD diagram
Normal HEAD: Detached HEAD:
.git/HEAD .git/HEAD
│ │
└── ref: refs/heads/main └── a3f1c2d... (direct hash)
│
└── a3f1c2d...
HEAD → main → a3f1c2d HEAD → a3f1c2d (no branch involved)Recovering from Detached HEAD
Options for leaving detached HEAD state
# Option 1: go back to where you came from (discard any commits made)
git switch -
# Option 2: create a new branch at the current detached position
git switch -c my-experiment
# Now your detached commits are safe on a new branch
# Option 3: attach to an existing branch (if you haven't committed anything)
git switch main
# Option 4: if you committed in detached state and then moved away,
# find the commit in reflog and create a branch:
git reflog | grep "HEAD@{" | head -10
# HEAD@{1}: commit: My experimental commit
git branch rescue-branch HEAD@{1}ORIG_HEAD
Using ORIG_HEAD to undo operations
# After a merge you regret: git merge feature/big-refactor # ... something went wrong git reset --hard ORIG_HEAD # undo the merge # After a rebase: git rebase main # ... looks wrong git reset --hard ORIG_HEAD # undo the rebase # After an accidental reset: git reset --hard HEAD~5 # oops git reset --hard ORIG_HEAD # undo the reset
Other Special HEAD-like Refs
Ref | Set By | Contains | Purpose |
|---|---|---|---|
HEAD | git switch/checkout/commit | Current branch or commit | Where you are now |
ORIG_HEAD | merge, rebase, reset, am | Previous HEAD value | One-step undo |
MERGE_HEAD | git merge (during conflict) | The other parent being merged | Complete or abort the merge |
CHERRY_PICK_HEAD | git cherry-pick (during conflict) | Commit being cherry-picked | Complete or abort cherry-pick |
REBASE_HEAD | git rebase (during conflict) | Commit being rebased | Complete or abort rebase |
FETCH_HEAD | git fetch | Last fetched branch info | Used by git merge after fetch |
Reading and Writing HEAD Programmatically
git symbolic-ref: read and write HEAD
# Read the current branch name from HEAD git symbolic-ref HEAD # refs/heads/main # Short form (just the branch name) git symbolic-ref --short HEAD # main # Programmatically switch branch by writing HEAD git symbolic-ref HEAD refs/heads/develop # (equivalent to git switch develop, but no working-tree update) # Detach HEAD to a specific commit git checkout --detach HEAD~3
HEAD in Git Commands
Common HEAD relative references
# HEAD = current commit # HEAD~ = parent of HEAD (same as HEAD~1) # HEAD~3 = 3 commits back # HEAD^ = first parent of HEAD (for merge commits) # HEAD^2 = second parent of HEAD (the merged-in branch) git show HEAD # show current commit git diff HEAD~1 # diff current vs one commit ago git log HEAD~5..HEAD # last 5 commits git reset HEAD~1 # undo last commit (keep changes staged) git revert HEAD # create new commit that undoes HEAD