threads / discuss / 39800

No one understands diff3 "Temporary merge branch" conflict markers

Subject: No one understands diff3 "Temporary merge branch" conflict markers

## tl;dr

4 messages between Jul 7, 2015 and Jul 7, 2015.

replies: 3people: 3as markdown or json

Edward Anderson· Jul 7, 2015, 14:55 UTC · lore

I have the diff3 conflictstyle enabled and want to be able to understand how to understand its output when there are criss-cross merges requiring temporary merge branches. Eg:

    <<<<<<< HEAD
      print(A);
    ||||||| merged common ancestors
    <<<<<<< Temporary merge branch 1
      print(B);
    =======
      print(C);
    >>>>>>> feature

I have combed Google but have been able to find no good explanation. People are hitting these and asking for help, but the suggestions are weak and fail to explain anything.

Background ----------

Our workflow often causes criss-cross merges. In this more extreme (but real) case, I have 16 ambiguously "best" merge bases:

    $ git merge-base --all HEAD feature | wc -l
    16
The 'resolve' strategy isn't an option for this many merge-bases:
    $ git merge -s resolve feature
    fatal: I cannot read more than 8 trees
    Merge with strategy resolve failed.
That brings me back to the recursive strategy.

https://lwn.net/Articles/210045/ "Branching and merging with git" describes what a criss-cross merge is and how the recursive strategy deals with them by merging the multiple merge-bases to form a single temporary merge base.

Show 21 quoted lines
> The classic confusing case is called a "criss-cross merge", and
> looks like this:
>
>          o--b-o-o--B
>         /    \ /
>  o--o--o      X
>         \    / \
>          o--a-o-o--A
>
> There are two common ancestors of A and B, marked a and b in the
> graph above.  And they're not the same.  You could use either one
> and get reasonable results, but how to choose?
>
> The details are too advanced for this discussion, but the default
> "recursive" merge strategy that git uses solves the answer by
> merging a and b into a temporary commit and using *that* as the
> merge base.
>
> Of course, a and b could have the same problem, so merging them
> could require another merge of still-older commits.  This is why the
> algorithm is called "recursive."

With the diff3 conflictstyle showing the merged common ancestors and the recursive merge strategy, the merged common ancestors may have a conflict

Conflict #1 -----------

     1 <<<<<<< HEAD
     2     unless admin
     3       fail Unauthorized.new("Admin only")
     4     end
     5 ||||||| merged common ancestors
     6 <<<<<<< Temporary merge branch 1
     7     unless admin
     8       fail Unauthorized.new("Admin only")
     9     end
    10 ||||||| merged common ancestors
    11     unless admin
    12       fail Unauthorized.new
    13     end
    14 =======
    15     fail Unauthorized.new unless admin
    16 >>>>>>> Temporary merge branch 2
    17 =======
    18     unless admin
    19         fail Unauthorized.new("Admin only")
    20       fail Unauthorized.new
    21     end
    22 >>>>>>> feature

It seems lines 6-16 are a conflict that occurred when merging the merge-bases. That conflict could be resolved by merging the change in Temporary merge branch 1 (add "Admin only") with Temporary merge branch 2 (convert multi-line unless to single-line) as this:

           fail Unauthorized.new("Admin only") unless admin

Thus, you might thing that you could refactor the above conflict to be this:

     1 <<<<<<< HEAD
     2     unless admin
     3       fail Unauthorized.new("Admin only")
     4     end
     5 ||||||| merged common ancestors
     6     fail Unauthorized.new("Admin only") unless admin
     7 =======
     8     unless admin
     9         fail Unauthorized.new("Admin only")
    10       fail Unauthorized.new
    11     end
    12 >>>>>>> feature

However this would be resolved by merging HEAD's apparent change (break single-line unless onto multiple lines) and feature's change, which appears to be a botched up conflict resolution.

Obviously I'm reading this wrong. What is the correct way?

Conflict #2 ------------

     1 <<<<<<< HEAD
     2     unless feature.enabled_for_user?(UserIdLookup.new(params).user_id)
     3       fail Unauthorized.new("Requires setting #{label}.")
     4 ||||||| merged common ancestors
     5 <<<<<<< Temporary merge branch 1
     6     unless feature.enabled_for_user?(params[:user_id])
     7       fail Unauthorized.new("Requires setting #{label}.")
     8 =======
     9     unless feature.enabled_for_user?(params[:user_id])
    10       fail Unauthorized.new("Requires setting #{label}.")
    11 >>>>>>> feature

This is the full conflict, and it doesn't seem to balance. In this case, there's only one Temporary merge branch. Can the line marker on line 5 just be ignored? If so, then this is easy to resolve, but I am left puzzled as to why the sections in lines 6-7 and 9-10 are identical. Is the Temporary merge branch 1 marker here simply because other conflicts had multiple Temporary merge branches that had to be labeled? Or is the 0 lines between lines 4 and 5 a significant deletion?

---

The git community needs some light and understanding shed on this topic, on how these conflicts are to be read and understood. I'm looking for something that can be applied methodically. If better understanding the recursive merge strategy is necessary, that is fine. However suggestions like "just resolve it to how you think it should be" or "talk to the authors" will not be appreciated, as they do not lend understanding regarding these conflict markers nor where the code they delineate comes from.

Does anyone out there understand this stuff?

Thanks, Edward

Matthieu Moy· Jul 7, 2015, 16:16 UTC · re: Edward Anderson · lore

Re: No one understands diff3 "Temporary merge branch" conflict markers

Edward Anderson <nilbus@nilbus.com> writes:
Show 12 quoted lines
> I have the diff3 conflictstyle enabled and want to be able to
> understand how to understand its output when there are criss-cross
> merges requiring temporary merge branches.  Eg:
>
>     <<<<<<< HEAD
>       print(A);
>     ||||||| merged common ancestors
>     <<<<<<< Temporary merge branch 1
>       print(B);
>     =======
>       print(C);
>     >>>>>>> feature
I guess you are seeing the result of the recursive-merge.
>> The details are too advanced for this discussion, but the default
>> "recursive" merge strategy that git uses solves the answer by
>> merging a and b into a temporary commit and using *that* as the
>> merge base.

That is the point. We don't have a good common ancestor, so Git builds one by merging the common ancestors. Then, two things can happen:

* The merge of the common ancestors is conflict-free. Then, we get a
  "sane" common ancestor.
* The merge has conflicts. In this case, the common ancestor that Git
  built has conflict markers. It is not a big issue, since when merging
  A, B, and ancestor(A, B), the result of the merge is either A or B,
  but never comes from ancestor(A, B). So, you never get to see the
  temporary ancestor(A, B), *except* when you request the common
  ancestor in the merge conflict.

It gets nasty since you get recursive merge conflicts, but you don't see the recursivity. Let me try to indent your conflict:

 1 <<<<<<< HEAD
 2     unless admin
 3       fail Unauthorized.new("Admin only")
 4     end
 5 ||||||| merged common ancestors
 6         <<<<<<< Temporary merge branch 1
 7             unless admin
 8               fail Unauthorized.new("Admin only")
 9             end
10         ||||||| merged common ancestors
11             unless admin
12               fail Unauthorized.new
13             end
14         =======
15             fail Unauthorized.new unless admin
16         >>>>>>> Temporary merge branch 2
17 =======
18     unless admin
19         fail Unauthorized.new("Admin only")
20       fail Unauthorized.new
21     end
22 >>>>>>> feature
> It seems lines 6-16 are a conflict that occurred when merging the
> merge-bases.
Yes.
Show 5 quoted lines
> That conflict could be resolved by merging the change in Temporary
> merge branch 1 (add "Admin only") with Temporary merge branch 2
> (convert multi-line unless to single-line) as this:
>
>            fail Unauthorized.new("Admin only") unless admin

That is probably what you would do if you resolved the conflict manually, but while merging the common ancestors, Git found an ancestor of an ancestor that was different from both ancestors being merged, and there was a conflict. Asking you to resolve this conflict would be essentially a loss of time since Git knows that the result won't appear in the final merge, but only in the merge base.

 1 <<<<<<< HEAD
 2     unless feature.enabled_for_user?(UserIdLookup.new(params).user_id)
 3       fail Unauthorized.new("Requires setting #{label}.")
 4 ||||||| merged common ancestors
 5         <<<<<<< Temporary merge branch 1
 6             unless feature.enabled_for_user?(params[:user_id])
 7               fail Unauthorized.new("Requires setting #{label}.")
 8 =======
 9     unless feature.enabled_for_user?(params[:user_id])
10       fail Unauthorized.new("Requires setting #{label}.")
11 >>>>>>> feature
> This is the full conflict, and it doesn't seem to balance.
Right: I guess the merge-base was stg like
<<<<<<< Temporary merge branch 1
    unless feature.enabled_for_user?(params[:user_id])
      fail Unauthorized.new("Requires setting #{label}.")
|||||||
blabla 1
=======
blabla 2
>>>>>>> Temporary merge branch 2

But then, the actual merge happens, using this as merge-base. A conflict occurs when the commits being merged and the merge-base are all different. In your case, I guess the commits being merged were identical on the next different hunks (the line "blablabla 1" probably was in both commits being merged, which allowed the merge algorithm to move to the next hunk), and there were no conflict in this hunk, hence you don't see the merge base.

I hope this helps on the "light and understanding" part of your question. Now, as of "what to do when I get this?", I would say: the recursive merge-base was computed internally, but not really meant to be shown to the user. You should probably ignore it and resolve the merge by looking only at the 2 sides of the conflict ("ours" and "theirs"). Sorry, this is probably not the answer you expected, but it's the best I can give ;-).

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/
Junio C Hamano· Jul 7, 2015, 17:44 UTC · re: Matthieu Moy · lore

Re: No one understands diff3 "Temporary merge branch" conflict markers

Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
> ... I would say: the
> recursive merge-base was computed internally, but not really meant to be
> shown to the user.

I wonder if the output becomes easier to read if we unconditionally turned off diff3-style for inner merges.

Matthieu Moy· Jul 7, 2015, 21:44 UTC · re: Junio C Hamano · lore

Re: No one understands diff3 "Temporary merge branch" conflict markers

Junio C Hamano <gitster@pobox.com> writes:
Show 8 quoted lines
> Matthieu Moy <Matthieu.Moy@grenoble-inp.fr> writes:
>
>> ... I would say: the
>> recursive merge-base was computed internally, but not really meant to be
>> shown to the user.
>
> I wonder if the output becomes easier to read if we unconditionally
> turned off diff3-style for inner merges.

Or replace the whole conflict markers with "Sorry, cannot compute a merge base" text when doing the recursive merge to build the merge base.

I don't know that area well enough to have a real opinion.
-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

← back to recent threads