Mercurial > hg
changeset 35907:9037c29e9f53
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
author | Kyle Lippincott <spectral@google.com> |
---|---|
date | Wed, 17 Jan 2018 17:35:05 -0800 |
parents | cd2342302928 |
children | a9802c9ecfb5 |
files | mercurial/configitems.py mercurial/filemerge.py mercurial/help/config.txt tests/test-merge-tools.t |
diffstat | 4 files changed, 362 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- 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)