[PATCH V2] extdiff: support tools that can be run simultaneously

Ludovic Chabant ludovic at chabant.com
Tue Feb 5 02:37:12 EST 2019


# HG changeset patch
# User Ludovic Chabant <ludovic at chabant.com>
# Date 1549173529 28800
#      Sat Feb 02 21:58:49 2019 -0800
# Node ID b8e97fbea8490173387735e72cc424c21d7a1c04
# Parent  3a3b053d0882a33ba7ea667052e445b193ffa4df
extdiff: support tools that can be run simultaneously

diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -59,6 +59,22 @@
   [diff-tools]
   kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
 
+If a program has a graphical interface, it might be interesting to tell
+Mercurial about it. It will prevent the program from being mistakenly
+used in a terminal-only environment (such as an SSH terminal session),
+and will make :hg:`extdiff --per-file` open multiple file diffs at once
+instead of one by one (if you still want to open file diffs one by one,
+you can use the --confirm option).
+
+Declaring that a tool has a graphical interface can be done with the
+``gui`` flag next to where ``diffargs`` are specified:
+
+::
+
+  [diff-tools]
+  kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
+  kdiff3.gui = true
+
 You can use -I/-X and list of file or directory names like normal
 :hg:`diff` command. The extdiff extension makes snapshots of only
 needed files, so running the external diff program will actually be
@@ -71,6 +87,7 @@
 import re
 import shutil
 import stat
+import subprocess
 
 from mercurial.i18n import _
 from mercurial.node import (
@@ -105,11 +122,19 @@
     generic=True,
 )
 
+configitem('extdiff', br'gui\..*',
+    generic=True,
+)
+
 configitem('diff-tools', br'.*\.diffargs$',
     default=None,
     generic=True,
 )
 
+configitem('diff-tools', br'.*\.gui$',
+    generic=True,
+)
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -176,13 +201,26 @@
         cmdline += ' $parent1 $child'
     return re.sub(regex, quote, cmdline)
 
-def _runperfilediff(cmdline, repo_root, ui, do3way, confirm,
+def _systembackground(cmd, environ=None, cwd=None):
+    ''' like 'procutil.system', but returns the Popen object directly
+        so we don't have to wait on it.
+    '''
+    cmd = procutil.quotecommand(cmd)
+    env = procutil.shellenviron(environ)
+    proc = subprocess.Popen(procutil.tonativestr(cmd),
+                            shell=True, close_fds=procutil.closefds,
+                            env=procutil.tonativeenv(env),
+                            cwd=pycompat.rapply(procutil.tonativestr, cwd))
+    return proc
+
+def _runperfilediff(cmdline, repo_root, ui, guitool, do3way, confirm,
                     commonfiles, tmproot, dir1a, dir1b,
                     dir2root, dir2,
                     rev1a, rev1b, rev2):
     # Note that we need to sort the list of files because it was
     # built in an "unstable" way and it's annoying to get files in a
     # random order, especially when "confirm" mode is enabled.
+    waitprocs = []
     totalfiles = len(commonfiles)
     for idx, commonfile in enumerate(sorted(commonfiles)):
         path1a = os.path.join(tmproot, dir1a, commonfile)
@@ -228,14 +266,32 @@
             parent1=path1a, plabel1=label1a,
             parent2=path1b, plabel2=label1b,
             child=path2, clabel=label2)
-        ui.debug('running %r in %s\n' % (pycompat.bytestr(curcmdline),
-                                         tmproot))
 
-        # Run the comparison program and wait for it to exit
-        # before we show the next file.
-        ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff')
+        if confirm or not guitool:
+            # Run the comparison program and wait for it to exit
+            # before we show the next file.
+            # This is because either we need to wait for confirmation
+            # from the user between each invocation, or because, as far
+            # as we know, the tool doesn't have a GUI, in which case
+            # we can't run multiple CLI programs at the same time.
+            ui.debug('running %r in %s\n' %
+                     (pycompat.bytestr(curcmdline), tmproot))
+            ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff')
+        else:
+            # Run the comparison program but don't wait, as we're
+            # going to rapid-fire each file diff and then wait on
+            # the whole group.
+            ui.debug('running %r in %s (backgrounded)\n' %
+                     (pycompat.bytestr(curcmdline), tmproot))
+            proc = _systembackground(curcmdline, cwd=tmproot)
+            waitprocs.append(proc)
 
-def dodiff(ui, repo, cmdline, pats, opts):
+    if waitprocs:
+        with ui.timeblockedsection('extdiff'):
+            for proc in waitprocs:
+                proc.wait()
+
+def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
     '''Do the actual diff:
 
     - copy to a temp structure if diffing 2 internal revisions
@@ -382,7 +438,8 @@
         else:
             # Run the external tool once for each pair of files
             _runperfilediff(
-                cmdline, repo.root, ui, do3way=do3way, confirm=confirm,
+                cmdline, repo.root, ui, guitool=guitool,
+                do3way=do3way, confirm=confirm,
                 commonfiles=common, tmproot=tmproot, dir1a=dir1a, dir1b=dir1b,
                 dir2root=dir2root, dir2=dir2,
                 rev1a=rev1a, rev1b=rev1b, rev2=rev2)
@@ -446,7 +503,13 @@
     to its parent.
 
     The --per-file option runs the external program repeatedly on each
-    file to diff, instead of once on two directories.
+    file to diff, instead of once on two directories. By default,
+    this happens one by one, where the next file diff is open in the
+    external program only once the previous external program (for the
+    previous file diff) has exited. If the external program has a
+    graphical interface, it can open all the file diffs at once instead
+    of one by one. See :hg:`help -e extdiff` for information about how
+    to tell Mercurial that a given program has a graphical interface.
 
     The --confirm option will prompt the user before each invocation of
     the external program. It is ignored if --per-file isn't specified.
@@ -475,20 +538,22 @@
     to its parent.
     """
 
-    def __init__(self, path, cmdline):
+    def __init__(self, path, cmdline, isgui):
         # We can't pass non-ASCII through docstrings (and path is
         # in an unknown encoding anyway), but avoid double separators on
         # Windows
         docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
         self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
         self._cmdline = cmdline
+        self._isgui = isgui
 
     def __call__(self, ui, repo, *pats, **opts):
         opts = pycompat.byteskwargs(opts)
         options = ' '.join(map(procutil.shellquote, opts['option']))
         if options:
             options = ' ' + options
-        return dodiff(ui, repo, self._cmdline + options, pats, opts)
+        return dodiff(ui, repo, self._cmdline + options, pats, opts,
+                      guitool=self._isgui)
 
 def uisetup(ui):
     for cmd, path in ui.configitems('extdiff'):
@@ -503,7 +568,8 @@
             cmdline = procutil.shellquote(path)
             if diffopts:
                 cmdline += ' ' + diffopts
-        elif cmd.startswith('opts.'):
+            isgui = ui.configbool('extdiff', 'gui.' + cmd)
+        elif cmd.startswith('opts.') or cmd.startswith('gui.'):
             continue
         else:
             if path:
@@ -517,15 +583,20 @@
                     path = filemerge.findexternaltool(ui, cmd) or cmd
                 cmdline = procutil.shellquote(path)
                 diffopts = False
+            isgui = ui.configbool('extdiff', 'gui.' + cmd)
         # look for diff arguments in [diff-tools] then [merge-tools]
         if not diffopts:
-            args = ui.config('diff-tools', cmd+'.diffargs') or \
-                   ui.config('merge-tools', cmd+'.diffargs')
-            if args:
-                cmdline += ' ' + args
+            key = cmd+'.diffargs'
+            for section in ('diff-tools', 'merge-tools'):
+                args = ui.config(section, key)
+                if args:
+                    cmdline += ' ' + args
+                    if isgui is None:
+                        isgui = ui.configbool(section, cmd+'.gui') or False
+                    break
         command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
                 helpcategory=command.CATEGORY_FILE_CONTENTS,
-                inferrepo=True)(savedcmd(path, cmdline))
+                inferrepo=True)(savedcmd(path, cmdline, isgui))
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = [savedcmd]
diff --git a/tests/test-extdiff.t b/tests/test-extdiff.t
--- a/tests/test-extdiff.t
+++ b/tests/test-extdiff.t
@@ -22,6 +22,10 @@
   > opts.falabala = diffing
   > cmd.edspace = echo
   > opts.edspace = "name  <user at example.com>"
+  > alabalaf =
+  > [merge-tools]
+  > alabalaf.executable = echo
+  > alabalaf.diffargs = diffing
   > EOF
 
   $ hg falabala
@@ -144,6 +148,42 @@
   diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
   [1]
 
+Test --per-file option for gui tool:
+
+  $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
+  diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
+  diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
+  diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
+  diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
+  making snapshot of 2 files from rev 46c0e4daeb72
+    a
+    b
+  making snapshot of 2 files from rev 81906f2b98ac
+    a
+    b
+  running '* diffing * *' in * (backgrounded) (glob)
+  running '* diffing * *' in * (backgrounded) (glob)
+  cleaning up temp directory
+  [1]
+
+Test --per-file option for gui tool again:
+
+  $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
+  diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
+  diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
+  diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
+  diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
+  making snapshot of 2 files from rev 46c0e4daeb72
+    a
+    b
+  making snapshot of 2 files from rev 81906f2b98ac
+    a
+    b
+  running '* diffing * *' in * (backgrounded) (glob)
+  running '* diffing * *' in * (backgrounded) (glob)
+  cleaning up temp directory
+  [1]
+
 Test --per-file and --confirm options:
 
   $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF


More information about the Mercurial-devel mailing list