Skip to main content
← back to field notes
3 min readDevelopment

Walking Back with Git: HEAD^ vs HEAD~ Demystified

gitversion-controltipsdevelopmentdevopsadvanced-git

Ever stared at a Git command with HEAD^ or HEAD~ and wondered if you're about to accidentally nuke your commit history? You're not alone. These two syntaxes look suspiciously similar but behave differently when your repository gets complex. Here's the playbook I'd run to understand when to use which one.

The Core Difference

Both HEAD^ and HEAD~ help you reference commits relative to your current position, but they navigate your Git history differently:

  • HEAD~ follows the first parent linearly through time
  • HEAD^ lets you choose which parent in merge scenarios

Think of HEAD~ as your trusty time machine that always takes the main path backward, while HEAD^ is more like a GPS that lets you pick which road to take at every intersection.

Linear History: They're Twins

When you're working with a simple, linear commit history, HEAD^ and HEAD~ are practically identical:

In this scenario:

  • HEAD~1 = HEAD^1 = commit B
  • HEAD~2 = HEAD^2 = commit A

Both syntaxes walk backward one step at a time along the same path.

Where Things Get Interesting: Merge Commits

The real difference emerges when merge commits enter the picture. Let's say you merged a feature branch:

Now HEAD points to merge commit M, which has two parents: C (from main) and E (from feature).

Here's where the magic happens:

  • HEAD^1 = commit C (first parent - the main branch)
  • HEAD^2 = commit E (second parent - the feature branch)
  • HEAD~1 = commit C (always follows first parent)
  • HEAD~2 = commit B (continues following first parent)

The Tilde (~) Rules

The tilde operator follows a simple pattern - it always takes the first parent and counts backward:

# Starting from merge commit M
git show HEAD~1 # Shows commit C
git show HEAD~2 # Shows commit B
git show HEAD~3 # Shows commit A

The tilde completely ignores the feature branch - it's laser-focused on the main development line.

The Caret (^) Flexibility

The caret operator gives you more control. You can specify which parent to follow:

# From merge commit M
git show HEAD^1 # First parent (C)
git show HEAD^2 # Second parent (E)
git show HEAD^1^1 # C's parent (B)
git show HEAD^2^1 # E's parent (D)

You can also stack them:

Real-World Scenarios

Scenario 1: Undoing the Last Commit on Main

# Both work the same in linear history
git reset HEAD~1
git reset HEAD^1

Scenario 2: Checking What Got Merged

# See the tip of the merged branch
git show HEAD^2

# See the entire feature branch
git log HEAD^2 --oneline

Scenario 3: Comparing Merge Parents

# What changed between main and the feature?
git diff HEAD^1..HEAD^2

# What did the merge actually do?
git show HEAD

Advanced Combinations

You can mix and match these operators:

# Go back 2 commits, then take the second parent
git show HEAD~2^2

# Take the second parent, then go back 1 commit
git show HEAD^2~1

For complex repository archaeology, this becomes incredibly powerful.

Pro Tips for Daily Use

Use Aliases for Common Patterns

Add these to your .gitconfig:

[alias]
parent1 = show HEAD^1
parent2 = show HEAD^2
back1 = show HEAD~1
back2 = show HEAD~2

Quick Branch Inspection

# See what's unique to each parent of a merge
git log HEAD^1 --oneline --not HEAD^2
git log HEAD^2 --oneline --not HEAD^1

Reset Strategies

# Undo merge but keep both branches intact
git reset HEAD^1

# Go back before the merge entirely
git reset HEAD~1

When to Use Which

Use HEAD~ when:

  • You want to walk back through the main development timeline
  • You're working with mostly linear history
  • You need to reference "X commits ago" regardless of merges

Use HEAD^ when:

  • You need to inspect specific parents of merge commits
  • You're debugging what got merged from where
  • You want to selectively navigate branch structures

Key Takeaways

  • HEAD~ follows the first parent linearly - your main timeline
  • HEAD^ lets you choose which parent to follow at merges
  • In linear history, they're functionally identical
  • Both can be stacked and combined for complex navigation
  • Master both for confident Git archaeology

The difference might seem subtle, but once you internalize it, you'll navigate Git history like you're reading a map instead of stumbling through a maze. Your future self will thank you when you're debugging that gnarly merge from three months ago.

field notes

you may also enjoy

more from this thread of thought

working with git: archive
Aug 5, 2025

working with git: archive

read more →
Surprise Driven Development
Jul 10, 2025

Surprise Driven Development

read more →
What's in a .git? A Deep Dive into Git's Hidden Engine
Jun 3, 2025

What's in a .git? A Deep Dive into Git's Hidden Engine

read more →
Git Worktrees: Multiple Branches, Zero Context Switching
May 30, 2025

Git Worktrees: Multiple Branches, Zero Context Switching

read more →
New Project Release: Pride Flags
May 5, 2025

New Project Release: Pride Flags

read more →
Git commits as documentation
Apr 29, 2025

Git commits as documentation

read more →
the field notes

recently written