how to get git log -p to show changes in merge commits

This leads me to believe that 'git log -p' doesn't show merge changes?

It does, with Git 2.31 (Q1 2021): "git log"(man) learned a new --diff-merges=<how> option.

And with git 2.33 (Q3 2021), git log -m will imply -p (patch) (see at the end of this answer).

See commit af04d8f, commit 1d24509, commit e58142a, commit 8efd2ef, commit b5ffa9e, commit 388091f, commit 5071c75, commit a6d19ec, commit 5733b20, commit 8c0ba52, commit 255a4da, commit 3d2b5f2, commit a6e66af, commit d9b1bc6, commit 1a2c4d8, commit 6fc944d, commit ec315c6, commit 14c14b4, commit e121b4b, commit 0c627f5, commit 3291eea, commit 3b6c17b, commit 09322b1, commit 564a4fc, commit 4f54544, commit 7acf0d0, commit 18f0947, commit a37eec6, commit 3d4fd94, commit 027c478, commit 299a663, commit 891e417 (21 Dec 2020) by Sergey Organov (sorganov).
(Merged by Junio C Hamano -- gitster -- in commit aac006a, 05 Feb 2021)

diff-merges: implement new values for --diff-merges

Signed-off-by: Sergey Organov

We first implement new options as exact synonyms for their original counterparts, to get all the infrastructure right, and keep functional improvements for later commits.

The following values are implemented:

--diff-merges=            old equivalent 
first|first-parent      = --first-parent (only format implications)
sep|separate            = -m
comb|combined           = -c
dense| dense-combined   = `--cc`

diff-merges: add old mnemonic counterparts to --diff-merges

Signed-off-by: Sergey Organov

This adds --diff-merges={m|c|cc} values that match mnemonics of old options, for those who are used to them.

Note that, say, --diff-meres=cc behaves differently than --cc, as the latter implies -p and therefore enables diffs for all the commits, while the former enables output of diffs for merge commits only.

diff-merges: add '--diff-merges=1' as synonym for 'first-parent'

Signed-off-by: Sergey Organov

As we now have --diff-merges={m|c|cc}, add --diff-merges=1 as synonym for --diff-merges=first-parent, to have shorter mnemonics for it as well.

doc/git-show: include --diff-merges description

Signed-off-by: Sergey Organov

Move description of --diff-merges option from git-log.txt to diff-options.txt so that it is included in the git-show(man) help.

diff-options now includes in its man page:

--diff-merges=(off|none|first-parent|1|separate|m|combined|c|dense-combined|cc)

--no-diff-merges

Specify diff format to be used for merge commits. Default is {diff-merges-default} unless --first-parent is in use, in which case first-parent is the default.

--diff-merges=(off|none):

--no-diff-merges:

Disable output of diffs for merge commits. Useful to override implied value.

--diff-merges=first-parent:

--diff-merges=1:

This option makes merge commits show the full diff with respect to the first parent only.

--diff-merges=separate:

--diff-merges=m:

-m:

This makes merge commits show the full diff with respect to each of the parents.
Separate log entry and diff is generated for each parent.
-m doesn't produce any output without -p.

--diff-merges=combined:

--diff-merges=c:

-c:

With this option, diff output for a merge commit shows the differences from each of the parents to the merge result simultaneously instead of showing pairwise diff between a parent and the result one at a time.

Furthermore, it lists only files which were modified from all parents.

-c implies -p.

--diff-merges=dense-combined:

--diff-merges=cc:

--cc:

With this option the output produced by --diff-merges=combined is further compressed by omitting uninteresting hunks whose contents in the parents have only two variants and the merge result picks one of them without modification. --cc implies -p.

--combined-all-paths

This flag causes combined diffs (used for merge commits) to list the name of the file from all parents. It thus only has effect when --diff-merges=[dense-]combined is in use, and is likely only useful if filename changes are detected (i.e. when either rename or copy detection have been requested). endif::git-log[]

git log now includes in its man page:

:diff-merges-default: off

git show now includes in its man page:

DIFF FORMATTING

The options below can be used to change the way git show generates diff output.

git show now includes in its man page:

:diff-merges-default: dense-combined


This is based on the work from Git 2.29 (Q4 2020), formally documented:

See commit 5fbb4bc, commit 9a6d515, commit 6cea104, commit 6f2e02a, commit 9ab89a2, commit 6fae74b, commit eed5332 (29 Jul 2020) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit eca8c62, 17 Aug 2020)

doc/git-log: clarify handling of merge commit diffs

Signed-off-by: Jeff King

It can be surprising that git-log doesn't show any diff for merge commits by default.
Arguably "--cc" would be a reasonable default, but it's very expensive (which is why we turn it on for "git show"(man) but not for "git log").

Let's at least document the current behavior, including the recent "--first-parent implies -m" case

git log now includes in its man page:

Note that unless one of -c, --cc, or -m is given, merge commits will never show a diff, even if a diff format like --patch is selected, nor will they match search options like -S.

The exception is when --first-parent is in use, in which merges are treated like normal single-parent commits (this can be overridden by providing a combined-diff option or with --no-diff-merges).


And you also have:

To countermand the implicit "-m" option when the "--first-parent" option is used with "git log"(man) , we added the "--[no-]diff-merges" option in the jk/log-fp-implies-m topic.

To leave the door open to allow the "--diff-merges" option to take values that instructs how patches for merge commits should be computed (e.g. "cc"? "-p against first parent?"), redefine "--diff-merges" to take non-optional value, and implement "off" that means the same thing as "--no-diff-merges".

See commit 298889d, commit 405a2fd, commit 6501580 (06 Aug 2020) by Sergey Organov (sorganov).
(Merged by Junio C Hamano -- gitster -- in commit a555b51, 17 Aug 2020)

doc/git-log: describe --diff-merges=off

Signed-off-by: Sergey Organov

git log now includes in its man page:

--diff-merges=off

--no-diff-merges

Disable output of diffs for merge commits (default).
Useful to override -m, -c, or --cc.


With Git 2.32 (Q2 2021), "git log"(man) learned --diff-merges=<style> option, with an associated configuration variable log.diffMerges.

See commit 364bc11, commit 17c13e6, commit 38fc4db, commit 26a0f58, commit 4320815 (13 Apr 2021) by Sergey Organov (sorganov).
(Merged by Junio C Hamano -- gitster -- in commit 59bb0aa, 30 Apr 2021)

diff-merges: introduce log.diffMerges config variable

Signed-off-by: Sergey Organov

New log.diffMerges configuration variable sets the format that --diff-merges=on will be using.
The default is "separate".

Added documentation for log.diffMerges.

git config now includes in its man page:

log.diffMerges

Set default diff format to be used for merge commits. See --diff-merges in git log for details. Defaults to separate.


With Git 2.33 (Q3 2021), the "-m" option in "git log"(man) -m that does not specify which format, if any, of diff is desired did not have any visible effect; it now implies some form of diff (by default "--patch") is produced.

See commit f5bfcc8, commit fd16a39, commit 1e20a40, commit 23f6d40, commit 19b2517, commit e0b1642, commit 3ae7fe2, commit faf16d4, commit 48229c1, commit 7a55fa0 (21 May 2021) by Sergey Organov (sorganov).
(Merged by Junio C Hamano -- gitster -- in commit 8e444e6, 14 Jun 2021)

diff-merges: let "-m" imply "-p"

Signed-off-by: Sergey Organov

Fix long standing inconsistency between -c/--cc that do imply -p on one side, and -m that did not imply -p on the other side.

Change corresponding test accordingly, as "log -m" output should now match one from "log -m -p", rather than from just "log".

Change documentation accordingly.

NOTES:

After this patch

git log -m

produces diffs without need to provide -p as well, that improves both consistency and usability.
It gets even more useful if one sets "log.diffMerges" configuration variable to "first-parent" to force -m produce usual diff with respect to first parent only.

This patch, however, does not change behavior when specific diff format is explicitly provided on the command-line, so that commands like

git log -m --raw
git log -m --stat

are not affected, nor does it change commands where specific diff format is active by default, such as:

git diff-tree -m

It's also worth to be noticed that exact historical semantics of -m is still provided by --diff-merges=separate.

diff-options now includes in its man page:

the default format. The default format could be changed using

diff-options now includes in its man page:

-m implies -p.

diff-options now includes in its man page:

This is the format that -m produced historically.


Note: With Git 2.33 (Q3 2021), earlier "git log -m"(man) was changed to always produce patch output, which would break existing scripts, which has been reverted.

See commit 6a38e33 (05 Aug 2021) by Jonathan Nieder (artagnon).
(Merged by Junio C Hamano -- gitster -- in commit 4c90d89, 11 Aug 2021)

Revert 'diff-merges: let "-m" imply "-p"'

Signed-off-by: Jonathan Nieder

This reverts commit f5bfcc8 (diff-merges: let , 2021-05-21, Git v2.33.0-rc0 -- merge listed in batch #2), which made "git log -m"(man) imply --patch by default.
The logic was that "-m", which makes diff generation for merges perform a diff against each parent, has no use unless I am viewing the diff, so we could save the user some typing by turning on display of the resulting diff automatically.
That wasn't expected to adversely affect scripts because scripts would either be using a command like "git diff-tree"(man) that already emits diffs by default or would be combining -m with a diff generation option such as --name-status.
By saving typing for interactive use without adversely affecting scripts in the wild, it would be a pure improvement.

The problem is that although diff generation options are only relevant for the displayed diff, a script author can imagine them affecting path limiting.
For example, I might run

git log -w --format=%H -- README

hoping to list commits that edited README, excluding whitespace-only changes.
In fact, a whitespace-only change is not TREESAME so the use of -w here has no effect (since we don't apply these diff generation flags to the diff_options struct rev_info::pruning used for this purpose), but the documentation suggests that it should work

Suppose you specified foo as the <paths>. We shall call
commits that modify foo !TREESAME, and the rest TREESAME. (In
a diff filtered for foo, they look different and equal,
respectively.)

and a script author who has not tested whitespace-only changes wouldn't notice.

Similarly, a script author could include

git log -m --first-parent --format=%H -- README

to filter the first-parent history for commits that modified README.
The -m is a no-op but it reflects the script author's intent.
For example, until 1e20a40 (stash list: stop passing , 2021-05-21, Git v2.33.0-rc0 -- merge listed in batch #2) (stash list: stop passing "-m" to "git log", 2021-05-21), "git stash list"(man) did this.

As a result, we can't safely change "-m" to imply "-p" without fear of breaking such scripts.
Restore the previous behavior.

Noticed because Rust's src/bootstrap/bootstrap.py made use of this same construct: https://github.com/rust-lang/rust/pull/87513.
That script has been updated to omit the unnecessary "-m" option, but we can expect other scripts in the wild to have similar expectations.

diff-options now includes in its man page:

-m will produce the output only if -p is given as well.


This leads me to believe that 'git log -p' doesn't show merge changes? How can I get this functionality?

You are correct: by default, git log -p shows you the merge commit, but does not even attempt to show a diff for it.

As odradek wrote in a comment, adding the -c option will make git log show combined diffs. You may also use --cc (note two dashes for --cc, vs one dash for -c) which also shows a combined diff, or -m, which effectively splits the merge for diff purposes, and shows one diff per parent, against that parent.

These same three options can be used with git show. For whatever reason, though, git show defaults to --cc while git log defaults to nothing at all.

The difference between the three options is clear only with some merges, and it's a bit tricky to show them. We can, however, say one thing pretty clearly: any combined diff shows only files that differ from all parents. That is, both git show -c and git show --cc trim what's shown to try to be helpful. (The --cc form may trim more than -c, depending on what there is that can be shown. I do not have a handy example of this, though.)

For example, consider commit 3e5c63943d35be1804d302c0393affc4916c3dc3 in the Git repository for Git. This is a merge (with parents c13c783... and 20690b2..., and if we run two separate git diff commands, we can see that, as compared to its first parent, only two files change:

$ git diff --name-status 3e5c639^1 3e5c639
M       builtin/remote.c
M       t/t5505-remote.sh

but as compared to its second parent, many files (including those same two) change:

$ git diff --name-status 3e5c639^2 3e5c639 | expand
M       .gitignore
M       .mailmap
M       Documentation/Makefile
A       Documentation/RelNotes/2.12.0.txt
M       Documentation/SubmittingPatches
A       Documentation/asciidoctor-extensions.rb
M       Documentation/blame-options.txt
M       Documentation/cat-texi.perl
M       Documentation/config.txt
M       Documentation/diff-config.txt
[snipped here - but the same two files do appear in the 339-entry list]

If I run git show --cc on this, I get no diff listing at all; if I run git show -c on it, I get a diff listing for builtin/remote.c and for t/t5505-remote.sh.

If I run git show -m on this, I get instead two separate git diff listings. Neither is a "combined diff". The first starts with:

commit 3e5c63943d35be1804d302c0393affc4916c3dc3 (from
 c13c783c9d3d7d3eff937b7bf3642d2a7fe32644)

and shows just the first two files. The second starts with:

commit 3e5c63943d35be1804d302c0393affc4916c3dc3 (from
 20690b213975ad3e9b1bc851f434d818bd2d1de9)

and shows all 339 files.

Using -m is the Really Big Hammer, but you are left with a lot of sorting to do through a lot of pieces. Using --cc or -c is usually sufficient.

(Something else that may help, when looking for this sort of change with git log -p, is to add --full-history to make sure that git log follows both branches down each merge. This is only required if you are doing history simplification by adding -- <path> options.)

Tags:

Git