filemerge: support passing labels to external merge tools
This adds $labellocal, $labelother, and $labelbase to the replacement set for
merge-tools.<tool>.args config variables, and to the environment as HG_MY_LABEL,
HG_OTHER_LABEL, and HG_BASE_LABEL, respectively.
We also add merge-tools.<tool>.mergemarkers and
merge-tools.<tool>.mergemarkertemplate config variables as counterparts of
the variables available in [ui]. We are intentionally *not* respecting
ui.mergemarkers when calling out to external merge programs; too often the
default template will be too wide to display comfortably in most GUIs.
Differential Revision: https://phab.mercurial-scm.org/D2011
--- a/mercurial/configitems.py Fri Feb 02 23:20:55 2018 -0500
+++ b/mercurial/configitems.py Wed Jan 17 17:35:05 2018 -0800
@@ -743,6 +743,16 @@
generic=True,
priority=-1,
)
+coreconfigitem('merge-tools', br'.*\.mergemarkers$',
+ default='basic',
+ generic=True,
+ priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
+ default=dynamicdefault, # take from ui.mergemarkertemplate
+ generic=True,
+ priority=-1,
+)
coreconfigitem('merge-tools', br'.*\.priority$',
default=0,
generic=True,
--- a/mercurial/filemerge.py Fri Feb 02 23:20:55 2018 -0500
+++ b/mercurial/filemerge.py Wed Jan 17 17:35:05 2018 -0800
@@ -513,6 +513,11 @@
b, c = _maketempfiles(repo, fco, fca)
try:
out = ""
+ mylabel, otherlabel = labels[:2]
+ if len(labels) >= 3:
+ baselabel = labels[2]
+ else:
+ baselabel = 'base'
env = {'HG_FILE': fcd.path(),
'HG_MY_NODE': short(mynode),
'HG_OTHER_NODE': str(fco.changectx()),
@@ -520,6 +525,9 @@
'HG_MY_ISLINK': 'l' in fcd.flags(),
'HG_OTHER_ISLINK': 'l' in fco.flags(),
'HG_BASE_ISLINK': 'l' in fca.flags(),
+ 'HG_MY_LABEL': mylabel,
+ 'HG_OTHER_LABEL': otherlabel,
+ 'HG_BASE_LABEL': baselabel,
}
ui = repo.ui
@@ -528,7 +536,9 @@
# read input from backup, write to original
out = a
a = repo.wvfs.join(back.path())
- replace = {'local': a, 'base': b, 'other': c, 'output': out}
+ replace = {'local': a, 'base': b, 'other': c, 'output': out,
+ 'labellocal': mylabel, 'labelother': otherlabel,
+ 'labelbase': baselabel}
args = util.interpolate(br'\$', replace, args,
lambda s: util.shellquote(util.localpath(s)))
cmd = toolpath + ' ' + args
@@ -566,7 +576,7 @@
_defaultconflictlabels = ['local', 'other']
-def _formatlabels(repo, fcd, fco, fca, labels):
+def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
"""Formats the given labels using the conflict marker template.
Returns a list of formatted labels.
@@ -577,6 +587,8 @@
ui = repo.ui
template = ui.config('ui', 'mergemarkertemplate')
+ if tool is not None:
+ template = _toolstr(ui, tool, 'mergemarkertemplate', template)
template = templater.unquotestring(template)
tres = formatter.templateresources(ui, repo)
tmpl = formatter.maketemplater(ui, template, defaults=templatekw.keywords,
@@ -706,6 +718,7 @@
mergetype = func.mergetype
onfailure = func.onfailure
precheck = func.precheck
+ isexternal = False
else:
if wctx.isinmemory():
func = _xmergeimm
@@ -714,6 +727,7 @@
mergetype = fullmerge
onfailure = _("merging %s failed!\n")
precheck = None
+ isexternal = True
toolconf = tool, toolpath, binary, symlink
@@ -743,19 +757,42 @@
files = (None, None, None, back)
r = 1
try:
- markerstyle = ui.config('ui', 'mergemarkers')
+ internalmarkerstyle = ui.config('ui', 'mergemarkers')
+ if isexternal:
+ markerstyle = _toolstr(ui, tool, 'mergemarkers')
+ else:
+ markerstyle = internalmarkerstyle
+
if not labels:
labels = _defaultconflictlabels
+ formattedlabels = labels
if markerstyle != 'basic':
- labels = _formatlabels(repo, fcd, fco, fca, labels)
+ formattedlabels = _formatlabels(repo, fcd, fco, fca, labels,
+ tool=tool)
if premerge and mergetype == fullmerge:
- r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
+ # 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
+ # space-constrained areas of the UI, but still get full information
+ # in conflict markers if premerge is 'keep' or 'keep-merge3'.
+ premergelabels = labels
+ labeltool = None
+ if markerstyle != 'basic':
+ # respect 'tool's mergemarkertemplate (which defaults to
+ # ui.mergemarkertemplate)
+ labeltool = tool
+ if internalmarkerstyle != 'basic' or markerstyle != 'basic':
+ premergelabels = _formatlabels(repo, fcd, fco, fca,
+ premergelabels, tool=labeltool)
+
+ r = _premerge(repo, fcd, fco, fca, toolconf, files,
+ labels=premergelabels)
# complete if premerge successful (r is 0)
return not r, r, False
needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
- toolconf, files, labels=labels)
+ toolconf, files, labels=formattedlabels)
if needcheck:
r = _check(repo, r, ui, tool, fcd, files)
--- a/mercurial/help/config.txt Fri Feb 02 23:20:55 2018 -0500
+++ b/mercurial/help/config.txt Wed Jan 17 17:35:05 2018 -0800
@@ -1363,13 +1363,18 @@
``args``
The arguments to pass to the tool executable. You can refer to the
files being merged as well as the output file through these
- variables: ``$base``, ``$local``, ``$other``, ``$output``. The meaning
- of ``$local`` and ``$other`` can vary depending on which action is being
- performed. During and update or merge, ``$local`` represents the original
- state of the file, while ``$other`` represents the commit you are updating
- to or the commit you are merging with. During a rebase ``$local``
- represents the destination of the rebase, and ``$other`` represents the
- commit being rebased.
+ variables: ``$base``, ``$local``, ``$other``, ``$output``.
+
+ The meaning of ``$local`` and ``$other`` can vary depending on which action is
+ being performed. During an update or merge, ``$local`` represents the original
+ state of the file, while ``$other`` represents the commit you are updating to or
+ the commit you are merging with. During a rebase, ``$local`` represents the
+ destination of the rebase, and ``$other`` represents the commit being rebased.
+
+ Some operations define custom labels to assist with identifying the revisions,
+ accessible via ``$labellocal``, ``$labelother``, and ``$labelbase``. If custom
+ labels are not available, these will be ``local``, ``other``, and ``base``,
+ respectively.
(default: ``$local $base $other``)
``premerge``
@@ -1405,6 +1410,21 @@
``gui``
This tool requires a graphical interface to run. (default: False)
+``mergemarkers``
+ Controls whether the labels passed via ``$labellocal``, ``$labelother``, and
+ ``$labelbase`` are ``detailed`` (respecting ``mergemarkertemplate``) or
+ ``basic``. If ``premerge`` is ``keep`` or ``keep-merge3``, the conflict
+ markers generated during premerge will be ``detailed`` if either this option or
+ the corresponding option in the ``[ui]`` section is ``detailed``.
+ (default: ``basic``)
+
+``mergemarkertemplate``
+ This setting can be used to override ``mergemarkertemplate`` from the ``[ui]``
+ section on a per-tool basis; this applies to the ``$label``-prefixed variables
+ and to the conflict markers that are generated if ``premerge`` is ``keep` or
+ ``keep-merge3``. See the corresponding variable in ``[ui]`` for more
+ information.
+
.. container:: windows
``regkey``
@@ -2120,6 +2140,8 @@
markers is different from the encoding of the merged files,
serious problems may occur.
+ Can be overridden per-merge-tool, see the ``[merge-tools]`` section.
+
``origbackuppath``
The path to a directory used to store generated .orig files. If the path is
not a directory, one will be created. If set, files stored in this
--- a/tests/test-merge-tools.t Fri Feb 02 23:20:55 2018 -0500
+++ b/tests/test-merge-tools.t Wed Jan 17 17:35:05 2018 -0800
@@ -1059,6 +1059,150 @@
# hg resolve --list
R f
+premerge=keep respects ui.mergemarkers=basic:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic
+ merging f
+ <<<<<<< working copy
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev
+ revision 0
+ space
+ revision 4
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ aftermerge
+ # cat f
+ <<<<<<< working copy
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev
+ # hg stat
+ M f
+ # hg resolve --list
+ R f
+
+premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg merge -r 4 --config merge-tools.true.premerge=keep \
+ > --config ui.mergemarkers=basic \
+ > --config merge-tools.true.mergemarkers=detailed
+ merging f
+ <<<<<<< working copy: ef83787e2614 - test: revision 1
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
+ revision 0
+ space
+ revision 4
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ aftermerge
+ # cat f
+ <<<<<<< working copy: ef83787e2614 - test: revision 1
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
+ # hg stat
+ M f
+ # hg resolve --list
+ R f
+
+premerge=keep respects ui.mergemarkertemplate instead of
+true.mergemarkertemplate if true.mergemarkers=basic:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg merge -r 4 --config merge-tools.true.premerge=keep \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}'
+ merging f
+ <<<<<<< working copy: uitmpl 1
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: uitmpl 4
+ revision 0
+ space
+ revision 4
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ aftermerge
+ # cat f
+ <<<<<<< working copy: uitmpl 1
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: uitmpl 4
+ # hg stat
+ M f
+ # hg resolve --list
+ R f
+
+premerge=keep respects true.mergemarkertemplate instead of
+true.mergemarkertemplate if true.mergemarkers=detailed:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ hg merge -r 4 --config merge-tools.true.premerge=keep \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
+ > --config merge-tools.true.mergemarkers=detailed
+ merging f
+ <<<<<<< working copy: tooltmpl ef83787e2614
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: tooltmpl 81448d39c9a0
+ revision 0
+ space
+ revision 4
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ aftermerge
+ # cat f
+ <<<<<<< working copy: tooltmpl ef83787e2614
+ revision 1
+ space
+ =======
+ revision 4
+ >>>>>>> merge rev: tooltmpl 81448d39c9a0
+ # hg stat
+ M f
+ # hg resolve --list
+ R f
Tool execution
@@ -1190,6 +1334,142 @@
# hg resolve --list
R f
+Merge using a tool that supports labellocal, labelother, and labelbase, checking
+that they're quoted properly as well. This is using the default 'basic'
+mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both
+mergemarkertemplate settings:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ cat <<EOF > printargs_merge_tool
+ > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
+ > EOF
+ $ hg --config merge-tools.true.executable='sh' \
+ > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config ui.mergemarkers=detailed \
+ > merge -r 2
+ merging f
+ arg: "ll:working copy"
+ arg: "lo:"
+ arg: "merge rev"
+ arg: "lb:base: /tmp/f~base.*" (glob)
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ rm -f 'printargs_merge_tool'
+
+Merge using a tool that supports labellocal, labelother, and labelbase, checking
+that they're quoted properly as well. This is using 'detailed' mergemarkers,
+even though ui.mergemarkers is 'basic', and using the tool's
+mergemarkertemplate:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ cat <<EOF > printargs_merge_tool
+ > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
+ > EOF
+ $ hg --config merge-tools.true.executable='sh' \
+ > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
+ > --config merge-tools.true.mergemarkers=detailed \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config ui.mergemarkers=basic \
+ > merge -r 2
+ merging f
+ arg: "ll:working copy: tooltmpl ef83787e2614"
+ arg: "lo:"
+ arg: "merge rev: tooltmpl 0185f4e0cf02"
+ arg: "lb:base: /tmp/f~base.*" (glob)
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ rm -f 'printargs_merge_tool'
+
+The merge tool still gets labellocal and labelother as 'basic' even when
+premerge=keep is used and has 'detailed' markers:
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ cat <<EOF > mytool
+ > echo labellocal: \"\$1\"
+ > echo labelother: \"\$2\"
+ > echo "output (arg)": \"\$3\"
+ > echo "output (contents)":
+ > cat "\$3"
+ > EOF
+ $ hg --config merge-tools.true.executable='sh' \
+ > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
+ > --config merge-tools.true.premerge=keep \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config ui.mergemarkers=detailed \
+ > merge -r 2
+ merging f
+ labellocal: "working copy"
+ labelother: "merge rev"
+ output (arg): "$TESTTMP/f"
+ output (contents):
+ <<<<<<< working copy: uitmpl 1
+ revision 1
+ =======
+ revision 2
+ >>>>>>> merge rev: uitmpl 2
+ space
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ rm -f 'mytool'
+
+premerge=keep uses the *tool's* mergemarkertemplate if tool's
+mergemarkers=detailed; labellocal and labelother also use the tool's template
+
+ $ beforemerge
+ [merge-tools]
+ false.whatever=
+ true.priority=1
+ true.executable=cat
+ # hg update -C 1
+ $ cat <<EOF > mytool
+ > echo labellocal: \"\$1\"
+ > echo labelother: \"\$2\"
+ > echo "output (arg)": \"\$3\"
+ > echo "output (contents)":
+ > cat "\$3"
+ > EOF
+ $ hg --config merge-tools.true.executable='sh' \
+ > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
+ > --config merge-tools.true.premerge=keep \
+ > --config merge-tools.true.mergemarkers=detailed \
+ > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
+ > --config ui.mergemarkertemplate='uitmpl {rev}' \
+ > --config ui.mergemarkers=detailed \
+ > merge -r 2
+ merging f
+ labellocal: "working copy: tooltmpl ef83787e2614"
+ labelother: "merge rev: tooltmpl 0185f4e0cf02"
+ output (arg): "$TESTTMP/f"
+ output (contents):
+ <<<<<<< working copy: tooltmpl ef83787e2614
+ revision 1
+ =======
+ revision 2
+ >>>>>>> merge rev: tooltmpl 0185f4e0cf02
+ space
+ 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ rm -f 'mytool'
+
Issue3581: Merging a filename that needs to be quoted
(This test doesn't work on Windows filesystems even on Linux, so check
for Unix-like permission)