mercurial/filemerge.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    40     stringutil,
    40     stringutil,
    41 )
    41 )
    42 
    42 
    43 
    43 
    44 def _toolstr(ui, tool, part, *args):
    44 def _toolstr(ui, tool, part, *args):
    45     return ui.config("merge-tools", tool + "." + part, *args)
    45     return ui.config(b"merge-tools", tool + b"." + part, *args)
    46 
    46 
    47 
    47 
    48 def _toolbool(ui, tool, part, *args):
    48 def _toolbool(ui, tool, part, *args):
    49     return ui.configbool("merge-tools", tool + "." + part, *args)
    49     return ui.configbool(b"merge-tools", tool + b"." + part, *args)
    50 
    50 
    51 
    51 
    52 def _toollist(ui, tool, part):
    52 def _toollist(ui, tool, part):
    53     return ui.configlist("merge-tools", tool + "." + part)
    53     return ui.configlist(b"merge-tools", tool + b"." + part)
    54 
    54 
    55 
    55 
    56 internals = {}
    56 internals = {}
    57 # Merge tools to document.
    57 # Merge tools to document.
    58 internalsdoc = {}
    58 internalsdoc = {}
    67 # IMPORTANT: keep the last line of this prompt very short ("What do you want to
    67 # IMPORTANT: keep the last line of this prompt very short ("What do you want to
    68 # do?") because of issue6158, ideally to <40 English characters (to allow other
    68 # do?") because of issue6158, ideally to <40 English characters (to allow other
    69 # languages that may take more columns to still have a chance to fit in an
    69 # languages that may take more columns to still have a chance to fit in an
    70 # 80-column screen).
    70 # 80-column screen).
    71 _localchangedotherdeletedmsg = _(
    71 _localchangedotherdeletedmsg = _(
    72     "file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
    72     b"file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
    73     "You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n"
    73     b"You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n"
    74     "What do you want to do?"
    74     b"What do you want to do?"
    75     "$$ &Changed $$ &Delete $$ &Unresolved"
    75     b"$$ &Changed $$ &Delete $$ &Unresolved"
    76 )
    76 )
    77 
    77 
    78 _otherchangedlocaldeletedmsg = _(
    78 _otherchangedlocaldeletedmsg = _(
    79     "file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
    79     b"file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
    80     "You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n"
    80     b"You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n"
    81     "What do you want to do?"
    81     b"What do you want to do?"
    82     "$$ &Changed $$ &Deleted $$ &Unresolved"
    82     b"$$ &Changed $$ &Deleted $$ &Unresolved"
    83 )
    83 )
    84 
    84 
    85 
    85 
    86 class absentfilectx(object):
    86 class absentfilectx(object):
    87     """Represents a file that's ostensibly in a context but is actually not
    87     """Represents a file that's ostensibly in a context but is actually not
   118             and fctx.ctx() == self.ctx()
   118             and fctx.ctx() == self.ctx()
   119             and fctx.path() == self.path()
   119             and fctx.path() == self.path()
   120         )
   120         )
   121 
   121 
   122     def flags(self):
   122     def flags(self):
   123         return ''
   123         return b''
   124 
   124 
   125     def changectx(self):
   125     def changectx(self):
   126         return self._ctx
   126         return self._ctx
   127 
   127 
   128     def isbinary(self):
   128     def isbinary(self):
   133 
   133 
   134 
   134 
   135 def _findtool(ui, tool):
   135 def _findtool(ui, tool):
   136     if tool in internals:
   136     if tool in internals:
   137         return tool
   137         return tool
   138     cmd = _toolstr(ui, tool, "executable", tool)
   138     cmd = _toolstr(ui, tool, b"executable", tool)
   139     if cmd.startswith('python:'):
   139     if cmd.startswith(b'python:'):
   140         return cmd
   140         return cmd
   141     return findexternaltool(ui, tool)
   141     return findexternaltool(ui, tool)
   142 
   142 
   143 
   143 
   144 def _quotetoolpath(cmd):
   144 def _quotetoolpath(cmd):
   145     if cmd.startswith('python:'):
   145     if cmd.startswith(b'python:'):
   146         return cmd
   146         return cmd
   147     return procutil.shellquote(cmd)
   147     return procutil.shellquote(cmd)
   148 
   148 
   149 
   149 
   150 def findexternaltool(ui, tool):
   150 def findexternaltool(ui, tool):
   151     for kn in ("regkey", "regkeyalt"):
   151     for kn in (b"regkey", b"regkeyalt"):
   152         k = _toolstr(ui, tool, kn)
   152         k = _toolstr(ui, tool, kn)
   153         if not k:
   153         if not k:
   154             continue
   154             continue
   155         p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
   155         p = util.lookupreg(k, _toolstr(ui, tool, b"regname"))
   156         if p:
   156         if p:
   157             p = procutil.findexe(p + _toolstr(ui, tool, "regappend", ""))
   157             p = procutil.findexe(p + _toolstr(ui, tool, b"regappend", b""))
   158             if p:
   158             if p:
   159                 return p
   159                 return p
   160     exe = _toolstr(ui, tool, "executable", tool)
   160     exe = _toolstr(ui, tool, b"executable", tool)
   161     return procutil.findexe(util.expandpath(exe))
   161     return procutil.findexe(util.expandpath(exe))
   162 
   162 
   163 
   163 
   164 def _picktool(repo, ui, path, binary, symlink, changedelete):
   164 def _picktool(repo, ui, path, binary, symlink, changedelete):
   165     strictcheck = ui.configbool('merge', 'strict-capability-check')
   165     strictcheck = ui.configbool(b'merge', b'strict-capability-check')
   166 
   166 
   167     def hascapability(tool, capability, strict=False):
   167     def hascapability(tool, capability, strict=False):
   168         if tool in internals:
   168         if tool in internals:
   169             return strict and internals[tool].capabilities.get(capability)
   169             return strict and internals[tool].capabilities.get(capability)
   170         return _toolbool(ui, tool, capability)
   170         return _toolbool(ui, tool, capability)
   173         return tool in internals and internals[tool].mergetype == nomerge
   173         return tool in internals and internals[tool].mergetype == nomerge
   174 
   174 
   175     def check(tool, pat, symlink, binary, changedelete):
   175     def check(tool, pat, symlink, binary, changedelete):
   176         tmsg = tool
   176         tmsg = tool
   177         if pat:
   177         if pat:
   178             tmsg = _("%s (for pattern %s)") % (tool, pat)
   178             tmsg = _(b"%s (for pattern %s)") % (tool, pat)
   179         if not _findtool(ui, tool):
   179         if not _findtool(ui, tool):
   180             if pat:  # explicitly requested tool deserves a warning
   180             if pat:  # explicitly requested tool deserves a warning
   181                 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
   181                 ui.warn(_(b"couldn't find merge tool %s\n") % tmsg)
   182             else:  # configured but non-existing tools are more silent
   182             else:  # configured but non-existing tools are more silent
   183                 ui.note(_("couldn't find merge tool %s\n") % tmsg)
   183                 ui.note(_(b"couldn't find merge tool %s\n") % tmsg)
   184         elif symlink and not hascapability(tool, "symlink", strictcheck):
   184         elif symlink and not hascapability(tool, b"symlink", strictcheck):
   185             ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
   185             ui.warn(_(b"tool %s can't handle symlinks\n") % tmsg)
   186         elif binary and not hascapability(tool, "binary", strictcheck):
   186         elif binary and not hascapability(tool, b"binary", strictcheck):
   187             ui.warn(_("tool %s can't handle binary\n") % tmsg)
   187             ui.warn(_(b"tool %s can't handle binary\n") % tmsg)
   188         elif changedelete and not supportscd(tool):
   188         elif changedelete and not supportscd(tool):
   189             # the nomerge tools are the only tools that support change/delete
   189             # the nomerge tools are the only tools that support change/delete
   190             # conflicts
   190             # conflicts
   191             pass
   191             pass
   192         elif not procutil.gui() and _toolbool(ui, tool, "gui"):
   192         elif not procutil.gui() and _toolbool(ui, tool, b"gui"):
   193             ui.warn(_("tool %s requires a GUI\n") % tmsg)
   193             ui.warn(_(b"tool %s requires a GUI\n") % tmsg)
   194         else:
   194         else:
   195             return True
   195             return True
   196         return False
   196         return False
   197 
   197 
   198     # internal config: ui.forcemerge
   198     # internal config: ui.forcemerge
   199     # forcemerge comes from command line arguments, highest priority
   199     # forcemerge comes from command line arguments, highest priority
   200     force = ui.config('ui', 'forcemerge')
   200     force = ui.config(b'ui', b'forcemerge')
   201     if force:
   201     if force:
   202         toolpath = _findtool(ui, force)
   202         toolpath = _findtool(ui, force)
   203         if changedelete and not supportscd(toolpath):
   203         if changedelete and not supportscd(toolpath):
   204             return ":prompt", None
   204             return b":prompt", None
   205         else:
   205         else:
   206             if toolpath:
   206             if toolpath:
   207                 return (force, _quotetoolpath(toolpath))
   207                 return (force, _quotetoolpath(toolpath))
   208             else:
   208             else:
   209                 # mimic HGMERGE if given tool not found
   209                 # mimic HGMERGE if given tool not found
   210                 return (force, force)
   210                 return (force, force)
   211 
   211 
   212     # HGMERGE takes next precedence
   212     # HGMERGE takes next precedence
   213     hgmerge = encoding.environ.get("HGMERGE")
   213     hgmerge = encoding.environ.get(b"HGMERGE")
   214     if hgmerge:
   214     if hgmerge:
   215         if changedelete and not supportscd(hgmerge):
   215         if changedelete and not supportscd(hgmerge):
   216             return ":prompt", None
   216             return b":prompt", None
   217         else:
   217         else:
   218             return (hgmerge, hgmerge)
   218             return (hgmerge, hgmerge)
   219 
   219 
   220     # then patterns
   220     # then patterns
   221 
   221 
   222     # whether binary capability should be checked strictly
   222     # whether binary capability should be checked strictly
   223     binarycap = binary and strictcheck
   223     binarycap = binary and strictcheck
   224 
   224 
   225     for pat, tool in ui.configitems("merge-patterns"):
   225     for pat, tool in ui.configitems(b"merge-patterns"):
   226         mf = match.match(repo.root, '', [pat])
   226         mf = match.match(repo.root, b'', [pat])
   227         if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
   227         if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
   228             if binary and not hascapability(tool, "binary", strict=True):
   228             if binary and not hascapability(tool, b"binary", strict=True):
   229                 ui.warn(
   229                 ui.warn(
   230                     _(
   230                     _(
   231                         "warning: check merge-patterns configurations,"
   231                         b"warning: check merge-patterns configurations,"
   232                         " if %r for binary file %r is unintentional\n"
   232                         b" if %r for binary file %r is unintentional\n"
   233                         "(see 'hg help merge-tools'"
   233                         b"(see 'hg help merge-tools'"
   234                         " for binary files capability)\n"
   234                         b" for binary files capability)\n"
   235                     )
   235                     )
   236                     % (pycompat.bytestr(tool), pycompat.bytestr(path))
   236                     % (pycompat.bytestr(tool), pycompat.bytestr(path))
   237                 )
   237                 )
   238             toolpath = _findtool(ui, tool)
   238             toolpath = _findtool(ui, tool)
   239             return (tool, _quotetoolpath(toolpath))
   239             return (tool, _quotetoolpath(toolpath))
   240 
   240 
   241     # then merge tools
   241     # then merge tools
   242     tools = {}
   242     tools = {}
   243     disabled = set()
   243     disabled = set()
   244     for k, v in ui.configitems("merge-tools"):
   244     for k, v in ui.configitems(b"merge-tools"):
   245         t = k.split('.')[0]
   245         t = k.split(b'.')[0]
   246         if t not in tools:
   246         if t not in tools:
   247             tools[t] = int(_toolstr(ui, t, "priority"))
   247             tools[t] = int(_toolstr(ui, t, b"priority"))
   248         if _toolbool(ui, t, "disabled"):
   248         if _toolbool(ui, t, b"disabled"):
   249             disabled.add(t)
   249             disabled.add(t)
   250     names = tools.keys()
   250     names = tools.keys()
   251     tools = sorted(
   251     tools = sorted(
   252         [(-p, tool) for tool, p in tools.items() if tool not in disabled]
   252         [(-p, tool) for tool, p in tools.items() if tool not in disabled]
   253     )
   253     )
   254     uimerge = ui.config("ui", "merge")
   254     uimerge = ui.config(b"ui", b"merge")
   255     if uimerge:
   255     if uimerge:
   256         # external tools defined in uimerge won't be able to handle
   256         # external tools defined in uimerge won't be able to handle
   257         # change/delete conflicts
   257         # change/delete conflicts
   258         if check(uimerge, path, symlink, binary, changedelete):
   258         if check(uimerge, path, symlink, binary, changedelete):
   259             if uimerge not in names and not changedelete:
   259             if uimerge not in names and not changedelete:
   260                 return (uimerge, uimerge)
   260                 return (uimerge, uimerge)
   261             tools.insert(0, (None, uimerge))  # highest priority
   261             tools.insert(0, (None, uimerge))  # highest priority
   262     tools.append((None, "hgmerge"))  # the old default, if found
   262     tools.append((None, b"hgmerge"))  # the old default, if found
   263     for p, t in tools:
   263     for p, t in tools:
   264         if check(t, None, symlink, binary, changedelete):
   264         if check(t, None, symlink, binary, changedelete):
   265             toolpath = _findtool(ui, t)
   265             toolpath = _findtool(ui, t)
   266             return (t, _quotetoolpath(toolpath))
   266             return (t, _quotetoolpath(toolpath))
   267 
   267 
   268     # internal merge or prompt as last resort
   268     # internal merge or prompt as last resort
   269     if symlink or binary or changedelete:
   269     if symlink or binary or changedelete:
   270         if not changedelete and len(tools):
   270         if not changedelete and len(tools):
   271             # any tool is rejected by capability for symlink or binary
   271             # any tool is rejected by capability for symlink or binary
   272             ui.warn(_("no tool found to merge %s\n") % path)
   272             ui.warn(_(b"no tool found to merge %s\n") % path)
   273         return ":prompt", None
   273         return b":prompt", None
   274     return ":merge", None
   274     return b":merge", None
   275 
   275 
   276 
   276 
   277 def _eoltype(data):
   277 def _eoltype(data):
   278     "Guess the EOL type of a file"
   278     b"Guess the EOL type of a file"
   279     if '\0' in data:  # binary
   279     if b'\0' in data:  # binary
   280         return None
   280         return None
   281     if '\r\n' in data:  # Windows
   281     if b'\r\n' in data:  # Windows
   282         return '\r\n'
   282         return b'\r\n'
   283     if '\r' in data:  # Old Mac
   283     if b'\r' in data:  # Old Mac
   284         return '\r'
   284         return b'\r'
   285     if '\n' in data:  # UNIX
   285     if b'\n' in data:  # UNIX
   286         return '\n'
   286         return b'\n'
   287     return None  # unknown
   287     return None  # unknown
   288 
   288 
   289 
   289 
   290 def _matcheol(file, back):
   290 def _matcheol(file, back):
   291     "Convert EOL markers in a file to match origfile"
   291     b"Convert EOL markers in a file to match origfile"
   292     tostyle = _eoltype(back.data())  # No repo.wread filters?
   292     tostyle = _eoltype(back.data())  # No repo.wread filters?
   293     if tostyle:
   293     if tostyle:
   294         data = util.readfile(file)
   294         data = util.readfile(file)
   295         style = _eoltype(data)
   295         style = _eoltype(data)
   296         if style:
   296         if style:
   297             newdata = data.replace(style, tostyle)
   297             newdata = data.replace(style, tostyle)
   298             if newdata != data:
   298             if newdata != data:
   299                 util.writefile(file, newdata)
   299                 util.writefile(file, newdata)
   300 
   300 
   301 
   301 
   302 @internaltool('prompt', nomerge)
   302 @internaltool(b'prompt', nomerge)
   303 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   303 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   304     """Asks the user which of the local `p1()` or the other `p2()` version to
   304     """Asks the user which of the local `p1()` or the other `p2()` version to
   305     keep as the merged version."""
   305     keep as the merged version."""
   306     ui = repo.ui
   306     ui = repo.ui
   307     fd = fcd.path()
   307     fd = fcd.path()
   309 
   309 
   310     # Avoid prompting during an in-memory merge since it doesn't support merge
   310     # Avoid prompting during an in-memory merge since it doesn't support merge
   311     # conflicts.
   311     # conflicts.
   312     if fcd.changectx().isinmemory():
   312     if fcd.changectx().isinmemory():
   313         raise error.InMemoryMergeConflictsError(
   313         raise error.InMemoryMergeConflictsError(
   314             'in-memory merge does not ' 'support file conflicts'
   314             b'in-memory merge does not ' b'support file conflicts'
   315         )
   315         )
   316 
   316 
   317     prompts = partextras(labels)
   317     prompts = partextras(labels)
   318     prompts['fd'] = uipathfn(fd)
   318     prompts[b'fd'] = uipathfn(fd)
   319     try:
   319     try:
   320         if fco.isabsent():
   320         if fco.isabsent():
   321             index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2)
   321             index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2)
   322             choice = ['local', 'other', 'unresolved'][index]
   322             choice = [b'local', b'other', b'unresolved'][index]
   323         elif fcd.isabsent():
   323         elif fcd.isabsent():
   324             index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2)
   324             index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2)
   325             choice = ['other', 'local', 'unresolved'][index]
   325             choice = [b'other', b'local', b'unresolved'][index]
   326         else:
   326         else:
   327             # IMPORTANT: keep the last line of this prompt ("What do you want to
   327             # IMPORTANT: keep the last line of this prompt ("What do you want to
   328             # do?") very short, see comment next to _localchangedotherdeletedmsg
   328             # do?") very short, see comment next to _localchangedotherdeletedmsg
   329             # at the top of the file for details.
   329             # at the top of the file for details.
   330             index = ui.promptchoice(
   330             index = ui.promptchoice(
   331                 _(
   331                 _(
   332                     "file '%(fd)s' needs to be resolved.\n"
   332                     b"file '%(fd)s' needs to be resolved.\n"
   333                     "You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave "
   333                     b"You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave "
   334                     "(u)nresolved.\n"
   334                     b"(u)nresolved.\n"
   335                     "What do you want to do?"
   335                     b"What do you want to do?"
   336                     "$$ &Local $$ &Other $$ &Unresolved"
   336                     b"$$ &Local $$ &Other $$ &Unresolved"
   337                 )
   337                 )
   338                 % prompts,
   338                 % prompts,
   339                 2,
   339                 2,
   340             )
   340             )
   341             choice = ['local', 'other', 'unresolved'][index]
   341             choice = [b'local', b'other', b'unresolved'][index]
   342 
   342 
   343         if choice == 'other':
   343         if choice == b'other':
   344             return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   344             return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   345         elif choice == 'local':
   345         elif choice == b'local':
   346             return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   346             return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   347         elif choice == 'unresolved':
   347         elif choice == b'unresolved':
   348             return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   348             return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   349     except error.ResponseExpected:
   349     except error.ResponseExpected:
   350         ui.write("\n")
   350         ui.write(b"\n")
   351         return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   351         return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
   352 
   352 
   353 
   353 
   354 @internaltool('local', nomerge)
   354 @internaltool(b'local', nomerge)
   355 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   355 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   356     """Uses the local `p1()` version of files as the merged version."""
   356     """Uses the local `p1()` version of files as the merged version."""
   357     return 0, fcd.isabsent()
   357     return 0, fcd.isabsent()
   358 
   358 
   359 
   359 
   360 @internaltool('other', nomerge)
   360 @internaltool(b'other', nomerge)
   361 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   361 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   362     """Uses the other `p2()` version of files as the merged version."""
   362     """Uses the other `p2()` version of files as the merged version."""
   363     if fco.isabsent():
   363     if fco.isabsent():
   364         # local changed, remote deleted -- 'deleted' picked
   364         # local changed, remote deleted -- 'deleted' picked
   365         _underlyingfctxifabsent(fcd).remove()
   365         _underlyingfctxifabsent(fcd).remove()
   368         _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
   368         _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
   369         deleted = False
   369         deleted = False
   370     return 0, deleted
   370     return 0, deleted
   371 
   371 
   372 
   372 
   373 @internaltool('fail', nomerge)
   373 @internaltool(b'fail', nomerge)
   374 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   374 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
   375     """
   375     """
   376     Rather than attempting to merge files that were modified on both
   376     Rather than attempting to merge files that were modified on both
   377     branches, it marks them as unresolved. The resolve command must be
   377     branches, it marks them as unresolved. The resolve command must be
   378     used to resolve these conflicts."""
   378     used to resolve these conflicts."""
   399         return 1
   399         return 1
   400     unused, unused, unused, back = files
   400     unused, unused, unused, back = files
   401 
   401 
   402     ui = repo.ui
   402     ui = repo.ui
   403 
   403 
   404     validkeep = ['keep', 'keep-merge3']
   404     validkeep = [b'keep', b'keep-merge3']
   405 
   405 
   406     # do we attempt to simplemerge first?
   406     # do we attempt to simplemerge first?
   407     try:
   407     try:
   408         premerge = _toolbool(ui, tool, "premerge", not binary)
   408         premerge = _toolbool(ui, tool, b"premerge", not binary)
   409     except error.ConfigError:
   409     except error.ConfigError:
   410         premerge = _toolstr(ui, tool, "premerge", "").lower()
   410         premerge = _toolstr(ui, tool, b"premerge", b"").lower()
   411         if premerge not in validkeep:
   411         if premerge not in validkeep:
   412             _valid = ', '.join(["'" + v + "'" for v in validkeep])
   412             _valid = b', '.join([b"'" + v + b"'" for v in validkeep])
   413             raise error.ConfigError(
   413             raise error.ConfigError(
   414                 _("%s.premerge not valid " "('%s' is neither boolean nor %s)")
   414                 _(b"%s.premerge not valid " b"('%s' is neither boolean nor %s)")
   415                 % (tool, premerge, _valid)
   415                 % (tool, premerge, _valid)
   416             )
   416             )
   417 
   417 
   418     if premerge:
   418     if premerge:
   419         if premerge == 'keep-merge3':
   419         if premerge == b'keep-merge3':
   420             if not labels:
   420             if not labels:
   421                 labels = _defaultconflictlabels
   421                 labels = _defaultconflictlabels
   422             if len(labels) < 3:
   422             if len(labels) < 3:
   423                 labels.append('base')
   423                 labels.append(b'base')
   424         r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
   424         r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
   425         if not r:
   425         if not r:
   426             ui.debug(" premerge successful\n")
   426             ui.debug(b" premerge successful\n")
   427             return 0
   427             return 0
   428         if premerge not in validkeep:
   428         if premerge not in validkeep:
   429             # restore from backup and try again
   429             # restore from backup and try again
   430             _restorebackup(fcd, back)
   430             _restorebackup(fcd, back)
   431     return 1  # continue merging
   431     return 1  # continue merging
   434 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
   434 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
   435     tool, toolpath, binary, symlink, scriptfn = toolconf
   435     tool, toolpath, binary, symlink, scriptfn = toolconf
   436     uipathfn = scmutil.getuipathfn(repo)
   436     uipathfn = scmutil.getuipathfn(repo)
   437     if symlink:
   437     if symlink:
   438         repo.ui.warn(
   438         repo.ui.warn(
   439             _('warning: internal %s cannot merge symlinks ' 'for %s\n')
   439             _(b'warning: internal %s cannot merge symlinks ' b'for %s\n')
   440             % (tool, uipathfn(fcd.path()))
   440             % (tool, uipathfn(fcd.path()))
   441         )
   441         )
   442         return False
   442         return False
   443     if fcd.isabsent() or fco.isabsent():
   443     if fcd.isabsent() or fco.isabsent():
   444         repo.ui.warn(
   444         repo.ui.warn(
   445             _(
   445             _(
   446                 'warning: internal %s cannot merge change/delete '
   446                 b'warning: internal %s cannot merge change/delete '
   447                 'conflict for %s\n'
   447                 b'conflict for %s\n'
   448             )
   448             )
   449             % (tool, uipathfn(fcd.path()))
   449             % (tool, uipathfn(fcd.path()))
   450         )
   450         )
   451         return False
   451         return False
   452     return True
   452     return True
   463     r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
   463     r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
   464     return True, r, False
   464     return True, r, False
   465 
   465 
   466 
   466 
   467 @internaltool(
   467 @internaltool(
   468     'union',
   468     b'union',
   469     fullmerge,
   469     fullmerge,
   470     _(
   470     _(
   471         "warning: conflicts while merging %s! "
   471         b"warning: conflicts while merging %s! "
   472         "(edit, then use 'hg resolve --mark')\n"
   472         b"(edit, then use 'hg resolve --mark')\n"
   473     ),
   473     ),
   474     precheck=_mergecheck,
   474     precheck=_mergecheck,
   475 )
   475 )
   476 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   476 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   477     """
   477     """
   478     Uses the internal non-interactive simple merge algorithm for merging
   478     Uses the internal non-interactive simple merge algorithm for merging
   479     files. It will use both left and right sides for conflict regions.
   479     files. It will use both left and right sides for conflict regions.
   480     No markers are inserted."""
   480     No markers are inserted."""
   481     return _merge(
   481     return _merge(
   482         repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, 'union'
   482         repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'union'
   483     )
   483     )
   484 
   484 
   485 
   485 
   486 @internaltool(
   486 @internaltool(
   487     'merge',
   487     b'merge',
   488     fullmerge,
   488     fullmerge,
   489     _(
   489     _(
   490         "warning: conflicts while merging %s! "
   490         b"warning: conflicts while merging %s! "
   491         "(edit, then use 'hg resolve --mark')\n"
   491         b"(edit, then use 'hg resolve --mark')\n"
   492     ),
   492     ),
   493     precheck=_mergecheck,
   493     precheck=_mergecheck,
   494 )
   494 )
   495 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   495 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   496     """
   496     """
   497     Uses the internal non-interactive simple merge algorithm for merging
   497     Uses the internal non-interactive simple merge algorithm for merging
   498     files. It will fail if there are any conflicts and leave markers in
   498     files. It will fail if there are any conflicts and leave markers in
   499     the partially merged file. Markers will have two sections, one for each side
   499     the partially merged file. Markers will have two sections, one for each side
   500     of merge."""
   500     of merge."""
   501     return _merge(
   501     return _merge(
   502         repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, 'merge'
   502         repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'merge'
   503     )
   503     )
   504 
   504 
   505 
   505 
   506 @internaltool(
   506 @internaltool(
   507     'merge3',
   507     b'merge3',
   508     fullmerge,
   508     fullmerge,
   509     _(
   509     _(
   510         "warning: conflicts while merging %s! "
   510         b"warning: conflicts while merging %s! "
   511         "(edit, then use 'hg resolve --mark')\n"
   511         b"(edit, then use 'hg resolve --mark')\n"
   512     ),
   512     ),
   513     precheck=_mergecheck,
   513     precheck=_mergecheck,
   514 )
   514 )
   515 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   515 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   516     """
   516     """
   519     the partially merged file. Marker will have three sections, one from each
   519     the partially merged file. Marker will have three sections, one from each
   520     side of the merge and one for the base content."""
   520     side of the merge and one for the base content."""
   521     if not labels:
   521     if not labels:
   522         labels = _defaultconflictlabels
   522         labels = _defaultconflictlabels
   523     if len(labels) < 3:
   523     if len(labels) < 3:
   524         labels.append('base')
   524         labels.append(b'base')
   525     return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
   525     return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
   526 
   526 
   527 
   527 
   528 def _imergeauto(
   528 def _imergeauto(
   529     repo,
   529     repo,
   545         repo.ui, fcd, fca, fco, label=labels, localorother=localorother
   545         repo.ui, fcd, fca, fco, label=labels, localorother=localorother
   546     )
   546     )
   547     return True, r
   547     return True, r
   548 
   548 
   549 
   549 
   550 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
   550 @internaltool(b'merge-local', mergeonly, precheck=_mergecheck)
   551 def _imergelocal(*args, **kwargs):
   551 def _imergelocal(*args, **kwargs):
   552     """
   552     """
   553     Like :merge, but resolve all conflicts non-interactively in favor
   553     Like :merge, but resolve all conflicts non-interactively in favor
   554     of the local `p1()` changes."""
   554     of the local `p1()` changes."""
   555     success, status = _imergeauto(localorother='local', *args, **kwargs)
   555     success, status = _imergeauto(localorother=b'local', *args, **kwargs)
   556     return success, status, False
   556     return success, status, False
   557 
   557 
   558 
   558 
   559 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
   559 @internaltool(b'merge-other', mergeonly, precheck=_mergecheck)
   560 def _imergeother(*args, **kwargs):
   560 def _imergeother(*args, **kwargs):
   561     """
   561     """
   562     Like :merge, but resolve all conflicts non-interactively in favor
   562     Like :merge, but resolve all conflicts non-interactively in favor
   563     of the other `p2()` changes."""
   563     of the other `p2()` changes."""
   564     success, status = _imergeauto(localorother='other', *args, **kwargs)
   564     success, status = _imergeauto(localorother=b'other', *args, **kwargs)
   565     return success, status, False
   565     return success, status, False
   566 
   566 
   567 
   567 
   568 @internaltool(
   568 @internaltool(
   569     'tagmerge',
   569     b'tagmerge',
   570     mergeonly,
   570     mergeonly,
   571     _(
   571     _(
   572         "automatic tag merging of %s failed! "
   572         b"automatic tag merging of %s failed! "
   573         "(use 'hg resolve --tool :merge' or another merge "
   573         b"(use 'hg resolve --tool :merge' or another merge "
   574         "tool of your choice)\n"
   574         b"tool of your choice)\n"
   575     ),
   575     ),
   576 )
   576 )
   577 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   577 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   578     """
   578     """
   579     Uses the internal tag merge algorithm (experimental).
   579     Uses the internal tag merge algorithm (experimental).
   580     """
   580     """
   581     success, status = tagmerge.merge(repo, fcd, fco, fca)
   581     success, status = tagmerge.merge(repo, fcd, fco, fca)
   582     return success, status, False
   582     return success, status, False
   583 
   583 
   584 
   584 
   585 @internaltool('dump', fullmerge, binary=True, symlink=True)
   585 @internaltool(b'dump', fullmerge, binary=True, symlink=True)
   586 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   586 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   587     """
   587     """
   588     Creates three versions of the files to merge, containing the
   588     Creates three versions of the files to merge, containing the
   589     contents of local, other and base. These files can then be used to
   589     contents of local, other and base. These files can then be used to
   590     perform a merge manually. If the file to be merged is named
   590     perform a merge manually. If the file to be merged is named
   600 
   600 
   601     from . import context
   601     from . import context
   602 
   602 
   603     if isinstance(fcd, context.overlayworkingfilectx):
   603     if isinstance(fcd, context.overlayworkingfilectx):
   604         raise error.InMemoryMergeConflictsError(
   604         raise error.InMemoryMergeConflictsError(
   605             'in-memory merge does not ' 'support the :dump tool.'
   605             b'in-memory merge does not ' b'support the :dump tool.'
   606         )
   606         )
   607 
   607 
   608     util.writefile(a + ".local", fcd.decodeddata())
   608     util.writefile(a + b".local", fcd.decodeddata())
   609     repo.wwrite(fd + ".other", fco.data(), fco.flags())
   609     repo.wwrite(fd + b".other", fco.data(), fco.flags())
   610     repo.wwrite(fd + ".base", fca.data(), fca.flags())
   610     repo.wwrite(fd + b".base", fca.data(), fca.flags())
   611     return False, 1, False
   611     return False, 1, False
   612 
   612 
   613 
   613 
   614 @internaltool('forcedump', mergeonly, binary=True, symlink=True)
   614 @internaltool(b'forcedump', mergeonly, binary=True, symlink=True)
   615 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   615 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   616     """
   616     """
   617     Creates three versions of the files as same as :dump, but omits premerge.
   617     Creates three versions of the files as same as :dump, but omits premerge.
   618     """
   618     """
   619     return _idump(
   619     return _idump(
   629     # raises the question of what to do if the user only partially resolves the
   629     # raises the question of what to do if the user only partially resolves the
   630     # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
   630     # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
   631     # directory and tell the user how to get it is my best idea, but it's
   631     # directory and tell the user how to get it is my best idea, but it's
   632     # clunky.)
   632     # clunky.)
   633     raise error.InMemoryMergeConflictsError(
   633     raise error.InMemoryMergeConflictsError(
   634         'in-memory merge does not support ' 'external merge tools'
   634         b'in-memory merge does not support ' b'external merge tools'
   635     )
   635     )
   636 
   636 
   637 
   637 
   638 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args):
   638 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args):
   639     tmpl = ui.config('ui', 'pre-merge-tool-output-template')
   639     tmpl = ui.config(b'ui', b'pre-merge-tool-output-template')
   640     if not tmpl:
   640     if not tmpl:
   641         return
   641         return
   642 
   642 
   643     mappingdict = templateutil.mappingdict
   643     mappingdict = templateutil.mappingdict
   644     props = {
   644     props = {
   645         'ctx': fcl.changectx(),
   645         b'ctx': fcl.changectx(),
   646         'node': hex(mynode),
   646         b'node': hex(mynode),
   647         'path': fcl.path(),
   647         b'path': fcl.path(),
   648         'local': mappingdict(
   648         b'local': mappingdict(
   649             {
   649             {
   650                 'ctx': fcl.changectx(),
   650                 b'ctx': fcl.changectx(),
   651                 'fctx': fcl,
   651                 b'fctx': fcl,
   652                 'node': hex(mynode),
   652                 b'node': hex(mynode),
   653                 'name': _('local'),
   653                 b'name': _(b'local'),
   654                 'islink': 'l' in fcl.flags(),
   654                 b'islink': b'l' in fcl.flags(),
   655                 'label': env['HG_MY_LABEL'],
   655                 b'label': env[b'HG_MY_LABEL'],
   656             }
   656             }
   657         ),
   657         ),
   658         'base': mappingdict(
   658         b'base': mappingdict(
   659             {
   659             {
   660                 'ctx': fcb.changectx(),
   660                 b'ctx': fcb.changectx(),
   661                 'fctx': fcb,
   661                 b'fctx': fcb,
   662                 'name': _('base'),
   662                 b'name': _(b'base'),
   663                 'islink': 'l' in fcb.flags(),
   663                 b'islink': b'l' in fcb.flags(),
   664                 'label': env['HG_BASE_LABEL'],
   664                 b'label': env[b'HG_BASE_LABEL'],
   665             }
   665             }
   666         ),
   666         ),
   667         'other': mappingdict(
   667         b'other': mappingdict(
   668             {
   668             {
   669                 'ctx': fco.changectx(),
   669                 b'ctx': fco.changectx(),
   670                 'fctx': fco,
   670                 b'fctx': fco,
   671                 'name': _('other'),
   671                 b'name': _(b'other'),
   672                 'islink': 'l' in fco.flags(),
   672                 b'islink': b'l' in fco.flags(),
   673                 'label': env['HG_OTHER_LABEL'],
   673                 b'label': env[b'HG_OTHER_LABEL'],
   674             }
   674             }
   675         ),
   675         ),
   676         'toolpath': toolpath,
   676         b'toolpath': toolpath,
   677         'toolargs': args,
   677         b'toolargs': args,
   678     }
   678     }
   679 
   679 
   680     # TODO: make all of this something that can be specified on a per-tool basis
   680     # TODO: make all of this something that can be specified on a per-tool basis
   681     tmpl = templater.unquotestring(tmpl)
   681     tmpl = templater.unquotestring(tmpl)
   682 
   682 
   692 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   692 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
   693     tool, toolpath, binary, symlink, scriptfn = toolconf
   693     tool, toolpath, binary, symlink, scriptfn = toolconf
   694     uipathfn = scmutil.getuipathfn(repo)
   694     uipathfn = scmutil.getuipathfn(repo)
   695     if fcd.isabsent() or fco.isabsent():
   695     if fcd.isabsent() or fco.isabsent():
   696         repo.ui.warn(
   696         repo.ui.warn(
   697             _('warning: %s cannot merge change/delete conflict ' 'for %s\n')
   697             _(b'warning: %s cannot merge change/delete conflict ' b'for %s\n')
   698             % (tool, uipathfn(fcd.path()))
   698             % (tool, uipathfn(fcd.path()))
   699         )
   699         )
   700         return False, 1, None
   700         return False, 1, None
   701     unused, unused, unused, back = files
   701     unused, unused, unused, back = files
   702     localpath = _workingpath(repo, fcd)
   702     localpath = _workingpath(repo, fcd)
   703     args = _toolstr(repo.ui, tool, "args")
   703     args = _toolstr(repo.ui, tool, b"args")
   704 
   704 
   705     with _maketempfiles(
   705     with _maketempfiles(
   706         repo, fco, fca, repo.wvfs.join(back.path()), "$output" in args
   706         repo, fco, fca, repo.wvfs.join(back.path()), b"$output" in args
   707     ) as temppaths:
   707     ) as temppaths:
   708         basepath, otherpath, localoutputpath = temppaths
   708         basepath, otherpath, localoutputpath = temppaths
   709         outpath = ""
   709         outpath = b""
   710         mylabel, otherlabel = labels[:2]
   710         mylabel, otherlabel = labels[:2]
   711         if len(labels) >= 3:
   711         if len(labels) >= 3:
   712             baselabel = labels[2]
   712             baselabel = labels[2]
   713         else:
   713         else:
   714             baselabel = 'base'
   714             baselabel = b'base'
   715         env = {
   715         env = {
   716             'HG_FILE': fcd.path(),
   716             b'HG_FILE': fcd.path(),
   717             'HG_MY_NODE': short(mynode),
   717             b'HG_MY_NODE': short(mynode),
   718             'HG_OTHER_NODE': short(fco.changectx().node()),
   718             b'HG_OTHER_NODE': short(fco.changectx().node()),
   719             'HG_BASE_NODE': short(fca.changectx().node()),
   719             b'HG_BASE_NODE': short(fca.changectx().node()),
   720             'HG_MY_ISLINK': 'l' in fcd.flags(),
   720             b'HG_MY_ISLINK': b'l' in fcd.flags(),
   721             'HG_OTHER_ISLINK': 'l' in fco.flags(),
   721             b'HG_OTHER_ISLINK': b'l' in fco.flags(),
   722             'HG_BASE_ISLINK': 'l' in fca.flags(),
   722             b'HG_BASE_ISLINK': b'l' in fca.flags(),
   723             'HG_MY_LABEL': mylabel,
   723             b'HG_MY_LABEL': mylabel,
   724             'HG_OTHER_LABEL': otherlabel,
   724             b'HG_OTHER_LABEL': otherlabel,
   725             'HG_BASE_LABEL': baselabel,
   725             b'HG_BASE_LABEL': baselabel,
   726         }
   726         }
   727         ui = repo.ui
   727         ui = repo.ui
   728 
   728 
   729         if "$output" in args:
   729         if b"$output" in args:
   730             # read input from backup, write to original
   730             # read input from backup, write to original
   731             outpath = localpath
   731             outpath = localpath
   732             localpath = localoutputpath
   732             localpath = localoutputpath
   733         replace = {
   733         replace = {
   734             'local': localpath,
   734             b'local': localpath,
   735             'base': basepath,
   735             b'base': basepath,
   736             'other': otherpath,
   736             b'other': otherpath,
   737             'output': outpath,
   737             b'output': outpath,
   738             'labellocal': mylabel,
   738             b'labellocal': mylabel,
   739             'labelother': otherlabel,
   739             b'labelother': otherlabel,
   740             'labelbase': baselabel,
   740             b'labelbase': baselabel,
   741         }
   741         }
   742         args = util.interpolate(
   742         args = util.interpolate(
   743             br'\$',
   743             br'\$',
   744             replace,
   744             replace,
   745             args,
   745             args,
   746             lambda s: procutil.shellquote(util.localpath(s)),
   746             lambda s: procutil.shellquote(util.localpath(s)),
   747         )
   747         )
   748         if _toolbool(ui, tool, "gui"):
   748         if _toolbool(ui, tool, b"gui"):
   749             repo.ui.status(
   749             repo.ui.status(
   750                 _('running merge tool %s for file %s\n')
   750                 _(b'running merge tool %s for file %s\n')
   751                 % (tool, uipathfn(fcd.path()))
   751                 % (tool, uipathfn(fcd.path()))
   752             )
   752             )
   753         if scriptfn is None:
   753         if scriptfn is None:
   754             cmd = toolpath + ' ' + args
   754             cmd = toolpath + b' ' + args
   755             repo.ui.debug('launching merge tool: %s\n' % cmd)
   755             repo.ui.debug(b'launching merge tool: %s\n' % cmd)
   756             _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args)
   756             _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args)
   757             r = ui.system(
   757             r = ui.system(
   758                 cmd, cwd=repo.root, environ=env, blockedtag='mergetool'
   758                 cmd, cwd=repo.root, environ=env, blockedtag=b'mergetool'
   759             )
   759             )
   760         else:
   760         else:
   761             repo.ui.debug(
   761             repo.ui.debug(
   762                 'launching python merge script: %s:%s\n' % (toolpath, scriptfn)
   762                 b'launching python merge script: %s:%s\n' % (toolpath, scriptfn)
   763             )
   763             )
   764             r = 0
   764             r = 0
   765             try:
   765             try:
   766                 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
   766                 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
   767                 from . import extensions
   767                 from . import extensions
   768 
   768 
   769                 mod = extensions.loadpath(toolpath, 'hgmerge.%s' % tool)
   769                 mod = extensions.loadpath(toolpath, b'hgmerge.%s' % tool)
   770             except Exception:
   770             except Exception:
   771                 raise error.Abort(
   771                 raise error.Abort(
   772                     _("loading python merge script failed: %s") % toolpath
   772                     _(b"loading python merge script failed: %s") % toolpath
   773                 )
   773                 )
   774             mergefn = getattr(mod, scriptfn, None)
   774             mergefn = getattr(mod, scriptfn, None)
   775             if mergefn is None:
   775             if mergefn is None:
   776                 raise error.Abort(
   776                 raise error.Abort(
   777                     _("%s does not have function: %s") % (toolpath, scriptfn)
   777                     _(b"%s does not have function: %s") % (toolpath, scriptfn)
   778                 )
   778                 )
   779             argslist = procutil.shellsplit(args)
   779             argslist = procutil.shellsplit(args)
   780             # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
   780             # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
   781             from . import hook
   781             from . import hook
   782 
   782 
   783             ret, raised = hook.pythonhook(
   783             ret, raised = hook.pythonhook(
   784                 ui, repo, "merge", toolpath, mergefn, {'args': argslist}, True
   784                 ui, repo, b"merge", toolpath, mergefn, {b'args': argslist}, True
   785             )
   785             )
   786             if raised:
   786             if raised:
   787                 r = 1
   787                 r = 1
   788         repo.ui.debug('merge tool returned: %d\n' % r)
   788         repo.ui.debug(b'merge tool returned: %d\n' % r)
   789         return True, r, False
   789         return True, r, False
   790 
   790 
   791 
   791 
   792 def _formatconflictmarker(ctx, template, label, pad):
   792 def _formatconflictmarker(ctx, template, label, pad):
   793     """Applies the given template to the ctx, prefixed by the label.
   793     """Applies the given template to the ctx, prefixed by the label.
   796     can have aligned templated parts.
   796     can have aligned templated parts.
   797     """
   797     """
   798     if ctx.node() is None:
   798     if ctx.node() is None:
   799         ctx = ctx.p1()
   799         ctx = ctx.p1()
   800 
   800 
   801     props = {'ctx': ctx}
   801     props = {b'ctx': ctx}
   802     templateresult = template.renderdefault(props)
   802     templateresult = template.renderdefault(props)
   803 
   803 
   804     label = ('%s:' % label).ljust(pad + 1)
   804     label = (b'%s:' % label).ljust(pad + 1)
   805     mark = '%s %s' % (label, templateresult)
   805     mark = b'%s %s' % (label, templateresult)
   806 
   806 
   807     if mark:
   807     if mark:
   808         mark = mark.splitlines()[0]  # split for safety
   808         mark = mark.splitlines()[0]  # split for safety
   809 
   809 
   810     # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
   810     # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
   811     return stringutil.ellipsis(mark, 80 - 8)
   811     return stringutil.ellipsis(mark, 80 - 8)
   812 
   812 
   813 
   813 
   814 _defaultconflictlabels = ['local', 'other']
   814 _defaultconflictlabels = [b'local', b'other']
   815 
   815 
   816 
   816 
   817 def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
   817 def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
   818     """Formats the given labels using the conflict marker template.
   818     """Formats the given labels using the conflict marker template.
   819 
   819 
   822     cd = fcd.changectx()
   822     cd = fcd.changectx()
   823     co = fco.changectx()
   823     co = fco.changectx()
   824     ca = fca.changectx()
   824     ca = fca.changectx()
   825 
   825 
   826     ui = repo.ui
   826     ui = repo.ui
   827     template = ui.config('ui', 'mergemarkertemplate')
   827     template = ui.config(b'ui', b'mergemarkertemplate')
   828     if tool is not None:
   828     if tool is not None:
   829         template = _toolstr(ui, tool, 'mergemarkertemplate', template)
   829         template = _toolstr(ui, tool, b'mergemarkertemplate', template)
   830     template = templater.unquotestring(template)
   830     template = templater.unquotestring(template)
   831     tres = formatter.templateresources(ui, repo)
   831     tres = formatter.templateresources(ui, repo)
   832     tmpl = formatter.maketemplater(
   832     tmpl = formatter.maketemplater(
   833         ui, template, defaults=templatekw.keywords, resources=tres
   833         ui, template, defaults=templatekw.keywords, resources=tres
   834     )
   834     )
   849 
   849 
   850     Intended use is in strings of the form "(l)ocal%(l)s".
   850     Intended use is in strings of the form "(l)ocal%(l)s".
   851     """
   851     """
   852     if labels is None:
   852     if labels is None:
   853         return {
   853         return {
   854             "l": "",
   854             b"l": b"",
   855             "o": "",
   855             b"o": b"",
   856         }
   856         }
   857 
   857 
   858     return {
   858     return {
   859         "l": " [%s]" % labels[0],
   859         b"l": b" [%s]" % labels[0],
   860         "o": " [%s]" % labels[1],
   860         b"o": b" [%s]" % labels[1],
   861     }
   861     }
   862 
   862 
   863 
   863 
   864 def _restorebackup(fcd, back):
   864 def _restorebackup(fcd, back):
   865     # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
   865     # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
   917     """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
   917     """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
   918     copies `localpath` to another temporary file, so an external merge tool may
   918     copies `localpath` to another temporary file, so an external merge tool may
   919     use them.
   919     use them.
   920     """
   920     """
   921     tmproot = None
   921     tmproot = None
   922     tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix')
   922     tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix')
   923     if tmprootprefix:
   923     if tmprootprefix:
   924         tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
   924         tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
   925 
   925 
   926     def maketempfrompath(prefix, path):
   926     def maketempfrompath(prefix, path):
   927         fullbase, ext = os.path.splitext(path)
   927         fullbase, ext = os.path.splitext(path)
   928         pre = "%s~%s" % (os.path.basename(fullbase), prefix)
   928         pre = b"%s~%s" % (os.path.basename(fullbase), prefix)
   929         if tmproot:
   929         if tmproot:
   930             name = os.path.join(tmproot, pre)
   930             name = os.path.join(tmproot, pre)
   931             if ext:
   931             if ext:
   932                 name += ext
   932                 name += ext
   933             f = open(name, r"wb")
   933             f = open(name, r"wb")
   934         else:
   934         else:
   935             fd, name = pycompat.mkstemp(prefix=pre + '.', suffix=ext)
   935             fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext)
   936             f = os.fdopen(fd, r"wb")
   936             f = os.fdopen(fd, r"wb")
   937         return f, name
   937         return f, name
   938 
   938 
   939     def tempfromcontext(prefix, ctx):
   939     def tempfromcontext(prefix, ctx):
   940         f, name = maketempfrompath(prefix, ctx.path())
   940         f, name = maketempfrompath(prefix, ctx.path())
   941         data = repo.wwritedata(ctx.path(), ctx.data())
   941         data = repo.wwritedata(ctx.path(), ctx.data())
   942         f.write(data)
   942         f.write(data)
   943         f.close()
   943         f.close()
   944         return name
   944         return name
   945 
   945 
   946     b = tempfromcontext("base", fca)
   946     b = tempfromcontext(b"base", fca)
   947     c = tempfromcontext("other", fco)
   947     c = tempfromcontext(b"other", fco)
   948     d = localpath
   948     d = localpath
   949     if uselocalpath:
   949     if uselocalpath:
   950         # We start off with this being the backup filename, so remove the .orig
   950         # We start off with this being the backup filename, so remove the .orig
   951         # to make syntax-highlighting more likely.
   951         # to make syntax-highlighting more likely.
   952         if d.endswith('.orig'):
   952         if d.endswith(b'.orig'):
   953             d, _ = os.path.splitext(d)
   953             d, _ = os.path.splitext(d)
   954         f, d = maketempfrompath("local", d)
   954         f, d = maketempfrompath(b"local", d)
   955         with open(localpath, 'rb') as src:
   955         with open(localpath, b'rb') as src:
   956             f.write(src.read())
   956             f.write(src.read())
   957         f.close()
   957         f.close()
   958 
   958 
   959     try:
   959     try:
   960         yield b, c, d
   960         yield b, c, d
   989     ui = repo.ui
   989     ui = repo.ui
   990     fd = fcd.path()
   990     fd = fcd.path()
   991     uipathfn = scmutil.getuipathfn(repo)
   991     uipathfn = scmutil.getuipathfn(repo)
   992     fduipath = uipathfn(fd)
   992     fduipath = uipathfn(fd)
   993     binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
   993     binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
   994     symlink = 'l' in fcd.flags() + fco.flags()
   994     symlink = b'l' in fcd.flags() + fco.flags()
   995     changedelete = fcd.isabsent() or fco.isabsent()
   995     changedelete = fcd.isabsent() or fco.isabsent()
   996     tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
   996     tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
   997     scriptfn = None
   997     scriptfn = None
   998     if tool in internals and tool.startswith('internal:'):
   998     if tool in internals and tool.startswith(b'internal:'):
   999         # normalize to new-style names (':merge' etc)
   999         # normalize to new-style names (':merge' etc)
  1000         tool = tool[len('internal') :]
  1000         tool = tool[len(b'internal') :]
  1001     if toolpath and toolpath.startswith('python:'):
  1001     if toolpath and toolpath.startswith(b'python:'):
  1002         invalidsyntax = False
  1002         invalidsyntax = False
  1003         if toolpath.count(':') >= 2:
  1003         if toolpath.count(b':') >= 2:
  1004             script, scriptfn = toolpath[7:].rsplit(':', 1)
  1004             script, scriptfn = toolpath[7:].rsplit(b':', 1)
  1005             if not scriptfn:
  1005             if not scriptfn:
  1006                 invalidsyntax = True
  1006                 invalidsyntax = True
  1007             # missing :callable can lead to spliting on windows drive letter
  1007             # missing :callable can lead to spliting on windows drive letter
  1008             if '\\' in scriptfn or '/' in scriptfn:
  1008             if b'\\' in scriptfn or b'/' in scriptfn:
  1009                 invalidsyntax = True
  1009                 invalidsyntax = True
  1010         else:
  1010         else:
  1011             invalidsyntax = True
  1011             invalidsyntax = True
  1012         if invalidsyntax:
  1012         if invalidsyntax:
  1013             raise error.Abort(_("invalid 'python:' syntax: %s") % toolpath)
  1013             raise error.Abort(_(b"invalid 'python:' syntax: %s") % toolpath)
  1014         toolpath = script
  1014         toolpath = script
  1015     ui.debug(
  1015     ui.debug(
  1016         "picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
  1016         b"picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
  1017         % (
  1017         % (
  1018             tool,
  1018             tool,
  1019             fduipath,
  1019             fduipath,
  1020             pycompat.bytestr(binary),
  1020             pycompat.bytestr(binary),
  1021             pycompat.bytestr(symlink),
  1021             pycompat.bytestr(symlink),
  1033         if wctx.isinmemory():
  1033         if wctx.isinmemory():
  1034             func = _xmergeimm
  1034             func = _xmergeimm
  1035         else:
  1035         else:
  1036             func = _xmerge
  1036             func = _xmerge
  1037         mergetype = fullmerge
  1037         mergetype = fullmerge
  1038         onfailure = _("merging %s failed!\n")
  1038         onfailure = _(b"merging %s failed!\n")
  1039         precheck = None
  1039         precheck = None
  1040         isexternal = True
  1040         isexternal = True
  1041 
  1041 
  1042     toolconf = tool, toolpath, binary, symlink, scriptfn
  1042     toolconf = tool, toolpath, binary, symlink, scriptfn
  1043 
  1043 
  1046         return True, r, deleted
  1046         return True, r, deleted
  1047 
  1047 
  1048     if premerge:
  1048     if premerge:
  1049         if orig != fco.path():
  1049         if orig != fco.path():
  1050             ui.status(
  1050             ui.status(
  1051                 _("merging %s and %s to %s\n")
  1051                 _(b"merging %s and %s to %s\n")
  1052                 % (uipathfn(orig), uipathfn(fco.path()), fduipath)
  1052                 % (uipathfn(orig), uipathfn(fco.path()), fduipath)
  1053             )
  1053             )
  1054         else:
  1054         else:
  1055             ui.status(_("merging %s\n") % fduipath)
  1055             ui.status(_(b"merging %s\n") % fduipath)
  1056 
  1056 
  1057     ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
  1057     ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca))
  1058 
  1058 
  1059     if precheck and not precheck(repo, mynode, orig, fcd, fco, fca, toolconf):
  1059     if precheck and not precheck(repo, mynode, orig, fcd, fco, fca, toolconf):
  1060         if onfailure:
  1060         if onfailure:
  1061             if wctx.isinmemory():
  1061             if wctx.isinmemory():
  1062                 raise error.InMemoryMergeConflictsError(
  1062                 raise error.InMemoryMergeConflictsError(
  1063                     'in-memory merge does ' 'not support merge ' 'conflicts'
  1063                     b'in-memory merge does ' b'not support merge ' b'conflicts'
  1064                 )
  1064                 )
  1065             ui.warn(onfailure % fduipath)
  1065             ui.warn(onfailure % fduipath)
  1066         return True, 1, False
  1066         return True, 1, False
  1067 
  1067 
  1068     back = _makebackup(repo, ui, wctx, fcd, premerge)
  1068     back = _makebackup(repo, ui, wctx, fcd, premerge)
  1069     files = (None, None, None, back)
  1069     files = (None, None, None, back)
  1070     r = 1
  1070     r = 1
  1071     try:
  1071     try:
  1072         internalmarkerstyle = ui.config('ui', 'mergemarkers')
  1072         internalmarkerstyle = ui.config(b'ui', b'mergemarkers')
  1073         if isexternal:
  1073         if isexternal:
  1074             markerstyle = _toolstr(ui, tool, 'mergemarkers')
  1074             markerstyle = _toolstr(ui, tool, b'mergemarkers')
  1075         else:
  1075         else:
  1076             markerstyle = internalmarkerstyle
  1076             markerstyle = internalmarkerstyle
  1077 
  1077 
  1078         if not labels:
  1078         if not labels:
  1079             labels = _defaultconflictlabels
  1079             labels = _defaultconflictlabels
  1080         formattedlabels = labels
  1080         formattedlabels = labels
  1081         if markerstyle != 'basic':
  1081         if markerstyle != b'basic':
  1082             formattedlabels = _formatlabels(
  1082             formattedlabels = _formatlabels(
  1083                 repo, fcd, fco, fca, labels, tool=tool
  1083                 repo, fcd, fco, fca, labels, tool=tool
  1084             )
  1084             )
  1085 
  1085 
  1086         if premerge and mergetype == fullmerge:
  1086         if premerge and mergetype == fullmerge:
  1089             # setting is 'detailed'. This way tools can have basic labels in
  1089             # setting is 'detailed'. This way tools can have basic labels in
  1090             # space-constrained areas of the UI, but still get full information
  1090             # space-constrained areas of the UI, but still get full information
  1091             # in conflict markers if premerge is 'keep' or 'keep-merge3'.
  1091             # in conflict markers if premerge is 'keep' or 'keep-merge3'.
  1092             premergelabels = labels
  1092             premergelabels = labels
  1093             labeltool = None
  1093             labeltool = None
  1094             if markerstyle != 'basic':
  1094             if markerstyle != b'basic':
  1095                 # respect 'tool's mergemarkertemplate (which defaults to
  1095                 # respect 'tool's mergemarkertemplate (which defaults to
  1096                 # ui.mergemarkertemplate)
  1096                 # ui.mergemarkertemplate)
  1097                 labeltool = tool
  1097                 labeltool = tool
  1098             if internalmarkerstyle != 'basic' or markerstyle != 'basic':
  1098             if internalmarkerstyle != b'basic' or markerstyle != b'basic':
  1099                 premergelabels = _formatlabels(
  1099                 premergelabels = _formatlabels(
  1100                     repo, fcd, fco, fca, premergelabels, tool=labeltool
  1100                     repo, fcd, fco, fca, premergelabels, tool=labeltool
  1101                 )
  1101                 )
  1102 
  1102 
  1103             r = _premerge(
  1103             r = _premerge(
  1123 
  1123 
  1124         if r:
  1124         if r:
  1125             if onfailure:
  1125             if onfailure:
  1126                 if wctx.isinmemory():
  1126                 if wctx.isinmemory():
  1127                     raise error.InMemoryMergeConflictsError(
  1127                     raise error.InMemoryMergeConflictsError(
  1128                         'in-memory merge ' 'does not support ' 'merge conflicts'
  1128                         b'in-memory merge '
       
  1129                         b'does not support '
       
  1130                         b'merge conflicts'
  1129                     )
  1131                     )
  1130                 ui.warn(onfailure % fduipath)
  1132                 ui.warn(onfailure % fduipath)
  1131             _onfilemergefailure(ui)
  1133             _onfilemergefailure(ui)
  1132 
  1134 
  1133         return True, r, deleted
  1135         return True, r, deleted
  1135         if not r and back is not None:
  1137         if not r and back is not None:
  1136             back.remove()
  1138             back.remove()
  1137 
  1139 
  1138 
  1140 
  1139 def _haltmerge():
  1141 def _haltmerge():
  1140     msg = _('merge halted after failed merge (see hg resolve)')
  1142     msg = _(b'merge halted after failed merge (see hg resolve)')
  1141     raise error.InterventionRequired(msg)
  1143     raise error.InterventionRequired(msg)
  1142 
  1144 
  1143 
  1145 
  1144 def _onfilemergefailure(ui):
  1146 def _onfilemergefailure(ui):
  1145     action = ui.config('merge', 'on-failure')
  1147     action = ui.config(b'merge', b'on-failure')
  1146     if action == 'prompt':
  1148     if action == b'prompt':
  1147         msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
  1149         msg = _(b'continue merge operation (yn)?' b'$$ &Yes $$ &No')
  1148         if ui.promptchoice(msg, 0) == 1:
  1150         if ui.promptchoice(msg, 0) == 1:
  1149             _haltmerge()
  1151             _haltmerge()
  1150     if action == 'halt':
  1152     if action == b'halt':
  1151         _haltmerge()
  1153         _haltmerge()
  1152     # default action is 'continue', in which case we neither prompt nor halt
  1154     # default action is 'continue', in which case we neither prompt nor halt
  1153 
  1155 
  1154 
  1156 
  1155 def hasconflictmarkers(data):
  1157 def hasconflictmarkers(data):
  1156     return bool(
  1158     return bool(
  1157         re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", data, re.MULTILINE)
  1159         re.search(b"^(<<<<<<< .*|=======|>>>>>>> .*)$", data, re.MULTILINE)
  1158     )
  1160     )
  1159 
  1161 
  1160 
  1162 
  1161 def _check(repo, r, ui, tool, fcd, files):
  1163 def _check(repo, r, ui, tool, fcd, files):
  1162     fd = fcd.path()
  1164     fd = fcd.path()
  1163     uipathfn = scmutil.getuipathfn(repo)
  1165     uipathfn = scmutil.getuipathfn(repo)
  1164     unused, unused, unused, back = files
  1166     unused, unused, unused, back = files
  1165 
  1167 
  1166     if not r and (
  1168     if not r and (
  1167         _toolbool(ui, tool, "checkconflicts")
  1169         _toolbool(ui, tool, b"checkconflicts")
  1168         or 'conflicts' in _toollist(ui, tool, "check")
  1170         or b'conflicts' in _toollist(ui, tool, b"check")
  1169     ):
  1171     ):
  1170         if hasconflictmarkers(fcd.data()):
  1172         if hasconflictmarkers(fcd.data()):
  1171             r = 1
  1173             r = 1
  1172 
  1174 
  1173     checked = False
  1175     checked = False
  1174     if 'prompt' in _toollist(ui, tool, "check"):
  1176     if b'prompt' in _toollist(ui, tool, b"check"):
  1175         checked = True
  1177         checked = True
  1176         if ui.promptchoice(
  1178         if ui.promptchoice(
  1177             _("was merge of '%s' successful (yn)?" "$$ &Yes $$ &No")
  1179             _(b"was merge of '%s' successful (yn)?" b"$$ &Yes $$ &No")
  1178             % uipathfn(fd),
  1180             % uipathfn(fd),
  1179             1,
  1181             1,
  1180         ):
  1182         ):
  1181             r = 1
  1183             r = 1
  1182 
  1184 
  1183     if (
  1185     if (
  1184         not r
  1186         not r
  1185         and not checked
  1187         and not checked
  1186         and (
  1188         and (
  1187             _toolbool(ui, tool, "checkchanged")
  1189             _toolbool(ui, tool, b"checkchanged")
  1188             or 'changed' in _toollist(ui, tool, "check")
  1190             or b'changed' in _toollist(ui, tool, b"check")
  1189         )
  1191         )
  1190     ):
  1192     ):
  1191         if back is not None and not fcd.cmp(back):
  1193         if back is not None and not fcd.cmp(back):
  1192             if ui.promptchoice(
  1194             if ui.promptchoice(
  1193                 _(
  1195                 _(
  1194                     " output file %s appears unchanged\n"
  1196                     b" output file %s appears unchanged\n"
  1195                     "was merge successful (yn)?"
  1197                     b"was merge successful (yn)?"
  1196                     "$$ &Yes $$ &No"
  1198                     b"$$ &Yes $$ &No"
  1197                 )
  1199                 )
  1198                 % uipathfn(fd),
  1200                 % uipathfn(fd),
  1199                 1,
  1201                 1,
  1200             ):
  1202             ):
  1201                 r = 1
  1203                 r = 1
  1202 
  1204 
  1203     if back is not None and _toolbool(ui, tool, "fixeol"):
  1205     if back is not None and _toolbool(ui, tool, b"fixeol"):
  1204         _matcheol(_workingpath(repo, fcd), back)
  1206         _matcheol(_workingpath(repo, fcd), back)
  1205 
  1207 
  1206     return r
  1208     return r
  1207 
  1209 
  1208 
  1210 
  1224 
  1226 
  1225 def loadinternalmerge(ui, extname, registrarobj):
  1227 def loadinternalmerge(ui, extname, registrarobj):
  1226     """Load internal merge tool from specified registrarobj
  1228     """Load internal merge tool from specified registrarobj
  1227     """
  1229     """
  1228     for name, func in registrarobj._table.iteritems():
  1230     for name, func in registrarobj._table.iteritems():
  1229         fullname = ':' + name
  1231         fullname = b':' + name
  1230         internals[fullname] = func
  1232         internals[fullname] = func
  1231         internals['internal:' + name] = func
  1233         internals[b'internal:' + name] = func
  1232         internalsdoc[fullname] = func
  1234         internalsdoc[fullname] = func
  1233 
  1235 
  1234         capabilities = sorted([k for k, v in func.capabilities.items() if v])
  1236         capabilities = sorted([k for k, v in func.capabilities.items() if v])
  1235         if capabilities:
  1237         if capabilities:
  1236             capdesc = "    (actual capabilities: %s)" % ', '.join(capabilities)
  1238             capdesc = b"    (actual capabilities: %s)" % b', '.join(
  1237             func.__doc__ = func.__doc__ + pycompat.sysstr("\n\n%s" % capdesc)
  1239                 capabilities
       
  1240             )
       
  1241             func.__doc__ = func.__doc__ + pycompat.sysstr(b"\n\n%s" % capdesc)
  1238 
  1242 
  1239     # to put i18n comments into hg.pot for automatically generated texts
  1243     # to put i18n comments into hg.pot for automatically generated texts
  1240 
  1244 
  1241     # i18n: "binary" and "symlink" are keywords
  1245     # i18n: "binary" and "symlink" are keywords
  1242     # i18n: this text is added automatically
  1246     # i18n: this text is added automatically
  1243     _("    (actual capabilities: binary, symlink)")
  1247     _(b"    (actual capabilities: binary, symlink)")
  1244     # i18n: "binary" is keyword
  1248     # i18n: "binary" is keyword
  1245     # i18n: this text is added automatically
  1249     # i18n: this text is added automatically
  1246     _("    (actual capabilities: binary)")
  1250     _(b"    (actual capabilities: binary)")
  1247     # i18n: "symlink" is keyword
  1251     # i18n: "symlink" is keyword
  1248     # i18n: this text is added automatically
  1252     # i18n: this text is added automatically
  1249     _("    (actual capabilities: symlink)")
  1253     _(b"    (actual capabilities: symlink)")
  1250 
  1254 
  1251 
  1255 
  1252 # load built-in merge tools explicitly to setup internalsdoc
  1256 # load built-in merge tools explicitly to setup internalsdoc
  1253 loadinternalmerge(None, None, internaltool)
  1257 loadinternalmerge(None, None, internaltool)
  1254 
  1258