87 b = mdiff.splitnewlines(btext) |
81 b = mdiff.splitnewlines(btext) |
88 self.base = base |
82 self.base = base |
89 self.a = a |
83 self.a = a |
90 self.b = b |
84 self.b = b |
91 |
85 |
92 def merge_lines( |
|
93 self, |
|
94 name_a=None, |
|
95 name_b=None, |
|
96 name_base=None, |
|
97 start_marker=b'<<<<<<<', |
|
98 mid_marker=b'=======', |
|
99 end_marker=b'>>>>>>>', |
|
100 base_marker=None, |
|
101 localorother=None, |
|
102 minimize=False, |
|
103 ): |
|
104 """Return merge in cvs-like form.""" |
|
105 self.conflicts = False |
|
106 newline = b'\n' |
|
107 if len(self.a) > 0: |
|
108 if self.a[0].endswith(b'\r\n'): |
|
109 newline = b'\r\n' |
|
110 elif self.a[0].endswith(b'\r'): |
|
111 newline = b'\r' |
|
112 if name_a and start_marker: |
|
113 start_marker = start_marker + b' ' + name_a |
|
114 if name_b and end_marker: |
|
115 end_marker = end_marker + b' ' + name_b |
|
116 if name_base and base_marker: |
|
117 base_marker = base_marker + b' ' + name_base |
|
118 merge_regions = self.merge_regions() |
|
119 if minimize: |
|
120 merge_regions = self.minimize(merge_regions) |
|
121 for t in merge_regions: |
|
122 what = t[0] |
|
123 if what == b'unchanged': |
|
124 for i in range(t[1], t[2]): |
|
125 yield self.base[i] |
|
126 elif what == b'a' or what == b'same': |
|
127 for i in range(t[1], t[2]): |
|
128 yield self.a[i] |
|
129 elif what == b'b': |
|
130 for i in range(t[1], t[2]): |
|
131 yield self.b[i] |
|
132 elif what == b'conflict': |
|
133 if localorother == b'local': |
|
134 for i in range(t[3], t[4]): |
|
135 yield self.a[i] |
|
136 elif localorother == b'other': |
|
137 for i in range(t[5], t[6]): |
|
138 yield self.b[i] |
|
139 else: |
|
140 self.conflicts = True |
|
141 if start_marker is not None: |
|
142 yield start_marker + newline |
|
143 for i in range(t[3], t[4]): |
|
144 yield self.a[i] |
|
145 if base_marker is not None: |
|
146 yield base_marker + newline |
|
147 for i in range(t[1], t[2]): |
|
148 yield self.base[i] |
|
149 if mid_marker is not None: |
|
150 yield mid_marker + newline |
|
151 for i in range(t[5], t[6]): |
|
152 yield self.b[i] |
|
153 if end_marker is not None: |
|
154 yield end_marker + newline |
|
155 else: |
|
156 raise ValueError(what) |
|
157 |
|
158 def merge_groups(self): |
86 def merge_groups(self): |
159 """Yield sequence of line groups. Each one is a tuple: |
87 """Yield sequence of line groups. Each one is a tuple: |
160 |
88 |
161 'unchanged', lines |
89 'unchanged', lines |
162 Lines unchanged from base |
90 Lines unchanged from base |
278 yield b'unchanged', zmatch, zend |
208 yield b'unchanged', zmatch, zend |
279 iz = zend |
209 iz = zend |
280 ia = aend |
210 ia = aend |
281 ib = bend |
211 ib = bend |
282 |
212 |
283 def minimize(self, merge_regions): |
|
284 """Trim conflict regions of lines where A and B sides match. |
|
285 |
|
286 Lines where both A and B have made the same changes at the beginning |
|
287 or the end of each merge region are eliminated from the conflict |
|
288 region and are instead considered the same. |
|
289 """ |
|
290 for region in merge_regions: |
|
291 if region[0] != b"conflict": |
|
292 yield region |
|
293 continue |
|
294 # pytype thinks this tuple contains only 3 things, but |
|
295 # that's clearly not true because this code successfully |
|
296 # executes. It might be wise to rework merge_regions to be |
|
297 # some kind of attrs type. |
|
298 ( |
|
299 issue, |
|
300 z1, |
|
301 z2, |
|
302 a1, |
|
303 a2, |
|
304 b1, |
|
305 b2, |
|
306 ) = region # pytype: disable=bad-unpacking |
|
307 alen = a2 - a1 |
|
308 blen = b2 - b1 |
|
309 |
|
310 # find matches at the front |
|
311 ii = 0 |
|
312 while ( |
|
313 ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii] |
|
314 ): |
|
315 ii += 1 |
|
316 startmatches = ii |
|
317 |
|
318 # find matches at the end |
|
319 ii = 0 |
|
320 while ( |
|
321 ii < alen |
|
322 and ii < blen |
|
323 and self.a[a2 - ii - 1] == self.b[b2 - ii - 1] |
|
324 ): |
|
325 ii += 1 |
|
326 endmatches = ii |
|
327 |
|
328 if startmatches > 0: |
|
329 yield b'same', a1, a1 + startmatches |
|
330 |
|
331 yield ( |
|
332 b'conflict', |
|
333 z1, |
|
334 z2, |
|
335 a1 + startmatches, |
|
336 a2 - endmatches, |
|
337 b1 + startmatches, |
|
338 b2 - endmatches, |
|
339 ) |
|
340 |
|
341 if endmatches > 0: |
|
342 yield b'same', a2 - endmatches, a2 |
|
343 |
|
344 def find_sync_regions(self): |
213 def find_sync_regions(self): |
345 """Return a list of sync regions, where both descendants match the base. |
214 """Return a list of sync regions, where both descendants match the base. |
346 |
215 |
347 Generates a list of (base1, base2, a1, a2, b1, b2). There is |
216 Generates a list of (base1, base2, a1, a2, b1, b2). There is |
348 always a zero-length sync region at the end of all the files. |
217 always a zero-length sync region at the end of all the files. |
401 sl.append((intbase, intbase, abase, abase, bbase, bbase)) |
270 sl.append((intbase, intbase, abase, abase, bbase, bbase)) |
402 |
271 |
403 return sl |
272 return sl |
404 |
273 |
405 |
274 |
406 def _verifytext(text, path, ui, opts): |
275 def _verifytext(input): |
407 """verifies that text is non-binary (unless opts[text] is passed, |
276 """verifies that text is non-binary (unless opts[text] is passed, |
408 then we just warn)""" |
277 then we just warn)""" |
409 if stringutil.binary(text): |
278 if stringutil.binary(input.text()): |
410 msg = _(b"%s looks like a binary file.") % path |
279 msg = _(b"%s looks like a binary file.") % input.fctx.path() |
411 if not opts.get('quiet'): |
280 raise error.Abort(msg) |
412 ui.warn(_(b'warning: %s\n') % msg) |
281 |
413 if not opts.get('text'): |
282 |
414 raise error.Abort(msg) |
283 def _format_labels(*inputs): |
415 return text |
284 pad = max(len(input.label) if input.label else 0 for input in inputs) |
416 |
285 labels = [] |
417 |
286 for input in inputs: |
418 def _picklabels(defaults, overrides): |
287 if input.label: |
419 if len(overrides) > 3: |
288 if input.label_detail: |
420 raise error.Abort(_(b"can only specify three labels.")) |
289 label = ( |
421 result = defaults[:] |
290 (input.label + b':').ljust(pad + 1) |
422 for i, override in enumerate(overrides): |
291 + b' ' |
423 result[i] = override |
292 + input.label_detail |
424 return result |
293 ) |
425 |
294 else: |
426 |
295 label = input.label |
427 def is_not_null(ctx): |
296 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') |
428 if not util.safehasattr(ctx, "node"): |
297 labels.append(stringutil.ellipsis(label, 80 - 8)) |
429 return False |
298 else: |
430 return ctx.rev() != nullrev |
299 labels.append(None) |
431 |
300 return labels |
432 |
301 |
433 def _mergediff(m3, name_a, name_b, name_base): |
302 |
|
303 def _detect_newline(m3): |
|
304 if len(m3.a) > 0: |
|
305 if m3.a[0].endswith(b'\r\n'): |
|
306 return b'\r\n' |
|
307 elif m3.a[0].endswith(b'\r'): |
|
308 return b'\r' |
|
309 return b'\n' |
|
310 |
|
311 |
|
312 def _minimize(a_lines, b_lines): |
|
313 """Trim conflict regions of lines where A and B sides match. |
|
314 |
|
315 Lines where both A and B have made the same changes at the beginning |
|
316 or the end of each merge region are eliminated from the conflict |
|
317 region and are instead considered the same. |
|
318 """ |
|
319 alen = len(a_lines) |
|
320 blen = len(b_lines) |
|
321 |
|
322 # find matches at the front |
|
323 ii = 0 |
|
324 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]: |
|
325 ii += 1 |
|
326 startmatches = ii |
|
327 |
|
328 # find matches at the end |
|
329 ii = 0 |
|
330 while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]: |
|
331 ii += 1 |
|
332 endmatches = ii |
|
333 |
|
334 lines_before = a_lines[:startmatches] |
|
335 new_a_lines = a_lines[startmatches : alen - endmatches] |
|
336 new_b_lines = b_lines[startmatches : blen - endmatches] |
|
337 lines_after = a_lines[alen - endmatches :] |
|
338 return lines_before, new_a_lines, new_b_lines, lines_after |
|
339 |
|
340 |
|
341 def render_minimized( |
|
342 m3, |
|
343 name_a=None, |
|
344 name_b=None, |
|
345 start_marker=b'<<<<<<<', |
|
346 mid_marker=b'=======', |
|
347 end_marker=b'>>>>>>>', |
|
348 ): |
|
349 """Return merge in cvs-like form.""" |
|
350 newline = _detect_newline(m3) |
|
351 conflicts = False |
|
352 if name_a: |
|
353 start_marker = start_marker + b' ' + name_a |
|
354 if name_b: |
|
355 end_marker = end_marker + b' ' + name_b |
|
356 merge_groups = m3.merge_groups() |
|
357 lines = [] |
|
358 for what, group_lines in merge_groups: |
|
359 if what == b'conflict': |
|
360 conflicts = True |
|
361 base_lines, a_lines, b_lines = group_lines |
|
362 minimized = _minimize(a_lines, b_lines) |
|
363 lines_before, a_lines, b_lines, lines_after = minimized |
|
364 lines.extend(lines_before) |
|
365 lines.append(start_marker + newline) |
|
366 lines.extend(a_lines) |
|
367 lines.append(mid_marker + newline) |
|
368 lines.extend(b_lines) |
|
369 lines.append(end_marker + newline) |
|
370 lines.extend(lines_after) |
|
371 else: |
|
372 lines.extend(group_lines) |
|
373 return lines, conflicts |
|
374 |
|
375 |
|
376 def render_merge3(m3, name_a, name_b, name_base): |
|
377 """Render conflicts as 3-way conflict markers.""" |
|
378 newline = _detect_newline(m3) |
|
379 conflicts = False |
|
380 lines = [] |
|
381 for what, group_lines in m3.merge_groups(): |
|
382 if what == b'conflict': |
|
383 base_lines, a_lines, b_lines = group_lines |
|
384 conflicts = True |
|
385 lines.append(b'<<<<<<< ' + name_a + newline) |
|
386 lines.extend(a_lines) |
|
387 lines.append(b'||||||| ' + name_base + newline) |
|
388 lines.extend(base_lines) |
|
389 lines.append(b'=======' + newline) |
|
390 lines.extend(b_lines) |
|
391 lines.append(b'>>>>>>> ' + name_b + newline) |
|
392 else: |
|
393 lines.extend(group_lines) |
|
394 return lines, conflicts |
|
395 |
|
396 |
|
397 def render_mergediff(m3, name_a, name_b, name_base): |
|
398 """Render conflicts as conflict markers with one snapshot and one diff.""" |
|
399 newline = _detect_newline(m3) |
434 lines = [] |
400 lines = [] |
435 conflicts = False |
401 conflicts = False |
436 for group in m3.merge_groups(): |
402 for what, group_lines in m3.merge_groups(): |
437 if group[0] == b'conflict': |
403 if what == b'conflict': |
438 base_lines, a_lines, b_lines = group[1:] |
404 base_lines, a_lines, b_lines = group_lines |
439 base_text = b''.join(base_lines) |
405 base_text = b''.join(base_lines) |
440 b_blocks = list( |
406 b_blocks = list( |
441 mdiff.allblocks( |
407 mdiff.allblocks( |
442 base_text, |
408 base_text, |
443 b''.join(b_lines), |
409 b''.join(b_lines), |
470 for line in lines1[block[0] : block[1]]: |
436 for line in lines1[block[0] : block[1]]: |
471 yield b'-' + line |
437 yield b'-' + line |
472 for line in lines2[block[2] : block[3]]: |
438 for line in lines2[block[2] : block[3]]: |
473 yield b'+' + line |
439 yield b'+' + line |
474 |
440 |
475 lines.append(b"<<<<<<<\n") |
441 lines.append(b"<<<<<<<" + newline) |
476 if matching_lines(a_blocks) < matching_lines(b_blocks): |
442 if matching_lines(a_blocks) < matching_lines(b_blocks): |
477 lines.append(b"======= %s\n" % name_a) |
443 lines.append(b"======= " + name_a + newline) |
478 lines.extend(a_lines) |
444 lines.extend(a_lines) |
479 lines.append(b"------- %s\n" % name_base) |
445 lines.append(b"------- " + name_base + newline) |
480 lines.append(b"+++++++ %s\n" % name_b) |
446 lines.append(b"+++++++ " + name_b + newline) |
481 lines.extend(diff_lines(b_blocks, base_lines, b_lines)) |
447 lines.extend(diff_lines(b_blocks, base_lines, b_lines)) |
482 else: |
448 else: |
483 lines.append(b"------- %s\n" % name_base) |
449 lines.append(b"------- " + name_base + newline) |
484 lines.append(b"+++++++ %s\n" % name_a) |
450 lines.append(b"+++++++ " + name_a + newline) |
485 lines.extend(diff_lines(a_blocks, base_lines, a_lines)) |
451 lines.extend(diff_lines(a_blocks, base_lines, a_lines)) |
486 lines.append(b"======= %s\n" % name_b) |
452 lines.append(b"======= " + name_b + newline) |
487 lines.extend(b_lines) |
453 lines.extend(b_lines) |
488 lines.append(b">>>>>>>\n") |
454 lines.append(b">>>>>>>" + newline) |
489 conflicts = True |
455 conflicts = True |
490 else: |
456 else: |
491 lines.extend(group[1]) |
457 lines.extend(group_lines) |
492 return lines, conflicts |
458 return lines, conflicts |
493 |
459 |
494 |
460 |
495 def simplemerge(ui, localctx, basectx, otherctx, **opts): |
461 def _resolve(m3, sides): |
|
462 lines = [] |
|
463 for what, group_lines in m3.merge_groups(): |
|
464 if what == b'conflict': |
|
465 for side in sides: |
|
466 lines.extend(group_lines[side]) |
|
467 else: |
|
468 lines.extend(group_lines) |
|
469 return lines |
|
470 |
|
471 |
|
472 class MergeInput(object): |
|
473 def __init__(self, fctx, label=None, label_detail=None): |
|
474 self.fctx = fctx |
|
475 self.label = label |
|
476 # If the "detail" part is set, then that is rendered after the label and |
|
477 # separated by a ':'. The label is padded to make the ':' aligned among |
|
478 # all merge inputs. |
|
479 self.label_detail = label_detail |
|
480 self._text = None |
|
481 |
|
482 def text(self): |
|
483 if self._text is None: |
|
484 # Merges were always run in the working copy before, which means |
|
485 # they used decoded data, if the user defined any repository |
|
486 # filters. |
|
487 # |
|
488 # Maintain that behavior today for BC, though perhaps in the future |
|
489 # it'd be worth considering whether merging encoded data (what the |
|
490 # repository usually sees) might be more useful. |
|
491 self._text = self.fctx.decodeddata() |
|
492 return self._text |
|
493 |
|
494 |
|
495 def simplemerge( |
|
496 local, |
|
497 base, |
|
498 other, |
|
499 mode=b'merge', |
|
500 allow_binary=False, |
|
501 ): |
496 """Performs the simplemerge algorithm. |
502 """Performs the simplemerge algorithm. |
497 |
503 |
498 The merged result is written into `localctx`. |
504 The merged result is written into `localctx`. |
499 """ |
505 """ |
500 |
506 |
501 def readctx(ctx): |
507 if not allow_binary: |
502 # Merges were always run in the working copy before, which means |
508 _verifytext(local) |
503 # they used decoded data, if the user defined any repository |
509 _verifytext(base) |
504 # filters. |
510 _verifytext(other) |
505 # |
511 |
506 # Maintain that behavior today for BC, though perhaps in the future |
512 m3 = Merge3Text(base.text(), local.text(), other.text()) |
507 # it'd be worth considering whether merging encoded data (what the |
513 conflicts = False |
508 # repository usually sees) might be more useful. |
|
509 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts) |
|
510 |
|
511 mode = opts.get('mode', b'merge') |
|
512 name_a, name_b, name_base = None, None, None |
|
513 if mode != b'union': |
|
514 name_a, name_b, name_base = _picklabels( |
|
515 [localctx.path(), otherctx.path(), None], opts.get('label', []) |
|
516 ) |
|
517 |
|
518 try: |
|
519 localtext = readctx(localctx) |
|
520 basetext = readctx(basectx) |
|
521 othertext = readctx(otherctx) |
|
522 except error.Abort: |
|
523 return 1 |
|
524 |
|
525 m3 = Merge3Text(basetext, localtext, othertext) |
|
526 extrakwargs = { |
|
527 b"localorother": opts.get("localorother", None), |
|
528 b'minimize': True, |
|
529 } |
|
530 if mode == b'union': |
514 if mode == b'union': |
531 extrakwargs[b'start_marker'] = None |
515 lines = _resolve(m3, (1, 2)) |
532 extrakwargs[b'mid_marker'] = None |
516 elif mode == b'local': |
533 extrakwargs[b'end_marker'] = None |
517 lines = _resolve(m3, (1,)) |
534 elif name_base is not None: |
518 elif mode == b'other': |
535 extrakwargs[b'base_marker'] = b'|||||||' |
519 lines = _resolve(m3, (2,)) |
536 extrakwargs[b'name_base'] = name_base |
|
537 extrakwargs[b'minimize'] = False |
|
538 |
|
539 if mode == b'mergediff': |
|
540 lines, conflicts = _mergediff(m3, name_a, name_b, name_base) |
|
541 else: |
520 else: |
542 lines = list( |
521 if mode == b'mergediff': |
543 m3.merge_lines( |
522 labels = _format_labels(local, other, base) |
544 name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs) |
523 lines, conflicts = render_mergediff(m3, *labels) |
545 ) |
524 elif mode == b'merge3': |
546 ) |
525 labels = _format_labels(local, other, base) |
547 conflicts = m3.conflicts |
526 lines, conflicts = render_merge3(m3, *labels) |
548 |
527 else: |
549 # merge flags if necessary |
528 labels = _format_labels(local, other) |
550 flags = localctx.flags() |
529 lines, conflicts = render_minimized(m3, *labels) |
551 localflags = set(pycompat.iterbytestr(flags)) |
|
552 otherflags = set(pycompat.iterbytestr(otherctx.flags())) |
|
553 if is_not_null(basectx) and localflags != otherflags: |
|
554 baseflags = set(pycompat.iterbytestr(basectx.flags())) |
|
555 commonflags = localflags & otherflags |
|
556 addedflags = (localflags ^ otherflags) - baseflags |
|
557 flags = b''.join(sorted(commonflags | addedflags)) |
|
558 |
530 |
559 mergedtext = b''.join(lines) |
531 mergedtext = b''.join(lines) |
560 if opts.get('print'): |
532 return mergedtext, conflicts |
561 ui.fout.write(mergedtext) |
|
562 else: |
|
563 localctx.write(mergedtext, flags) |
|
564 |
|
565 if conflicts and not mode == b'union': |
|
566 return 1 |
|