--- a/mercurial/filemerge.py Sun May 13 11:09:53 2018 +0900
+++ b/mercurial/filemerge.py Wed May 16 14:11:41 2018 -0600
@@ -114,8 +114,16 @@
def _findtool(ui, tool):
if tool in internals:
return tool
+ cmd = _toolstr(ui, tool, "executable", tool)
+ if cmd.startswith('python:'):
+ return cmd
return findexternaltool(ui, tool)
+def _quotetoolpath(cmd):
+ if cmd.startswith('python:'):
+ return cmd
+ return procutil.shellquote(cmd)
+
def findexternaltool(ui, tool):
for kn in ("regkey", "regkeyalt"):
k = _toolstr(ui, tool, kn)
@@ -165,7 +173,7 @@
return ":prompt", None
else:
if toolpath:
- return (force, procutil.shellquote(toolpath))
+ return (force, _quotetoolpath(toolpath))
else:
# mimic HGMERGE if given tool not found
return (force, force)
@@ -183,7 +191,7 @@
mf = match.match(repo.root, '', [pat])
if mf(path) and check(tool, pat, symlink, False, changedelete):
toolpath = _findtool(ui, tool)
- return (tool, procutil.shellquote(toolpath))
+ return (tool, _quotetoolpath(toolpath))
# then merge tools
tools = {}
@@ -208,7 +216,7 @@
for p, t in tools:
if check(t, None, symlink, binary, changedelete):
toolpath = _findtool(ui, t)
- return (t, procutil.shellquote(toolpath))
+ return (t, _quotetoolpath(toolpath))
# internal merge or prompt as last resort
if symlink or binary or changedelete:
@@ -325,7 +333,7 @@
return filectx
def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
- tool, toolpath, binary, symlink = toolconf
+ tool, toolpath, binary, symlink, scriptfn = toolconf
if symlink or fcd.isabsent() or fco.isabsent():
return 1
unused, unused, unused, back = files
@@ -361,7 +369,7 @@
return 1 # continue merging
def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
- tool, toolpath, binary, symlink = toolconf
+ tool, toolpath, binary, symlink, scriptfn = toolconf
if symlink:
repo.ui.warn(_('warning: internal %s cannot merge symlinks '
'for %s\n') % (tool, fcd.path()))
@@ -430,7 +438,7 @@
Generic driver for _imergelocal and _imergeother
"""
assert localorother is not None
- tool, toolpath, binary, symlink = toolconf
+ tool, toolpath, binary, symlink, scriptfn = toolconf
r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
localorother=localorother)
return True, r
@@ -510,7 +518,7 @@
'external merge tools')
def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
- tool, toolpath, binary, symlink = toolconf
+ tool, toolpath, binary, symlink, scriptfn = toolconf
if fcd.isabsent() or fco.isabsent():
repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
'for %s\n') % (tool, fcd.path()))
@@ -551,12 +559,36 @@
args = util.interpolate(
br'\$', replace, args,
lambda s: procutil.shellquote(util.localpath(s)))
- cmd = toolpath + ' ' + args
if _toolbool(ui, tool, "gui"):
repo.ui.status(_('running merge tool %s for file %s\n') %
(tool, fcd.path()))
- repo.ui.debug('launching merge tool: %s\n' % cmd)
- r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
+ if scriptfn is None:
+ cmd = toolpath + ' ' + args
+ repo.ui.debug('launching merge tool: %s\n' % cmd)
+ r = ui.system(cmd, cwd=repo.root, environ=env,
+ blockedtag='mergetool')
+ else:
+ repo.ui.debug('launching python merge script: %s:%s\n' %
+ (toolpath, scriptfn))
+ r = 0
+ try:
+ # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
+ from . import extensions
+ mod = extensions.loadpath(toolpath, 'hgmerge.%s' % scriptfn)
+ except Exception:
+ raise error.Abort(_("loading python merge script failed: %s") %
+ toolpath)
+ mergefn = getattr(mod, scriptfn, None)
+ if mergefn is None:
+ raise error.Abort(_("%s does not have function: %s") %
+ (toolpath, scriptfn))
+ argslist = procutil.shellsplit(args)
+ # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
+ from . import hook
+ ret, raised = hook.pythonhook(ui, repo, "merge", toolpath,
+ mergefn, {'args': argslist}, True)
+ if raised:
+ r = 1
repo.ui.debug('merge tool returned: %d\n' % r)
return True, r, False
@@ -751,9 +783,24 @@
symlink = 'l' in fcd.flags() + fco.flags()
changedelete = fcd.isabsent() or fco.isabsent()
tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
+ scriptfn = None
if tool in internals and tool.startswith('internal:'):
# normalize to new-style names (':merge' etc)
tool = tool[len('internal'):]
+ if toolpath and toolpath.startswith('python:'):
+ invalidsyntax = False
+ if toolpath.count(':') >= 2:
+ script, scriptfn = toolpath[7:].rsplit(':', 1)
+ if not scriptfn:
+ invalidsyntax = True
+ # missing :callable can lead to spliting on windows drive letter
+ if '\\' in scriptfn or '/' in scriptfn:
+ invalidsyntax = True
+ else:
+ invalidsyntax = True
+ if invalidsyntax:
+ raise error.Abort(_("invalid 'python:' syntax: %s") % toolpath)
+ toolpath = script
ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
% (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
pycompat.bytestr(changedelete)))
@@ -774,7 +821,7 @@
precheck = None
isexternal = True
- toolconf = tool, toolpath, binary, symlink
+ toolconf = tool, toolpath, binary, symlink, scriptfn
if mergetype == nomerge:
r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
--- a/mercurial/hook.py Sun May 13 11:09:53 2018 +0900
+++ b/mercurial/hook.py Wed May 16 14:11:41 2018 -0600
@@ -24,7 +24,7 @@
stringutil,
)
-def _pythonhook(ui, repo, htype, hname, funcname, args, throw):
+def pythonhook(ui, repo, htype, hname, funcname, args, throw):
'''call python hook. hook is callable object, looked up as
name in python module. if callable returns "true", hook
fails, else passes. if hook raises exception, treated as
@@ -242,7 +242,7 @@
r = 1
raised = False
elif callable(cmd):
- r, raised = _pythonhook(ui, repo, htype, hname, cmd, args,
+ r, raised = pythonhook(ui, repo, htype, hname, cmd, args,
throw)
elif cmd.startswith('python:'):
if cmd.count(':') >= 2:
@@ -258,7 +258,7 @@
hookfn = getattr(mod, cmd)
else:
hookfn = cmd[7:].strip()
- r, raised = _pythonhook(ui, repo, htype, hname, hookfn, args,
+ r, raised = pythonhook(ui, repo, htype, hname, hookfn, args,
throw)
else:
r = _exthook(ui, repo, htype, hname, cmd, args, throw)