skip to content

In Git, what is the difference between HEAD~2 and HEAD^2?

level: middleimportance: must knowfreq 72%

answer

  1. One counts generations, the other picks a parent
  2. Only meaningful difference on merge commits
  3. HEAD~2 equals HEAD^^
  4. Second parent is the branch that was merged in
  5. Try it on a non-merge commit and see the error

basics

~20 s

In Git, HEAD~2 walks two steps back along the first-parent chain, i.e. the grandparent. HEAD^2 takes one step to the second parent, which exists only on a merge commit; on a non-merge it is an error.

solid answer

~40 s

Both are gitrevisions suffixes but they count different things. `~n` means "go back n generations, always following the first parent", so `HEAD~2` is the parent of the parent and `HEAD~0` is HEAD itself. `^n` selects **which** parent of a single commit, so `HEAD^1` (same as `HEAD^`) is the first parent, `HEAD^2` is the second parent, and on a commit with only one parent `HEAD^2` fails with a message about an invalid revision. A useful identity: `HEAD~2` equals `HEAD^^`, and `HEAD~1` equals `HEAD^`. `HEAD^0` is a special case that simply peels the argument to a commit object, which is how you say "this exact commit" when you want to detach from a branch name. The suffixes chain, so `HEAD~3^2` means the second parent of the commit three generations back.

code

console · 14 lines
console
$ git log --graph --oneline
*   9f1c2ab (HEAD -> main) Merge branch 'feature'
|\
| * 7b3d4ef feature: add parser
* | 2a9c8de main: fix typo
|/
* 4c5e6fa base commit

$ git rev-parse --short HEAD^1
2a9c8de
$ git rev-parse --short HEAD^2
7b3d4ef
$ git rev-parse --short HEAD~2
4c5e6fa

go deeper

for a junior

Remember HEAD~1 and HEAD^ both mean the parent, and that ~n counts generations back. That much covers everyday use like git reset HEAD~1.

for a middle

Explain that ^n selects the nth parent of one commit while ~n walks n first-parent steps, and be able to point at each parent of a merge in a drawn graph.

for a senior

Demonstrate the habit of resolving revisions with git rev-parse before destructive commands, and connect first-parent walking to reverting merges with the correct mainline number.

for a principal

Own the convention question: whether your history keeps first-parent order meaningful enough that tooling and release archaeology can rely on ~ and --first-parent at all.

## Two different questions Git's revision syntax (documented as `gitrevisions`; `git help revisions`) gives you two suffixes that both move backwards in the commit graph, and confusing them is one of the most common Git mistakes. - `<rev>~<n>` answers **"how far back?"** It moves `n` generations, and at every step it takes the *first* parent. `HEAD~1` is the parent, `HEAD~2` the grandparent, `HEAD~0` is HEAD itself. - `<rev>^<n>` answers **"which parent?"** It moves exactly one generation but lets you choose the parent by position. `HEAD^` is shorthand for `HEAD^1`, the first parent. `HEAD^2` is the second parent and only exists on a merge commit. So on a linear stretch of history, `~` and `^` look interchangeable: `HEAD^` and `HEAD~` are the same commit, and `HEAD^^` and `HEAD~2` are the same commit. They diverge the moment a merge is involved. ## On a merge commit Suppose you are on `main`, you merge `feature`, and the resulting merge commit is HEAD. Then: - `HEAD^1` (or `HEAD^`, or `HEAD~1`) is the tip of `main` before the merge — the branch you were on. - `HEAD^2` is the tip of `feature` — the branch you merged in. - `HEAD~2` is the parent of `HEAD^1`, i.e. two steps down the `main` side. It is **not** the feature branch. On a plain single-parent commit, `HEAD^2` is simply invalid and Git reports an unknown or ambiguous revision rather than silently picking something. ## Chaining and the special cases The suffixes compose left to right: `HEAD~3^2~1` means go back three first-parent generations, take that commit's second parent, then that commit's parent. This is how you name a specific commit on a side branch without looking up its hash. `HEAD^0` deserves its own note. Zero as the parent number means "the object itself, peeled to a commit". It is how you detach HEAD at the current commit (`git checkout HEAD^0`) and how you turn an annotated tag into the commit it points at, though `v1.0^{commit}` states that more explicitly. A practical trap on some shells: `^` is a special character in the Windows command prompt, so `HEAD^` may need quoting or you can use `HEAD~` instead. In PowerShell and POSIX shells `~` inside a word is fine, but a bare leading `~` would be tilde-expanded, which is another reason to quote revisions in scripts. ## Verifying rather than guessing Do not reason about a merge from memory — resolve it. `git rev-parse --short HEAD^2` prints the actual commit ID, and `git log --graph --oneline` shows which side is which. In an interview, saying "I would check with `git rev-parse` before running a destructive command against that revision" is exactly the instinct being probed, because `git reset --hard HEAD~2` and `git reset --hard HEAD^2` land in completely different places. ## Where it bites in practice The classic incident is reverting a merge: `git revert -m 1 <merge>` uses parent *number* 1 to say which side is mainline, and picking the wrong number reverts the wrong half of the merge. Range and log filters like `--first-parent` follow the same first-parent convention that `~` walks, which is why a first-parent view of a release branch reads as a list of merges rather than every contributor commit.

  • On a merge commit, which side of the merge is HEAD^2?
    The branch that was merged **in**. The first parent is the branch you had checked out when you ran `git merge`, and the second parent is the incoming one. Resolve it with `git rev-parse HEAD^2` rather than assuming, since the mapping flips if someone merged in the other direction.
  • What does HEAD^0 mean, and why would you use it?
    Parent number zero means the commit itself, peeled to a commit object. `git checkout HEAD^0` detaches HEAD at the current commit instead of staying on the branch name, and `<tag>^{commit}` is the more explicit way to turn an annotated tag into its commit.
  • What happens if you run git rev-parse HEAD^2 on an ordinary commit?
    It fails: the commit has only one parent, so there is no second parent to resolve, and Git reports the revision as unknown rather than falling back to the first parent. Adding `--verify --quiet` lets a script test for that case without printing an error.

~ is "walk n floors down the same staircase"; ^ is "pick which staircase to take on this floor".

saying these in an interview costs you the question

  • Says tilde and caret are interchangeable everywhere
  • Thinks HEAD~2 reaches the merged-in branch
  • Believes HEAD^2 means two commits back
  • Assumes second parent is whichever branch is newer
  • Runs reset --hard against an unverified revision

context