threads / discuss / 29742

4-way diff (base,ours,theirs,merged) to review merge results

Subject: 4-way diff (base,ours,theirs,merged) to review merge results

## tl;dr

3 messages between Feb 26, 2012 and Feb 26, 2012.

replies: 2people: 2as markdown or json

Neal Kreitzinger· Feb 26, 2012, 03:55 UTC · lore

Combined diff only tells you what the merge result auto-resolved (with rerere turned off and no merge-conflicts) in comparison to "ours" and "theirs". That only tells you what "ours" and "theirs" *had*, not what they *did* (or were trying to do). You need the merge-base version to see what "ours" and "theirs" did. Seeing what "ours" and "theirs" did will much-better tell you if "merged" did-the-right-thing or not. What is the best way to display a 4-way diff of merge-base, "ours", "theirs", and "merged" after a merge completes so you can review the "merged" results for correctness?

Before I try writing a script to dump the object-contents of the merge-base, "ours", "theirs", and "merged" versions of the-file-in-question to work-files and then feed them to a 4-way diff for review, I would like to see if someone already has a script or better-idea for this, or if git has something more straight-forward that already does-this-for-you.

Reason for this: If "ours" has line-x and "theirs" does not have line-x, and "merged" does have line-x you still have a mystery on your hands:

(Combined diff) ours: has line-x theirs (master): does not have line-x merged: has line-x merge-base (older master): *may-or-may-not* have line-x conclusion: I'm not very sure if "merged" should have line-x or not...

Based on the combined-diff only, I don't know if "merged" should have line-x or not because I don't know if "ours" *added* line-x to the merge-base or if "theirs" *removed* line-x from the merge-base. IOW, if "theirs" is master and "ours" is way-behind master then I pretty-much know I probably need to take "theirs" because it has the latest-stuff. However, I don't know if "theirs" took line-x out of master (and "ours" just has line-x because its old), or if line-x was never in master and "ours" really-needed to add it. Having merge-base context allows for more accurate conclusions like this:

ours: has line-x theirs (master): does not have line-x merged: has line-x merge-base (older master): has line-x conclusion: I should probably take line-x out of "merged"

ours: has line-x theirs (master): does not have line-x merged: has line-x merge-base: does not have line-x conclusion: I should probably keep line-x in "merged"

Thanks in advance for you feedback.

v/r, neal

Junio C Hamano· Feb 26, 2012, 08:12 UTC · re: Neal Kreitzinger · lore

Re: 4-way diff (base,ours,theirs,merged) to review merge results

"Neal Kreitzinger" <neal@rsss.com> writes:
Show 6 quoted lines
> (Combined diff)
> ours:  has line-x
> theirs (master):  does not have line-x
> merged:  has line-x
> merge-base (older master):  *may-or-may-not* have line-x
> conclusion:  I'm not very sure if "merged" should have line-x or not...

When I need this information to resolve a merge in an area of the code that I am not very familiar with, the first thing I do is this:

  $ git merge $other
  $ git diff
  ... yikes, that is a complex conflict!
  $ git checkout --conflict=diff3 $the_path_with_difficult_conflict
  $ git diff
The output will also show the lines from the merge base.

The default style of showing the conflict we use is called the "merge" style (it originally came from the "merge" program of the RCS suite), and it only gives the two sides without the base version. It is sufficient when the person who is making the merge is familiar with the baseline history of the code (e.g. in a contributor-to-integrator pull based workflow, especially when contributors are encouraged to keep their topics focused and short). The "diff3" style that also gives the base version is needed less often in such a setting. That, and also the resulting output is much shorter, is the reason why "merge" style is the default.

When the person who is making the merge is not very familiar with the baseline history (e.g. when using Git as an improved CVS and a contributor pulls the updated upstream into his history), however, "diff3" style may be more often helpful---as you mentioned, "merge" style requires that you know your code well enough to either already know or be able to guess how the version in the merge base looked like, but by definition, pulling the updated upstream into your work will pull more stuff (because many other people are working on the code on the other side) than pulling one topic from a contributor into the integrator tree, so there may be more need to see the version from the merge base in such a workflow.

By setting the configuration variable "merge.conflictstyle" to "diff3", you would get the base version by default whenever there is a conflict.

Junio C Hamano· Feb 26, 2012, 09:05 UTC · re: Neal Kreitzinger · lore

Re: 4-way diff (base,ours,theirs,merged) to review merge results

"Neal Kreitzinger" <neal@rsss.com> writes:
> ....  What is the 
> best way to display a 4-way diff of merge-base, "ours", "theirs", and 
> "merged" after a merge completes so you can review the "merged" results for 
> correctness?

Ahh, sorry. While everything I wrote in my previous reply is correct with respect to what happens _during_ a merge until you resolve it, I did not realize that you were asking how to view a merge _after_ it is made.

For a two-parent merge $M, "git show --cc $M" runs a three-way diff between $M (merge result), $M^1 (the first parent) and $M^2 (the other parent) and the combined diff it shows is equivalent to:

  $ git diff --cc $M $M^1 $M^2

Notice the order of parameters. Unlike a normal "diff A B" to ask the command to explain how the state B is reached from state A, you give the result $M and ask the command to explain how it was reached from other states.

So in a similar way, running
  $ git diff --cc $M $M^1 $M^2 $(git merge-base $M^1 $M^2)

should show a combined patch that explains the state at $M relative to the states recorded in its parents and the merge base.

I've never tried it myself, though, as I never needed such an operation.

You can try a trivial example with 4d9e079, which merges 583c389 ec7ff5b and has conflicts in cache.h

$ git show 4d9e079 -- cache.h Output omitted; you can see it is the same as the next one for yourself.

$ git diff --cc 4d9e079 583c389 ec7ff5b -- cache.h diff --cc cache.h index 3a8e125,24732e6..422c5cf

--- a/cache.h
+++ b/cache.h
@@@ -1177,7 -1176,7 +1177,8 @@@ extern void setup_pager(void)
  extern const char *pager_program;
  extern int pager_in_use(void);
  extern int pager_use_color;
 +extern int term_columns(void);
+ extern int decimal_width(int);
  
  extern const char *editor_program;
  extern const char *askpass_program;

One side adds term_columns, the other side adds decimal_width.

$ git diff --cc 4d9e079 583c389 ec7ff5b \
    $(git merge-base 583c389 ec7ff5b) -- cache.h
diff --cc cache.h
index 3a8e125,24732e6,9bd8c2d..422c5cf
--- a/cache.h
+++ b/cache.h
@@@@ -1177,7 -1176,7 -1176,6 +1177,8 @@@@ extern void setup_pager(void)
   extern const char *pager_program;
   extern int pager_in_use(void);
   extern int pager_use_color;
 ++extern int term_columns(void);
+ +extern int decimal_width(int);
   
   extern const char *editor_program;
   extern const char *askpass_program;

The third column is a diff between $M and $(git merge-base $M^1 $M^2); the
resulting two new lines are indeed shown as additions against the merge
base.

← back to recent threads