mercurial/simplemerge.py
branchstable
changeset 48796 c00d3ce4e94b
parent 48758 7dad4665d223
child 48875 6000f5b25c9b
equal deleted inserted replaced
48776:b84ff512b645 48796:c00d3ce4e94b
    17 # s: "i hate that."
    17 # s: "i hate that."
    18 
    18 
    19 from __future__ import absolute_import
    19 from __future__ import absolute_import
    20 
    20 
    21 from .i18n import _
    21 from .i18n import _
    22 from .node import nullrev
       
    23 from . import (
    22 from . import (
    24     error,
    23     error,
    25     mdiff,
    24     mdiff,
    26     pycompat,
    25     pycompat,
    27     util,
       
    28 )
    26 )
    29 from .utils import stringutil
    27 from .utils import stringutil
    30 
       
    31 
       
    32 class CantReprocessAndShowBase(Exception):
       
    33     pass
       
    34 
    28 
    35 
    29 
    36 def intersect(ra, rb):
    30 def intersect(ra, rb):
    37     """Given two ranges return the range where they intersect or None.
    31     """Given two ranges return the range where they intersect or None.
    38 
    32 
    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
   168              Lines taken from a (and equal to b)
    96              Lines taken from a (and equal to b)
   169 
    97 
   170         'b', lines
    98         'b', lines
   171              Lines taken from b
    99              Lines taken from b
   172 
   100 
   173         'conflict', base_lines, a_lines, b_lines
   101         'conflict', (base_lines, a_lines, b_lines)
   174              Lines from base were changed to either a or b and conflict.
   102              Lines from base were changed to either a or b and conflict.
   175         """
   103         """
   176         for t in self.merge_regions():
   104         for t in self.merge_regions():
   177             what = t[0]
   105             what = t[0]
   178             if what == b'unchanged':
   106             if what == b'unchanged':
   182             elif what == b'b':
   110             elif what == b'b':
   183                 yield what, self.b[t[1] : t[2]]
   111                 yield what, self.b[t[1] : t[2]]
   184             elif what == b'conflict':
   112             elif what == b'conflict':
   185                 yield (
   113                 yield (
   186                     what,
   114                     what,
   187                     self.base[t[1] : t[2]],
   115                     (
   188                     self.a[t[3] : t[4]],
   116                         self.base[t[1] : t[2]],
   189                     self.b[t[5] : t[6]],
   117                         self.a[t[3] : t[4]],
       
   118                         self.b[t[5] : t[6]],
       
   119                     ),
   190                 )
   120                 )
   191             else:
   121             else:
   192                 raise ValueError(what)
   122                 raise ValueError(what)
   193 
   123 
   194     def merge_regions(self):
   124     def merge_regions(self):
   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