comparison mercurial/simplemerge.py @ 28072:c3e9269d9602

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.
author Ryan McElroy <rmcelroy@fb.com>
date Wed, 10 Feb 2016 09:06:08 -0800
parents 261324dd5f5b
children 5f7d13d3bd4d
comparison
equal deleted inserted replaced
28071:261324dd5f5b 28072:c3e9269d9602
90 name_base=None, 90 name_base=None,
91 start_marker='<<<<<<<', 91 start_marker='<<<<<<<',
92 mid_marker='=======', 92 mid_marker='=======',
93 end_marker='>>>>>>>', 93 end_marker='>>>>>>>',
94 base_marker=None, 94 base_marker=None,
95 localorother=None): 95 localorother=None,
96 minimize=False):
96 """Return merge in cvs-like form. 97 """Return merge in cvs-like form.
97 """ 98 """
98 self.conflicts = False 99 self.conflicts = False
99 newline = '\n' 100 newline = '\n'
100 if len(self.a) > 0: 101 if len(self.a) > 0:
107 if name_b and end_marker: 108 if name_b and end_marker:
108 end_marker = end_marker + ' ' + name_b 109 end_marker = end_marker + ' ' + name_b
109 if name_base and base_marker: 110 if name_base and base_marker:
110 base_marker = base_marker + ' ' + name_base 111 base_marker = base_marker + ' ' + name_base
111 merge_regions = self.merge_regions() 112 merge_regions = self.merge_regions()
113 if minimize:
114 merge_regions = self.minimize(merge_regions)
112 for t in merge_regions: 115 for t in merge_regions:
113 what = t[0] 116 what = t[0]
114 if what == 'unchanged': 117 if what == 'unchanged':
115 for i in range(t[1], t[2]): 118 for i in range(t[1], t[2]):
116 yield self.base[i] 119 yield self.base[i]
439 out = opener(os.path.basename(local), "w", atomictemp=True) 442 out = opener(os.path.basename(local), "w", atomictemp=True)
440 else: 443 else:
441 out = sys.stdout 444 out = sys.stdout
442 445
443 m3 = Merge3Text(basetext, localtext, othertext) 446 m3 = Merge3Text(basetext, localtext, othertext)
444 extrakwargs = {"localorother": opts.get("localorother", None)} 447 extrakwargs = {
448 "localorother": opts.get("localorother", None),
449 'minimize': True,
450 }
445 if mode == 'union': 451 if mode == 'union':
446 extrakwargs['start_marker'] = None 452 extrakwargs['start_marker'] = None
447 extrakwargs['mid_marker'] = None 453 extrakwargs['mid_marker'] = None
448 extrakwargs['end_marker'] = None 454 extrakwargs['end_marker'] = None
449 elif name_base is not None: 455 elif name_base is not None:
450 extrakwargs['base_marker'] = '|||||||' 456 extrakwargs['base_marker'] = '|||||||'
451 extrakwargs['name_base'] = name_base 457 extrakwargs['name_base'] = name_base
458 extrakwargs['minimize'] = False
452 for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs): 459 for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs):
453 out.write(line) 460 out.write(line)
454 461
455 if not opts.get('print'): 462 if not opts.get('print'):
456 out.close() 463 out.close()