##// END OF EJS Templates
extdiff: support tools that can be run simultaneously
Ludovic Chabant -
r41724:a4cd77a4 default
parent child Browse files
Show More
@@ -59,6 +59,22 b' sections for diff tool arguments, when n'
59 [diff-tools]
59 [diff-tools]
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61
61
62 If a program has a graphical interface, it might be interesting to tell
63 Mercurial about it. It will prevent the program from being mistakenly
64 used in a terminal-only environment (such as an SSH terminal session),
65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
66 instead of one by one (if you still want to open file diffs one by one,
67 you can use the --confirm option).
68
69 Declaring that a tool has a graphical interface can be done with the
70 ``gui`` flag next to where ``diffargs`` are specified:
71
72 ::
73
74 [diff-tools]
75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
76 kdiff3.gui = true
77
62 You can use -I/-X and list of file or directory names like normal
78 You can use -I/-X and list of file or directory names like normal
63 :hg:`diff` command. The extdiff extension makes snapshots of only
79 :hg:`diff` command. The extdiff extension makes snapshots of only
64 needed files, so running the external diff program will actually be
80 needed files, so running the external diff program will actually be
@@ -71,6 +87,7 b' import os'
71 import re
87 import re
72 import shutil
88 import shutil
73 import stat
89 import stat
90 import subprocess
74
91
75 from mercurial.i18n import _
92 from mercurial.i18n import _
76 from mercurial.node import (
93 from mercurial.node import (
@@ -105,11 +122,19 b" configitem('extdiff', br'opts\\..*',"
105 generic=True,
122 generic=True,
106 )
123 )
107
124
125 configitem('extdiff', br'gui\..*',
126 generic=True,
127 )
128
108 configitem('diff-tools', br'.*\.diffargs$',
129 configitem('diff-tools', br'.*\.diffargs$',
109 default=None,
130 default=None,
110 generic=True,
131 generic=True,
111 )
132 )
112
133
134 configitem('diff-tools', br'.*\.gui$',
135 generic=True,
136 )
137
113 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
138 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
114 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
139 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
115 # be specifying the version(s) of Mercurial they are tested with, or
140 # be specifying the version(s) of Mercurial they are tested with, or
@@ -176,13 +201,26 b' def formatcmdline(cmdline, repo_root, do'
176 cmdline += ' $parent1 $child'
201 cmdline += ' $parent1 $child'
177 return re.sub(regex, quote, cmdline)
202 return re.sub(regex, quote, cmdline)
178
203
179 def _runperfilediff(cmdline, repo_root, ui, do3way, confirm,
204 def _systembackground(cmd, environ=None, cwd=None):
205 ''' like 'procutil.system', but returns the Popen object directly
206 so we don't have to wait on it.
207 '''
208 cmd = procutil.quotecommand(cmd)
209 env = procutil.shellenviron(environ)
210 proc = subprocess.Popen(procutil.tonativestr(cmd),
211 shell=True, close_fds=procutil.closefds,
212 env=procutil.tonativeenv(env),
213 cwd=pycompat.rapply(procutil.tonativestr, cwd))
214 return proc
215
216 def _runperfilediff(cmdline, repo_root, ui, guitool, do3way, confirm,
180 commonfiles, tmproot, dir1a, dir1b,
217 commonfiles, tmproot, dir1a, dir1b,
181 dir2root, dir2,
218 dir2root, dir2,
182 rev1a, rev1b, rev2):
219 rev1a, rev1b, rev2):
183 # Note that we need to sort the list of files because it was
220 # Note that we need to sort the list of files because it was
184 # built in an "unstable" way and it's annoying to get files in a
221 # built in an "unstable" way and it's annoying to get files in a
185 # random order, especially when "confirm" mode is enabled.
222 # random order, especially when "confirm" mode is enabled.
223 waitprocs = []
186 totalfiles = len(commonfiles)
224 totalfiles = len(commonfiles)
187 for idx, commonfile in enumerate(sorted(commonfiles)):
225 for idx, commonfile in enumerate(sorted(commonfiles)):
188 path1a = os.path.join(tmproot, dir1a, commonfile)
226 path1a = os.path.join(tmproot, dir1a, commonfile)
@@ -228,14 +266,32 b' def _runperfilediff(cmdline, repo_root, '
228 parent1=path1a, plabel1=label1a,
266 parent1=path1a, plabel1=label1a,
229 parent2=path1b, plabel2=label1b,
267 parent2=path1b, plabel2=label1b,
230 child=path2, clabel=label2)
268 child=path2, clabel=label2)
231 ui.debug('running %r in %s\n' % (pycompat.bytestr(curcmdline),
232 tmproot))
233
269
234 # Run the comparison program and wait for it to exit
270 if confirm or not guitool:
235 # before we show the next file.
271 # Run the comparison program and wait for it to exit
236 ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff')
272 # before we show the next file.
273 # This is because either we need to wait for confirmation
274 # from the user between each invocation, or because, as far
275 # as we know, the tool doesn't have a GUI, in which case
276 # we can't run multiple CLI programs at the same time.
277 ui.debug('running %r in %s\n' %
278 (pycompat.bytestr(curcmdline), tmproot))
279 ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff')
280 else:
281 # Run the comparison program but don't wait, as we're
282 # going to rapid-fire each file diff and then wait on
283 # the whole group.
284 ui.debug('running %r in %s (backgrounded)\n' %
285 (pycompat.bytestr(curcmdline), tmproot))
286 proc = _systembackground(curcmdline, cwd=tmproot)
287 waitprocs.append(proc)
237
288
238 def dodiff(ui, repo, cmdline, pats, opts):
289 if waitprocs:
290 with ui.timeblockedsection('extdiff'):
291 for proc in waitprocs:
292 proc.wait()
293
294 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
239 '''Do the actual diff:
295 '''Do the actual diff:
240
296
241 - copy to a temp structure if diffing 2 internal revisions
297 - copy to a temp structure if diffing 2 internal revisions
@@ -382,7 +438,8 b' def dodiff(ui, repo, cmdline, pats, opts'
382 else:
438 else:
383 # Run the external tool once for each pair of files
439 # Run the external tool once for each pair of files
384 _runperfilediff(
440 _runperfilediff(
385 cmdline, repo.root, ui, do3way=do3way, confirm=confirm,
441 cmdline, repo.root, ui, guitool=guitool,
442 do3way=do3way, confirm=confirm,
386 commonfiles=common, tmproot=tmproot, dir1a=dir1a, dir1b=dir1b,
443 commonfiles=common, tmproot=tmproot, dir1a=dir1a, dir1b=dir1b,
387 dir2root=dir2root, dir2=dir2,
444 dir2root=dir2root, dir2=dir2,
388 rev1a=rev1a, rev1b=rev1b, rev2=rev2)
445 rev1a=rev1a, rev1b=rev1b, rev2=rev2)
@@ -446,7 +503,13 b' def extdiff(ui, repo, *pats, **opts):'
446 to its parent.
503 to its parent.
447
504
448 The --per-file option runs the external program repeatedly on each
505 The --per-file option runs the external program repeatedly on each
449 file to diff, instead of once on two directories.
506 file to diff, instead of once on two directories. By default,
507 this happens one by one, where the next file diff is open in the
508 external program only once the previous external program (for the
509 previous file diff) has exited. If the external program has a
510 graphical interface, it can open all the file diffs at once instead
511 of one by one. See :hg:`help -e extdiff` for information about how
512 to tell Mercurial that a given program has a graphical interface.
450
513
451 The --confirm option will prompt the user before each invocation of
514 The --confirm option will prompt the user before each invocation of
452 the external program. It is ignored if --per-file isn't specified.
515 the external program. It is ignored if --per-file isn't specified.
@@ -475,20 +538,22 b' class savedcmd(object):'
475 to its parent.
538 to its parent.
476 """
539 """
477
540
478 def __init__(self, path, cmdline):
541 def __init__(self, path, cmdline, isgui):
479 # We can't pass non-ASCII through docstrings (and path is
542 # We can't pass non-ASCII through docstrings (and path is
480 # in an unknown encoding anyway), but avoid double separators on
543 # in an unknown encoding anyway), but avoid double separators on
481 # Windows
544 # Windows
482 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
545 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
483 self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
546 self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
484 self._cmdline = cmdline
547 self._cmdline = cmdline
548 self._isgui = isgui
485
549
486 def __call__(self, ui, repo, *pats, **opts):
550 def __call__(self, ui, repo, *pats, **opts):
487 opts = pycompat.byteskwargs(opts)
551 opts = pycompat.byteskwargs(opts)
488 options = ' '.join(map(procutil.shellquote, opts['option']))
552 options = ' '.join(map(procutil.shellquote, opts['option']))
489 if options:
553 if options:
490 options = ' ' + options
554 options = ' ' + options
491 return dodiff(ui, repo, self._cmdline + options, pats, opts)
555 return dodiff(ui, repo, self._cmdline + options, pats, opts,
556 guitool=self._isgui)
492
557
493 def uisetup(ui):
558 def uisetup(ui):
494 for cmd, path in ui.configitems('extdiff'):
559 for cmd, path in ui.configitems('extdiff'):
@@ -503,7 +568,8 b' def uisetup(ui):'
503 cmdline = procutil.shellquote(path)
568 cmdline = procutil.shellquote(path)
504 if diffopts:
569 if diffopts:
505 cmdline += ' ' + diffopts
570 cmdline += ' ' + diffopts
506 elif cmd.startswith('opts.'):
571 isgui = ui.configbool('extdiff', 'gui.' + cmd)
572 elif cmd.startswith('opts.') or cmd.startswith('gui.'):
507 continue
573 continue
508 else:
574 else:
509 if path:
575 if path:
@@ -517,15 +583,20 b' def uisetup(ui):'
517 path = filemerge.findexternaltool(ui, cmd) or cmd
583 path = filemerge.findexternaltool(ui, cmd) or cmd
518 cmdline = procutil.shellquote(path)
584 cmdline = procutil.shellquote(path)
519 diffopts = False
585 diffopts = False
586 isgui = ui.configbool('extdiff', 'gui.' + cmd)
520 # look for diff arguments in [diff-tools] then [merge-tools]
587 # look for diff arguments in [diff-tools] then [merge-tools]
521 if not diffopts:
588 if not diffopts:
522 args = ui.config('diff-tools', cmd+'.diffargs') or \
589 key = cmd + '.diffargs'
523 ui.config('merge-tools', cmd+'.diffargs')
590 for section in ('diff-tools', 'merge-tools'):
524 if args:
591 args = ui.config(section, key)
525 cmdline += ' ' + args
592 if args:
593 cmdline += ' ' + args
594 if isgui is None:
595 isgui = ui.configbool(section, cmd + '.gui') or False
596 break
526 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
597 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
527 helpcategory=command.CATEGORY_FILE_CONTENTS,
598 helpcategory=command.CATEGORY_FILE_CONTENTS,
528 inferrepo=True)(savedcmd(path, cmdline))
599 inferrepo=True)(savedcmd(path, cmdline, isgui))
529
600
530 # tell hggettext to extract docstrings from these functions:
601 # tell hggettext to extract docstrings from these functions:
531 i18nfunctions = [savedcmd]
602 i18nfunctions = [savedcmd]
@@ -22,6 +22,10 b' Should diff cloned directories:'
22 > opts.falabala = diffing
22 > opts.falabala = diffing
23 > cmd.edspace = echo
23 > cmd.edspace = echo
24 > opts.edspace = "name <user@example.com>"
24 > opts.edspace = "name <user@example.com>"
25 > alabalaf =
26 > [merge-tools]
27 > alabalaf.executable = echo
28 > alabalaf.diffargs = diffing
25 > EOF
29 > EOF
26
30
27 $ hg falabala
31 $ hg falabala
@@ -144,6 +148,42 b' Test --per-file option:'
144 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
148 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
145 [1]
149 [1]
146
150
151 Test --per-file option for gui tool:
152
153 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
154 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
155 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
156 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
157 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
158 making snapshot of 2 files from rev 46c0e4daeb72
159 a
160 b
161 making snapshot of 2 files from rev 81906f2b98ac
162 a
163 b
164 running '* diffing * *' in * (backgrounded) (glob)
165 running '* diffing * *' in * (backgrounded) (glob)
166 cleaning up temp directory
167 [1]
168
169 Test --per-file option for gui tool again:
170
171 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
172 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
173 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
174 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
175 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
176 making snapshot of 2 files from rev 46c0e4daeb72
177 a
178 b
179 making snapshot of 2 files from rev 81906f2b98ac
180 a
181 b
182 running '* diffing * *' in * (backgrounded) (glob)
183 running '* diffing * *' in * (backgrounded) (glob)
184 cleaning up temp directory
185 [1]
186
147 Test --per-file and --confirm options:
187 Test --per-file and --confirm options:
148
188
149 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
189 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
@@ -823,7 +823,13 b' Extension module help vs command help:'
823 the working directory files are compared to its parent.
823 the working directory files are compared to its parent.
824
824
825 The --per-file option runs the external program repeatedly on each file to
825 The --per-file option runs the external program repeatedly on each file to
826 diff, instead of once on two directories.
826 diff, instead of once on two directories. By default, this happens one by
827 one, where the next file diff is open in the external program only once
828 the previous external program (for the previous file diff) has exited. If
829 the external program has a graphical interface, it can open all the file
830 diffs at once instead of one by one. See 'hg help -e extdiff' for
831 information about how to tell Mercurial that a given program has a
832 graphical interface.
827
833
828 The --confirm option will prompt the user before each invocation of the
834 The --confirm option will prompt the user before each invocation of the
829 external program. It is ignored if --per-file isn't specified.
835 external program. It is ignored if --per-file isn't specified.
@@ -905,6 +911,20 b' Extension module help vs command help:'
905 [diff-tools]
911 [diff-tools]
906 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
912 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
907
913
914 If a program has a graphical interface, it might be interesting to tell
915 Mercurial about it. It will prevent the program from being mistakenly used in
916 a terminal-only environment (such as an SSH terminal session), and will make
917 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
918 (if you still want to open file diffs one by one, you can use the --confirm
919 option).
920
921 Declaring that a tool has a graphical interface can be done with the "gui"
922 flag next to where "diffargs" are specified:
923
924 [diff-tools]
925 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
926 kdiff3.gui = true
927
908 You can use -I/-X and list of file or directory names like normal 'hg diff'
928 You can use -I/-X and list of file or directory names like normal 'hg diff'
909 command. The extdiff extension makes snapshots of only needed files, so
929 command. The extdiff extension makes snapshots of only needed files, so
910 running the external diff program will actually be pretty fast (at least
930 running the external diff program will actually be pretty fast (at least
General Comments 0
You need to be logged in to leave comments. Login now