Using three-way diffing context for merge conflict style in Git

Date: September 24, 2020 Last modified: September 24, 2020

It recently came to my attention that not everyone overrides the default merge.conflictStyle git-config setting. So in case anyone here wanted to try something new out that would provide more context during a Git conflict resolution scenario here you go.

I use diff3 which you can set like this to override the default:

git config --global merge.conflictStyle diff3

When rebasing you will get the following markers:

<<<<<<< HEAD
THIS IS SOME CODE
||||||| merged common ancestors
This is some code
=======
This is some other code
>>>>>>> d5439077.....

Notice the new section from the default version between ||||||| and ====== which denotes the state that the rebased commit expected to see.

The meaning of the diff between <<<<<<< and ||||||| markers remains what it used to be in the two way diff output which is the code on the branch you are rebasing on top of and the content between the ====== and >>>>>>> is what you want to record on top of the HEAD of the rebase branch.

A simple illustration

My last merge conflict arose when attempting to apply an old stash to the latest HEAD of new branch off of our mainline. It was a Nix expression conflict and looked like the following:

<<<<<<< Updated upstream
  ruby = self.ruby_2_5;
||||||| merged common ancestors
  nodejs = nodejs_10_22_1;
  ruby = self.ruby_2_5;
=======
  nodejs = nodejs_10_22_1;
  #ruby = self.ruby_2_5;
>>>>>>> Stashed changes

What this is saying is that the current upstream has the following at that location of the file:

  ruby = self.ruby_2_5;

When this commit was applied last that part of the upstream content looked like this:

   nodejs = nodejs_10_22_1;
   ruby = self.ruby_2_4;

My changes has content at that region of the file that looks like this now:

  nodejs = nodejs_10_22_1;
  #ruby = self.ruby_2_5;

What this is saying is that another change to the upstream branch has removed (or moved) the nodejs version pinning, so maybe I should discard it (of course context matters given how Git works).

Ignoring the first line of the region around our change means we just need to decide how to resolve the line that contains the change:

<<<<<<< Updated upstream
  ruby = self.ruby_2_4;
=======
  #ruby = self.ruby_2_5;
<<<<<<< Stashed changes

The added context of the middle section allowed me to understand what was not relevant for resolution which we could not have confidently deduced from just a two way merge conflict context (only the top and the bottom parts of the diff above).

My resulting change was to delete the entirety of the conflict because I had merely commented out a line of Nix which means we could just delete it to get the same effect. It is also a great illustration of why commented out unused code, even just to "try" something (assuming you have the previous version in Git) hinders effectiveness and I should feel bad for this. I do! :)

Concluding thoughts

Even in a simple example, we found having this extra context in the merge conflict beneficial since it allowed us to understand what part of the conflict was relevant to resolve.

Hopefully that provided a quick illustration of how to improve effectiveness of resolving merge conflicts, even in a minimally beneficial case.