Mercurial > public > mercurial-scm > hg
comparison mercurial/simplemerge.py @ 46108:bdc2bf68f19e
mergetools: add new conflict marker format with diffs in
I use 3-way conflict markers. Often when I resolve them, I manually
compare one the base with one side and apply the differences to the
other side. That can be hard when the conflict marker is large. This
patch introduces a new type of conflict marker, which I'm hoping will
make it easier to resolve conflicts.
The new format uses `<<<<<<<` and `>>>>>>>` to open and close the
markers, just like our existing 2-way and 3-way conflict
markers. Instead of having 2 or 3 snapshots (left+right or
left+base+right), it has a sequence of diffs. A diff looks like this:
```
------- base
+++++++ left
a
-b
+c
d
```
A diff that adds one side ("diff from nothing") has a `=======` header
instead and does not have have `+` prefixed on its lines. A regular
3-way merge can be viewed as adding one side plus a diff between the
base and the other side. It thus has two ways of being represented,
depending on which side is being diffed:
```
<<<<<<<
======= left
contents
on
left
------- base
+++++++ right
contents
on
-left
+right
>>>>>>>
```
or
```
<<<<<<<
------- base
+++++++ left
contents
on
-right
+left
======= right
contents
on
right
>>>>>>>
```
I've made it so the new merge tool tries to pick a version that has
the most common lines (no difference in the example above).
I've called the new tool "mergediff" to stick to the convention of
starting with "merge" if the tool tries a regular 3-way merge.
The idea came from my pet VCS (placeholder name `jj`), which has
support for octopus merges and other ways of ending up with merges of
more than 3 versions. I wanted to be able to represent such conflicts
in the working copy and therefore thought of this format (although I
have not yet implemented it in my VCS). I then attended a meeting with
Larry McVoy, who said BitKeeper has an option (`bk smerge -g`) for
showing a similar format, which reminded me to actually attempt this
in Mercurial.
Differential Revision: https://phab.mercurial-scm.org/D9551
author | Martin von Zweigbergk <martinvonz@google.com> |
---|---|
date | Tue, 08 Dec 2020 16:45:13 -0800 |
parents | a771ffc378a8 |
children | 59fa3890d40a |
comparison
equal
deleted
inserted
replaced
46107:aa4dbc14f735 | 46108:bdc2bf68f19e |
---|---|
453 if not util.safehasattr(ctx, "node"): | 453 if not util.safehasattr(ctx, "node"): |
454 return False | 454 return False |
455 return ctx.node() != nodemod.nullid | 455 return ctx.node() != nodemod.nullid |
456 | 456 |
457 | 457 |
458 def _mergediff(m3, name_a, name_b, name_base): | |
459 lines = [] | |
460 conflicts = False | |
461 for group in m3.merge_groups(): | |
462 if group[0] == b'conflict': | |
463 base_lines, a_lines, b_lines = group[1:] | |
464 base_text = b''.join(base_lines) | |
465 b_blocks = list( | |
466 mdiff.allblocks( | |
467 base_text, | |
468 b''.join(b_lines), | |
469 lines1=base_lines, | |
470 lines2=b_lines, | |
471 ) | |
472 ) | |
473 a_blocks = list( | |
474 mdiff.allblocks( | |
475 base_text, | |
476 b''.join(a_lines), | |
477 lines1=base_lines, | |
478 lines2=b_lines, | |
479 ) | |
480 ) | |
481 | |
482 def matching_lines(blocks): | |
483 return sum( | |
484 block[1] - block[0] | |
485 for block, kind in blocks | |
486 if kind == b'=' | |
487 ) | |
488 | |
489 def diff_lines(blocks, lines1, lines2): | |
490 for block, kind in blocks: | |
491 if kind == b'=': | |
492 for line in lines1[block[0] : block[1]]: | |
493 yield b' ' + line | |
494 else: | |
495 for line in lines1[block[0] : block[1]]: | |
496 yield b'-' + line | |
497 for line in lines2[block[2] : block[3]]: | |
498 yield b'+' + line | |
499 | |
500 lines.append(b"<<<<<<<\n") | |
501 if matching_lines(a_blocks) < matching_lines(b_blocks): | |
502 lines.append(b"======= %s\n" % name_a) | |
503 lines.extend(a_lines) | |
504 lines.append(b"------- %s\n" % name_base) | |
505 lines.append(b"+++++++ %s\n" % name_b) | |
506 lines.extend(diff_lines(b_blocks, base_lines, b_lines)) | |
507 else: | |
508 lines.append(b"------- %s\n" % name_base) | |
509 lines.append(b"+++++++ %s\n" % name_a) | |
510 lines.extend(diff_lines(a_blocks, base_lines, a_lines)) | |
511 lines.append(b"======= %s\n" % name_b) | |
512 lines.extend(b_lines) | |
513 lines.append(b">>>>>>>\n") | |
514 conflicts = True | |
515 else: | |
516 lines.extend(group[1]) | |
517 return lines, conflicts | |
518 | |
519 | |
458 def simplemerge(ui, localctx, basectx, otherctx, **opts): | 520 def simplemerge(ui, localctx, basectx, otherctx, **opts): |
459 """Performs the simplemerge algorithm. | 521 """Performs the simplemerge algorithm. |
460 | 522 |
461 The merged result is written into `localctx`. | 523 The merged result is written into `localctx`. |
462 """ | 524 """ |
497 elif name_base is not None: | 559 elif name_base is not None: |
498 extrakwargs[b'base_marker'] = b'|||||||' | 560 extrakwargs[b'base_marker'] = b'|||||||' |
499 extrakwargs[b'name_base'] = name_base | 561 extrakwargs[b'name_base'] = name_base |
500 extrakwargs[b'minimize'] = False | 562 extrakwargs[b'minimize'] = False |
501 | 563 |
502 lines = m3.merge_lines( | 564 if mode == b'mergediff': |
503 name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs) | 565 lines, conflicts = _mergediff(m3, name_a, name_b, name_base) |
504 ) | 566 else: |
567 lines = list( | |
568 m3.merge_lines( | |
569 name_a=name_a, name_b=name_b, **pycompat.strkwargs(extrakwargs) | |
570 ) | |
571 ) | |
572 conflicts = m3.conflicts | |
505 | 573 |
506 # merge flags if necessary | 574 # merge flags if necessary |
507 flags = localctx.flags() | 575 flags = localctx.flags() |
508 localflags = set(pycompat.iterbytestr(flags)) | 576 localflags = set(pycompat.iterbytestr(flags)) |
509 otherflags = set(pycompat.iterbytestr(otherctx.flags())) | 577 otherflags = set(pycompat.iterbytestr(otherctx.flags())) |
517 if opts.get('print'): | 585 if opts.get('print'): |
518 ui.fout.write(mergedtext) | 586 ui.fout.write(mergedtext) |
519 else: | 587 else: |
520 localctx.write(mergedtext, flags) | 588 localctx.write(mergedtext, flags) |
521 | 589 |
522 if m3.conflicts and not mode == b'union': | 590 if conflicts and not mode == b'union': |
523 return 1 | 591 return 1 |