Should diff3 be default conflictstyle on git?

26.4k views Asked by At

Recently I enabled diff3 and it's much easier to resolve conflict now.

Previously in some cases I had to check the log to see why people did this and that to do the merge. But with diff3 the information is displayed all in one place

<<<<<<< HEAD
THIS IS USEFUL
||||||| merged common ancestors
This is useful
=======
This is really useful
>>>>>>> c2392943.....

From that we can easily see that the result should be "THIS IS REALLY USEFUL"

I wonder if there is any downside to diff3? Why it is not the default behavior of git?

5

There are 5 answers

4
VonC On BEST ANSWER

For other readers (and from this article):

git has an option to display merge conflicts in diff3 format (by default it only displays the two files to be merged). You can enable it like so:

git config --global merge.conflictstyle diff3

There's really no reason you shouldn't enable the diff3 style, because you frequently need the ancestor to determine what the correct merge is.

This was introduced fairly early (2008), and I suppose it isn't the default, because a default Unix diff isn't display as a 3-way diff.

With Git 2.35, you also have zdiff3 ("zealous diff3").


As mentioned in this thread, if you want to run this command without setting the config, so that you can switch between normal diffs and diff3s easily, this is possible in one specific case:

If a conflict is marked in the index (i.e., the state you are in after a conflicted merge, but before you mark a path as resolved), you can do:

git checkout --conflict=diff3 <path...>

Note that that is actually checking out the index contents into the working tree, so any edits you may have made to the conflicted working tree copy will be overwritten.


Note that the |||||| merged common ancestors will evolve with git 2.24 (Q4 2019)

See commit b657047 (07 Oct 2019), commit 8e4ec33 (01 Oct 2019), and commit 4615a8c, commit 45ef16f, commit f3081da, commit 5bf7e57, commit e95e481, commit a779fb8, commit 8599ab4, commit 7c0a6c8, commit c749ab1, commit bab5687, commit ff1bfa2, commit 4d7101e, commit 724dd76, commit 345480d, commit b4db8a2, commit 98a1d3d, commit 9822175, commit 10f751c (17 Aug 2019) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit 280bd44, 15 Oct 2019)

merge-recursive: provide a better label for diff3 common ancestor

Signed-off-by: Elijah Newren

In commit 7ca56aa07619 ("merge-recursive: add a label for ancestor", 2010-03-20, Git v1.7.1-rc0 -- merge), a label was added for the '||||||' line to make it have the more informative heading '|||||| merged common ancestors', with the statement:

It would be nicer to use a more informative label.
Perhaps someone will provide one some day.

This chosen label was perfectly reasonable when recursiveness kicks in, i.e. when there are multiple merge bases.

(I can't think of a better label in such cases.)

But it is actually somewhat misleading when there is a unique merge base or no merge base.

Change this based on the number of merge bases:

>=2: "merged common ancestors"
  1:   <abbreviated commit hash>
  0:   "<empty tree>"

Tests have also been added to check that we get the right ancestor name for each of the three cases.


With Git 2.25 (Q1 2020), "git apply --3way" learned to honor merge.conflictStyle configuration variable, like merges would.

See commit 091489d, commit aa76ae4, commit 9580620, commit b006968, commit fa87b81 (23 Oct 2019) by Denton Liu (Denton-L).
(Merged by Junio C Hamano -- gitster -- in commit eff313f, 10 Nov 2019)

apply: respect merge.conflictStyle in --3way

Signed-off-by: Denton Liu

Before, when doing a 3-way merge, the merge.conflictStyle option was not respected and the "merge" style was always used, even if "diff3" was specified.

Call git_xmerge_config() at the end of git_apply_config() so that the merge.conflictStyle config is read.

0
Thomas Jacob On

While I must say that I like diff3 much, one downside is that conflicts sometimes tend to be larger then.

And of course, changing defaults for new features can confuse people.

4
Tom Ellis On

diff3 should be the default. It is not only useful for resolving conflicts, it makes resolving conflicts possible. It is literally impossible to correctly resolve conflicts using (only) the default merge conflict style. I suggest to everyone to set diff3 in their global options.

git config --global merge.conflictStyle diff3

Why is it literally impossible? Consider a branch which adds a function foo1 at a particular source file location

def foo1():
    print("foo1")

and another branch which adds a function foo2 at the same location

def foo2():
    print("foo2")

If I rebase one on the other I get a conflict. The default merge conflict style will show

++<<<<<<< HEAD
 +def foo1():
 +    print("foo1")
++=======
+ def foo2():
+     print("foo2")
++>>>>>>> Add foo2

What are the conflict markers telling me? They're telling me that I need to add both foo1 and foo2 to the file, right? Unfortunately not! Consider a file in which foo1 and foo2 already exist, and two branches, one of which removes foo1 and one of which removes foo2. If I rebase one on the other what is the result? The default merge conflict style will show

++<<<<<<< HEAD
 +def foo1():
 +    print("foo1")
++=======
+ def foo2():
+     print("foo2")
++>>>>>>> Remove foo1

Under the default conflict style the case of removing two functions is completely indistinguishable from the case of adding two functions (besides the text of the commit message which can only ever be a hint)! Therefore it is insufficient for purpose of resolving conflicts. This probably explains why resolving conflicts is seen as a dark art. diff3 not only makes it possible, it often makes it easy.

EDIT: here is what the diff3s look like in each case:

  • Each foo added

    ++<<<<<<< HEAD
    +def foo2():
    +    print("foo2")
    ++||||||| ef9ce8d
    ++=======
    + def foo1():
    +     print("foo1")
    ++>>>>>>> a1
    

    You can see (from the middle section) that nothing was there before.

  • Each foo deleted

    ++<<<<<<< HEAD
    +def foo2():
    +    print("foo2")
    ++||||||| 845b035
    + def foo1():
    +     print("foo1")
    ++
    ++def foo2():
    ++    print("foo2")
    ++=======
    ++def foo1():
    ++    print("foo1")
    ++>>>>>>> a2
    

    You can see (from the middle section) that both foos were there before.

0
stackzebra On

Why it is not the default behavior of git?

I think it's not default because git mergetool shows 3 panels on top anyway: local, base (common ancestor) and remote + the 4th panel at the bottom, which has the content as you write in your question.

So if you turn on diff3 and use mergetool, you then have information duplicated in the middle top panel and in the section between ||||||| and ======== in the bottom panel.

0
VonC On

Another contender for default merge conflict style: zdiff3, starting with Git 2.35 (Q1 2022): "Zealous diff3" style of merge conflict presentation has been added.

See commit ddfc44a (01 Dec 2021) by Elijah Newren (newren).
See commit 4496526 (01 Dec 2021) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster -- in commit 4ce498b, 15 Dec 2021)

xdiff: implement a zealous diff3, or "zdiff3"

Based-on-patch-by: Uwe Kleine-König
Co-authored-by: Elijah Newren
Signed-off-by: Phillip Wood
Signed-off-by: Elijah Newren

"zdiff3" is identical to ordinary diff3 except that it allows compaction of common lines on the two sides of history at the beginning or end of the conflict hunk.
For example, the following diff3 conflict:

1
2
3
4
<<<<<<
A
B
C
D
E
||||||
5
6
======
A
X
C
Y
E
>>>>>>
7
8
9

has common lines 'A', 'C', and 'E' on the two sides.
With zdiff3, one would instead get the following conflict:

1
2
3
4
A
<<<<<<
B
C
D
||||||
5
6
======
X
C
Y
>>>>>>
E
7
8
9

Note that the common lines, 'A', and 'E' were moved outside the conflict.

Unlike with the two-way conflicts from the 'merge' conflictStyle, the zdiff3 conflict is NOT split into multiple conflict regions to allow the common 'C' lines to be shown outside a conflict, because zdiff3 shows the base version too and the base version cannot be reasonably split.

Note also that the removing of lines common to the two sides might make the remaining text inside the conflict region match the base text inside the conflict region (for example, if the diff3 conflict had '5 6 E' on the right side of the conflict, then the common line 'E' would be moved outside and both the base and right side's remaining conflict text would be the lines '5' and '6').
This has the potential to surprise users and make them think there should not have been a conflict, but there definitely was a conflict and it should remain.