All Git commits are snapshots. This is true of merge commits as well as of regular boring non-merge commits. The key difference between a merge commit and a non-merge commit is that a merge commit has (at least) two parents, instead of just one.1
When you view ordinary, single-parent commits with git show <hash>
or git log -p
, Git notices that these are in fact ordinary single-parent commits, and so Git extracts the parent commit's snapshot first, and then extracts the commit's snapshot, and then Git runs:
git diff [options] <parent-hash> <commit-hash>
and that tells you what changed between this commit's parent, and this commit.
You can, manually, do precisely the same thing yourself for any commit, including merge commits. But because a merge commit has two parents, you will discover that you must choose one of the two parents before you can produce this diff.
This is why git log -p
doesn't bother showing anything for a merge commit: it's too hard to know which parent to choose. Well, that is, except for the fact that to see what changes the merge brought in to the main line, you usually want the first parent.2 Or, if you do want the second parent, well, just specify that! The git log
command tells you about both parents:
commit 085d2abf57be3e424cad0b7dc8c27fe41921258e
Merge: cf22247b63 c3749f6e59
Author: ...
so here we can run:
git diff cf22247b63 085d2abf57be3e424cad0b7dc8c27fe41921258e
to view the changes between the first parent and the commit, or the obvious other git diff
command to view the changes between the second parent and the commit.
The git show
command, when applied to a merge, will show what Git calls a combined diff by default: it will run a diff against each parent, then throw out from this diff any file that exactly matches either parent, and show you only changes to the remaining files, mostly where conflicts were. If that's insufficient—as it very often is—there is a simple option to apply here:
git show -m 085d2abf57be3e424cad0b7dc8c27fe41921258e
will "split" the commit into two separate virtual commits (neither gets stored anywhere). Git can then diff 085d2abf57be3e424cad0b7dc8c27fe41921258e (from cf22...)
against its first parent first, and then diff 085d2abf57be3e424cad0b7dc8c27fe41921258e (from c374...)
against its second parent. The result is two separate diffs, as if there were two separate, non-merge commits, and this lets you find out what changed.
1A root commit has no parents. Any Git repository with at least one commit has at least one root commit, since the very first commit has no parent.
2This assumes you don't use git pull
, which creates what some call a foxtrot merge.