Search code examples
gitmergegit-diffgit-merge-conflictgit-show

Show a combined diff of a merge commit, including added files by either parent branch


After doing a merge which includes changes of a coworker, I want to them, to outline wheat each of us changed and how I resolved the conflicts.

The standard way that git displays merges, however, is not adequate for this, because it shows only chunks that each of us modified. So by default, git show MERGE_COMMIT only shows how manual merge conflicts were resolved.

What I need: To see all changes, that happened from the time that the branches diverged, even if there were no merge conflicts, and attribute them to one of the parents.

git show -c and -m are not enough: I figured out that there are the arguments to git diff-tree which are also understood by git show, namely -m, -c, and --cc

  • --cc (used by default by git show MERGE_COMMIT) does not show auto-merged hunks and new files
  • -c Does not show new files
  • -m does show the new files, but it separates the two branches, which makes it hard to judge whether the merged file makes sense semantically.
  • A diff to the merge base git diff $(git merge-base HEAD^1 HEAD^2) HEAD (Thanks @1615903) does not attribute the changes like the -c/--cc options: Using these two, before the diff I get is one column for each parent, so from the column of the +/- I can infer which parent made that change.

Toy example Base commit creates file, branch A adds some lines, branch B adds some lines, one at the same position as A, creating a conflict. Furthermore, both A and B add file_a and file_b, respectively:

git init
# Add content to "file" to modify later
for f in `seq 1 15`; do echo "Line $f" >> file; done
git add file && git commit -m "initial commit"

# Divergent branch A: add "change a" to file and add new file_a
sed -i "s/Line 2/Line 2\nchange a/" file
sed -i "s/Line 10/Line 10\nchange a/" file
echo change a > file_a
git add . && git commit -m "change a"

# Divergent branch B: add "change b" to file and add new file_b
git checkout -b dev master~
sed -i "s/Line 14/Line 14\nchange b/" file
sed -i "s/Line 10/Line 10\nchange b/" file
echo change b > file_b
git add . && git commit -m "change b"

git checkout master
git merge dev
## Auto-merging file
## CONFLICT (content): Merge conflict in file
## Automatic merge failed; fix conflicts and then commit the result.

# Create the union -- remove the conflict markers
sed -i "/^[^Lc].*$/d" file
cat file
## Line 1
## Line 2
## change a
## [...]
## Line 10
## change a
## change b
## [...]
## Line 14
## change b
## Line 15

git add . && git commit -m "Merge 'dev' into 'master'"

The standard option only shows the line where the merge conflict was (two branches modified the same line)

git show HEAD:

[...]
diff --cc file
index 2fe2e63,019eb18..92b6fb7
--- a/file
+++ b/file
@@@ -9,7 -8,7 +9,8 @@@ Line 
  Line 8
  Line 9
  Line 10
 +change a
+ change b
  Line 11
  Line 12
  Line 13

It is a bit better with git show -c HEAD, this shows the other two hunks in file which were resolved automatically:

[...]
diff --combined file
index 2fe2e63,019eb18..92b6fb7
--- a/file
+++ b/file
@@@ -1,6 -1,5 +1,6 @@@
  Line 1
  Line 2
 +change a
  Line 3
  Line 4
  Line 5
@@@ -9,9 -8,10 +9,11 @@@ Line 
  Line 8
  Line 9
  Line 10
 +change a
+ change b
  Line 11
  Line 12
  Line 13
  Line 14
+ change b
  Line 15

What is still missing is file_a and file_b, which do not show up.


Solution

  • git show -c gets you what you want, combined diffs for the ordinary modifications, except it doesn't list the things like adds and deletes where there's no diffs to combine, so (for example) git show -m --oneline --raw first to list all the changes, followed by -c to show combined diffs where that makes sense. Add --diff-filter=m to skip the header if there's no adds/deletes, keeping the two sets non-overlapping.

    git show -m --oneline --raw; git show -c --oneline`
    

    or

    git show -m --oneline --raw --diff-filter=m; git show -c --oneline