--- a/mercurial/filemerge.py Mon Mar 21 10:55:50 2022 +0100
+++ b/mercurial/filemerge.py Tue Jan 18 13:05:21 2022 -0800
@@ -1051,6 +1051,7 @@
markerstyle = internalmarkerstyle
if mergetype == fullmerge:
+ _run_partial_resolution_tools(repo, local, other, base)
# conflict markers generated by premerge will use 'detailed'
# settings if either ui.mergemarkers or the tool's mergemarkers
# setting is 'detailed'. This way tools can have basic labels in
@@ -1115,6 +1116,62 @@
backup.remove()
+def _run_partial_resolution_tools(repo, local, other, base):
+ """Runs partial-resolution tools on the three inputs and updates them."""
+ ui = repo.ui
+ # Tuples of (order, name, executable path)
+ tools = []
+ seen = set()
+ section = b"partial-merge-tools"
+ for k, v in ui.configitems(section):
+ name = k.split(b'.')[0]
+ if name in seen:
+ continue
+ patterns = ui.configlist(section, b'%s.patterns' % name, [])
+ is_match = True
+ if patterns:
+ m = match.match(repo.root, b'', patterns)
+ is_match = m(local.fctx.path())
+ if is_match:
+ order = ui.configint(section, b'%s.order' % name, 0)
+ executable = ui.config(section, b'%s.executable' % name, name)
+ tools.append((order, name, executable))
+
+ if not tools:
+ return
+ # Sort in configured order (first in tuple)
+ tools.sort()
+
+ files = [
+ (b"local", local.fctx.path(), local.text()),
+ (b"base", base.fctx.path(), base.text()),
+ (b"other", other.fctx.path(), other.text()),
+ ]
+
+ with _maketempfiles(files) as temppaths:
+ localpath, basepath, otherpath = temppaths
+
+ for order, name, executable in tools:
+ cmd = procutil.shellquote(executable)
+ # TODO: Allow the user to configure the command line using
+ # $local, $base, $other.
+ cmd = b'%s %s %s %s' % (cmd, localpath, basepath, otherpath)
+ r = ui.system(cmd, cwd=repo.root, blockedtag=b'partial-mergetool')
+ if r:
+ raise error.StateError(
+ b'partial merge tool %s exited with code %d' % (name, r)
+ )
+ local_text = util.readfile(localpath)
+ other_text = util.readfile(otherpath)
+ if local_text == other_text:
+ # No need to run other tools if all conflicts have been resolved
+ break
+
+ local.set_text(local_text)
+ base.set_text(util.readfile(basepath))
+ other.set_text(other_text)
+
+
def _haltmerge():
msg = _(b'merge halted after failed merge (see hg resolve)')
raise error.InterventionRequired(msg)