# HG changeset patch # User Ryan McElroy # Date 2016-02-10 17:06:08 # Node ID c3e9269d96027670b79a4c4ffc02a3320c00e846 # Parent 261324dd5f5b41f7e77f95355db53167a87190c4 merge: minimize conflicts when common base is not shown (issue4447) Previously, two changes that were nearly, but not quite, identical would result in large merge conflict regions that looked very similar, and were thus very confusing to users, and lead people used to other source control systems to claim that "mercurial's merge algorithms suck". In the relatively common case of a new file being introduced in two branches with very slight modifications, the old behavior would show the entire file as a conflict, and it would be very difficult for a user to determine what was going on. In the past, mercurial attempted to solve this with a "very smart" algorithm that would find all common lines, but this has significant problems as described in 2ea6d906cf9b. Instead, we use a "very dumb" algorithm introduced in the previous patch that simply matches lines at the periphery of conflict regions. This minimizes most conflict regions well, though there may still be some degenerate edge cases, like small modification to the beginning and end of a large file. diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py --- a/mercurial/simplemerge.py +++ b/mercurial/simplemerge.py @@ -92,7 +92,8 @@ class Merge3Text(object): mid_marker='=======', end_marker='>>>>>>>', base_marker=None, - localorother=None): + localorother=None, + minimize=False): """Return merge in cvs-like form. """ self.conflicts = False @@ -109,6 +110,8 @@ class Merge3Text(object): if name_base and base_marker: base_marker = base_marker + ' ' + name_base merge_regions = self.merge_regions() + if minimize: + merge_regions = self.minimize(merge_regions) for t in merge_regions: what = t[0] if what == 'unchanged': @@ -441,7 +444,10 @@ def simplemerge(ui, local, base, other, out = sys.stdout m3 = Merge3Text(basetext, localtext, othertext) - extrakwargs = {"localorother": opts.get("localorother", None)} + extrakwargs = { + "localorother": opts.get("localorother", None), + 'minimize': True, + } if mode == 'union': extrakwargs['start_marker'] = None extrakwargs['mid_marker'] = None @@ -449,6 +455,7 @@ def simplemerge(ui, local, base, other, elif name_base is not None: extrakwargs['base_marker'] = '|||||||' extrakwargs['name_base'] = name_base + extrakwargs['minimize'] = False for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs): out.write(line) diff --git a/tests/test-conflict.t b/tests/test-conflict.t --- a/tests/test-conflict.t +++ b/tests/test-conflict.t @@ -46,16 +46,13 @@ $ cat a Small Mathematical Series. - <<<<<<< local: 618808747361 - test: branch2 1 2 3 + <<<<<<< local: 618808747361 - test: branch2 6 8 ======= - 1 - 2 - 3 4 5 >>>>>>> other: c0c68e4fe667 - test: branch1 @@ -79,16 +76,13 @@ Verify custom conflict markers $ cat a Small Mathematical Series. - <<<<<<< local: test 2 1 2 3 + <<<<<<< local: test 2 6 8 ======= - 1 - 2 - 3 4 5 >>>>>>> other: test 1 @@ -108,16 +102,13 @@ Verify line splitting of custom conflict $ cat a Small Mathematical Series. - <<<<<<< local: test 2 1 2 3 + <<<<<<< local: test 2 6 8 ======= - 1 - 2 - 3 4 5 >>>>>>> other: test 1 @@ -150,16 +141,13 @@ Verify line trimming of custom conflict $ cat a Small Mathematical Series. - <<<<<<< local: 123456789012345678901234567890123456789012345678901234567890\xe3\x81\x82... (esc) 1 2 3 + <<<<<<< local: 123456789012345678901234567890123456789012345678901234567890\xe3\x81\x82... (esc) 6 8 ======= - 1 - 2 - 3 4 5 >>>>>>> other: branch1 @@ -179,16 +167,13 @@ Verify basic conflict markers $ cat a Small Mathematical Series. - <<<<<<< local 1 2 3 + <<<<<<< local 6 8 ======= - 1 - 2 - 3 4 5 >>>>>>> other diff --git a/tests/test-contrib.t b/tests/test-contrib.t --- a/tests/test-contrib.t +++ b/tests/test-contrib.t @@ -148,11 +148,10 @@ conflicts base <<<<<<< conflict-local not other - end ======= other + >>>>>>> conflict-other end - >>>>>>> conflict-other [1] 1 label @@ -161,11 +160,10 @@ 1 label base <<<<<<< foo not other - end ======= other + >>>>>>> conflict-other end - >>>>>>> conflict-other [1] 2 labels @@ -174,11 +172,10 @@ 2 labels base <<<<<<< foo not other - end ======= other + >>>>>>> bar end - >>>>>>> bar [1] 3 labels diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t --- a/tests/test-rebase-conflicts.t +++ b/tests/test-rebase-conflicts.t @@ -305,3 +305,55 @@ Check that the right ancestors is used w rebase completed updating the branch cache truncating cache/rbc-revs-v1 to 72 + +Test minimization of merge conflicts + $ hg up -q null + $ echo a > a + $ hg add a + $ hg commit -q -m 'a' + $ echo b >> a + $ hg commit -q -m 'ab' + $ hg bookmark ab + $ hg up -q '.^' + $ echo b >> a + $ echo c >> a + $ hg commit -q -m 'abc' + $ hg rebase -s 7bc217434fc1 -d ab --keep + rebasing 13:7bc217434fc1 "abc" (tip) + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + unresolved conflicts (see hg resolve, then hg rebase --continue) + [1] + $ hg diff + diff -r 328e4ab1f7cc a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a * (glob) + @@ -1,2 +1,6 @@ + a + b + +<<<<<<< dest: 328e4ab1f7cc ab - test: ab + +======= + +c + +>>>>>>> source: 7bc217434fc1 - test: abc + $ hg rebase --abort + rebase aborted + $ hg up -q -C 7bc217434fc1 + $ hg rebase -s . -d ab --keep -t internal:merge3 + rebasing 13:7bc217434fc1 "abc" (tip) + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + unresolved conflicts (see hg resolve, then hg rebase --continue) + [1] + $ hg diff + diff -r 328e4ab1f7cc a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a * (glob) + @@ -1,2 +1,8 @@ + a + +<<<<<<< dest: 328e4ab1f7cc ab - test: ab + b + +||||||| base + +======= + +b + +c + +>>>>>>> source: 7bc217434fc1 - test: abc