57 kdiff3 = |
57 kdiff3 = |
58 |
58 |
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 |
65 pretty fast (at least faster than having to compare the entire tree). |
81 pretty fast (at least faster than having to compare the entire tree). |
66 ''' |
82 ''' |
103 configitem('extdiff', br'opts\..*', |
120 configitem('extdiff', br'opts\..*', |
104 default='', |
121 default='', |
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, |
|
131 generic=True, |
|
132 ) |
|
133 |
|
134 configitem('diff-tools', br'.*\.gui$', |
110 generic=True, |
135 generic=True, |
111 ) |
136 ) |
112 |
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 |
174 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') |
199 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') |
175 if not do3way and not re.search(regex, cmdline): |
200 if not do3way and not re.search(regex, cmdline): |
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) |
189 label1a = commonfile + rev1a |
227 label1a = commonfile + rev1a |
190 if not os.path.isfile(path1a): |
228 if not os.path.isfile(path1a): |
226 curcmdline = formatcmdline( |
264 curcmdline = formatcmdline( |
227 cmdline, repo_root, do3way=do3way, |
265 cmdline, repo_root, do3way=do3way, |
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), |
269 |
232 tmproot)) |
270 if confirm or not guitool: |
233 |
271 # Run the comparison program and wait for it to exit |
234 # Run the comparison program and wait for it to exit |
272 # before we show the next file. |
235 # before we show the next file. |
273 # This is because either we need to wait for confirmation |
236 ui.system(curcmdline, cwd=tmproot, blockedtag='extdiff') |
274 # from the user between each invocation, or because, as far |
237 |
275 # as we know, the tool doesn't have a GUI, in which case |
238 def dodiff(ui, repo, cmdline, pats, opts): |
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) |
|
288 |
|
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 |
242 - copy to a temp structure if diffing working revision with |
298 - copy to a temp structure if diffing working revision with |
243 another one and more than 1 file is changed |
299 another one and more than 1 file is changed |
380 tmproot)) |
436 tmproot)) |
381 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') |
437 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') |
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) |
389 |
446 |
390 for copy_fn, working_fn, st in fnsandstat: |
447 for copy_fn, working_fn, st in fnsandstat: |
444 that revision is compared to the working directory, and, when no |
501 that revision is compared to the working directory, and, when no |
445 revisions are specified, the working directory files are compared |
502 revisions are specified, the working directory files are compared |
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. |
453 ''' |
516 ''' |
454 opts = pycompat.byteskwargs(opts) |
517 opts = pycompat.byteskwargs(opts) |
473 that revision is compared to the working directory, and, when no |
536 that revision is compared to the working directory, and, when no |
474 revisions are specified, the working directory files are compared |
537 revisions are specified, the working directory files are compared |
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'): |
495 path = util.expandpath(path) |
560 path = util.expandpath(path) |
496 if cmd.startswith('cmd.'): |
561 if cmd.startswith('cmd.'): |
501 path = filemerge.findexternaltool(ui, cmd) or cmd |
566 path = filemerge.findexternaltool(ui, cmd) or cmd |
502 diffopts = ui.config('extdiff', 'opts.' + cmd) |
567 diffopts = ui.config('extdiff', 'opts.' + cmd) |
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: |
510 # case "cmd = path opts" |
576 # case "cmd = path opts" |
511 cmdline = path |
577 cmdline = path |
515 path = procutil.findexe(cmd) |
581 path = procutil.findexe(cmd) |
516 if path is None: |
582 if path is None: |
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] |