Reputation: 1659
When I have a merge commit and run git show <commit-ish>
, it shows only the commit log, not the the diffs:
commit c0f50178901e09a1237f7b9d9173ec5d1c4936c
Merge: ed234b ded051
Author: abc
Date: Mon Nov 21 15:56:33 2016 -0800
Merge branch 'abc'
I understand the real commit is in merge log, but I want to save typing. Is there a way to show the diff in one?
Upvotes: 165
Views: 87761
Reputation: 1323743
See also git show --diff-merges
, with --diff-merges
introduced with Git 2.31 (Q1 2021).
As mentioned here, those solutions involve showing a combined diff, like:
git diff --cc $M $M^1 $M^2 $(git merge-base $M^1 $M^2)
But: the output from "diff --cc
" did not show the original paths when the
merge involved renames.
A new option in Git 2.22 (Q1 2019) adds the paths in the original trees to the output.
git diff --cc --combined-all-paths $M $M^1 $M^2 $(git merge-base $M^1 $M^2)
log
,diff-tree
: add--combined-all-paths
optionThe combined diff format for merges will only list one filename, even if rename or copy detection is active.
For example, with raw format one might see:
::100644 100644 100644 fabadb8 cc95eb0 4866510 MM describe.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh ::100644 100644 100644 e07d6c5 9042e82 ee91881 RR phooey.c
This doesn't let us know what the original name of
bar.sh
was in the first parent, and doesn't let us know what either of the original names ofphooey.c
were in either of the parents.In contrast, for non-merge commits, raw format does provide original filenames (and a rename score to boot).
In order to also provide original filenames for merge commits, add a--combined-all-paths
option (which must be used with either-c
or--cc
, and is likely only useful with rename or copy detection active) so that we can print tab-separated filenames when renames are involved.This transforms the above output to:
::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c desc.c desc.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM foo.sh bar.sh bar.sh ::100644 100644 100644 e07d6c5 9042e82 ee91881 RR fooey.c fuey.c phooey.c
Further, in patch format, this changes the from/to headers so that instead of just having one "from" header, we get one for each parent.
For example, instead of having--- a/phooey.c +++ b/phooey.c
we would see
--- a/fooey.c --- a/fuey.c +++ b/phooey.c
Upvotes: 9
Reputation: 48743
Modern git has elaborate diff options:
--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r), --no-diff-merges
Specify diff format to be used for merge commits.
Default is dense-combined 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=on, --diff-merges=m, -m
This option makes diff output for merge commits to be shown in the default format.
-m will produce the output only if -p is given as well. The default format
could be changed using log.diffMerges configuration parameter, which default value is separate.
--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
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.
--diff-merges=remerge, --diff-merges=r, --remerge-diff
With this option, two-parent merge commits are remerged to create a temporary
tree object -- potentially containing files with conflict markers and such.
A diff is then shown between that temporary tree and the actual merge commit.
The output emitted when this option is used is subject to change,
and so is its interaction with other options (unless explicitly documented).
--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.
Upvotes: 3
Reputation: 4046
Here's a simple command:
git show HEAD -m
Maybe it's slightly more memorable, given that it mirrors other commonly used commands.
It shows all the changes made to the merged branch as a result of the merge.
Upvotes: 9
Reputation: 487983
Use one of the following:
git show -m c05f017
git show --first-parent c05f017
git diff c05f017^ c05f017
There's a fundamental error in your question: commits are not diffs; commits are snapshots. This might seem like a distinction without a difference—and for some commits, it is. But for merge commits, it's not.
When git show
(or git log -p
) shows a commit as a diff, it's doing so by comparing the commit's snapshot to something else. The git diff
command does the same thing: it compares one commit to another commit. (Or it can compare a commit to the work-tree, or to the contents of the index, or a few other combinations as well.)
For ordinary commits, it's trivially obvious what to compare: compare this commit's snapshot to the previous (i.e., parent) commit's snapshot. So that is what git show
does (and git log -p
too): it runs a git diff
from the parent commit, to this commit.
Merge commits don't have just one parent commit, though. They have two parents.1 This is what makes them "merge commits" in the first place: the definition of a merge commit is a commit with at least two parents.
1A merge commit can have three or more parents. These are called "octopus merges". They don't do anything special, though, and are mainly for showing off. :-) You can ignore them here.
git show
compare against?What git log -p
chooses to do by default is not to compare at all. You can make it show something by adding various flags (see below).
What git show
chooses to do by default is more complicated. Since there are two parents, git show
first compares against the "first parent",2 then compares against the second parent. Then—this part is quite crucial—it combines the two diffs, producing a so-called "combined diff".
For the next section, let me note a tricky, but very useful, bit of Git syntax. If you have a commit ID like c05f017
, you can add a caret or "hat" character ^
after that, to name a parent commit. You can optionally add another number to select which parent. For regular (non-merge) commits there's only one, so c05f017^
is the parent. For merge commits, c05f017^
and c05f017^1
both mean the first parent, while c05f017^2
means the second parent.
2I put this in quotes because the first parent idea is especially important in Git, as we will see in a moment. In other words, Git cares most about which parent is first, while the rest are just "the rest".
The combined diff format is described in the documentation, but a key bit is first described here, so as to make it especially obscure:3
Note that combined diff lists only files which were modified from all parents.
That is, suppose M is a merge commit, and diffing M^1 vs M says file mainline.txt
and common.txt
were both changed. Suppose further that diffing M^2 and M says that file sidebranch.txt
and common.txt
were both changed. The combined diff will show only common.txt
, skipping both mainline.txt
and sidebranch.txt
because those two files were only modified from one parent (each). (Even then Git may show only some of the diffs for common.txt
.)
3It took me a long time to find this in the documentation, as I kept looking at the other section.
The -m
option—m probably stands for merge here—tells Git to, in effect, "split" the merge. That is, instead of trying to combine the diffs against each parent into one big combined diff, just show the diff against each parent, one diff at a time.
This is sometimes what you want. When it's not what you want, you can run your own explicit git diff
to just diff against one of the two parents (or see below).
Usually, the correct answer is "the first parent".
The key to the "first parent" notion is that when Git makes a merge commit, it always records the branch you're on at the time, as the first parent. The other branch becomes the second parent.
That is, if you're on develop
and you merge topic
:
$ git checkout develop
$ git merge topic
Git will make a new commit—a merge commit, with two parents—on your current branch, develop
. The first parent of the merge commit will be the commit that was the tip of develop
just a moment ago. The second parent will be the commit that is (still) the tip of topic
.
Since you're usually concerned with what the merge brought in, comparing against the first parent will give you that. So usually that's what you want. For this reason, git show
allows you to run git show --first-parent
. That "splits" the commit and then git show
only diffs against the first parent. (This is a bit different than git show -m
, which splits the commit twice: the first split compares against the first parent, and the second split compares against the second parent.)
Similarly, you can run git log -p --first-parent
Here, the --first-parent
flag has an even more important effect: the log operation does not look at any of the side branch's commits at all, only those on the main (first-parent) line. Note that if your Git is older than 2.31, you still need the -m
flag as well (when using git log
, that is; git show
defaults to --cc
and hence does not require the -m
, and all of this was cleaned up in Git 2.31).
Upvotes: 316
Reputation: 2385
git show -c c0f501
will display a combined diff from commit c0f501
to both of its parents, as printed by git diff
during a merge.
This gives a better overview than git show -m
.
However, it only displays changes in files changed relative to both parents (or at least two parents for octopus merges). E.g. when files f1
and f2
are changed on the first parent and f2
and f3
are changed on the second parent, only the changes from f2
will be displayed by this command. The changes in f1
and f3
will not be displayed.
So to get a complete overview it still makes sense to use git show -m
before or after using git show -c
.
An example output of git show -c
for an octopus merge with changes in two files, only one of which is changed for more than one parent:
commit 3a9f99582921495f7c25e682d4af36d3407983f9 (HEAD -> master)
Merge: 33fb507 91c772b edf2d9c
Author: Foo Bar <[email protected]>
Date: Mon Mar 22 15:56:37 2021 +0100
Merge branches 'b1' and 'b2'
diff --combined b
index 4658c0c,a305e3c,2788b76..9c7beb1
--- a/b
+++ b/b
@@@@ -1,5 -1,5 -1,5 +1,7 @@@@
1
2
+ +a
3
++b
4
++ c
In contrast, git show -m
gives the following lengthy but complete output:
git show -m 3a9f99582921495f7c25e682d4af36d3407983f9
commit 3a9f99582921495f7c25e682d4af36d3407983f9 (from 33fb5076fbbcc2d82aa0b877c959b8e4cc4f7b74)
Merge: 33fb507 91c772b edf2d9c
Author: Foo Bar <[email protected]>
Date: Mon Mar 22 15:56:37 2021 +0100
Merge branches 'b1' and 'b2'
diff --git a/a b/a
index 94ebaf9..775aea6 100644
--- a/a
+++ b/a
@@ -1,4 +1,5 @@
1
+a
2
3
4
diff --git a/b b/b
index 4658c0c..9c7beb1 100644
--- a/b
+++ b/b
@@ -1,5 +1,7 @@
1
2
+a
3
b
4
+c
commit 3a9f99582921495f7c25e682d4af36d3407983f9 (from 91c772b2c57ff9b4791b57712c26aefbd0c7e730)
Merge: 33fb507 91c772b edf2d9c
Author: Foo Bar <[email protected]>
Date: Mon Mar 22 15:56:37 2021 +0100
Merge branches 'b1' and 'b2'
diff --git a/b b/b
index a305e3c..9c7beb1 100644
--- a/b
+++ b/b
@@ -2,4 +2,6 @@
2
a
3
+b
4
+c
commit 3a9f99582921495f7c25e682d4af36d3407983f9 (from edf2d9c9a255a709875988278a4eda6f7072196e)
Merge: 33fb507 91c772b edf2d9c
Author: Foo Bar <[email protected]>
Date: Mon Mar 22 15:56:37 2021 +0100
Merge branches 'b1' and 'b2'
diff --git a/a b/a
index 94ebaf9..775aea6 100644
--- a/a
+++ b/a
@@ -1,4 +1,5 @@
1
+a
2
3
4
diff --git a/b b/b
index 2788b76..9c7beb1 100644
--- a/b
+++ b/b
@@ -1,5 +1,7 @@
1
2
+a
3
+b
4
c
Upvotes: 1