##// END OF EJS Templates
extdiff: remove unrequired do3way argument to diffpatch()...
Pulkit Goyal -
r45688:d23881b1 default
parent child Browse files
Show More
@@ -1,755 +1,754 b''
1 1 # extdiff.py - external diff program support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to allow external programs to compare revisions
9 9
10 10 The extdiff Mercurial extension allows you to use external programs
11 11 to compare revisions, or revision with working directory. The external
12 12 diff programs are called with a configurable set of options and two
13 13 non-option arguments: paths to directories containing snapshots of
14 14 files to compare.
15 15
16 16 If there is more than one file being compared and the "child" revision
17 17 is the working directory, any modifications made in the external diff
18 18 program will be copied back to the working directory from the temporary
19 19 directory.
20 20
21 21 The extdiff extension also allows you to configure new diff commands, so
22 22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
23 23
24 24 [extdiff]
25 25 # add new command that runs GNU diff(1) in 'context diff' mode
26 26 cdiff = gdiff -Nprc5
27 27 ## or the old way:
28 28 #cmd.cdiff = gdiff
29 29 #opts.cdiff = -Nprc5
30 30
31 31 # add new command called meld, runs meld (no need to name twice). If
32 32 # the meld executable is not available, the meld tool in [merge-tools]
33 33 # will be used, if available
34 34 meld =
35 35
36 36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
38 38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 39 # your .vimrc
40 40 vimdiff = gvim -f "+next" \\
41 41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
42 42
43 43 Tool arguments can include variables that are expanded at runtime::
44 44
45 45 $parent1, $plabel1 - filename, descriptive label of first parent
46 46 $child, $clabel - filename, descriptive label of child revision
47 47 $parent2, $plabel2 - filename, descriptive label of second parent
48 48 $root - repository root
49 49 $parent is an alias for $parent1.
50 50
51 51 The extdiff extension will look in your [diff-tools] and [merge-tools]
52 52 sections for diff tool arguments, when none are specified in [extdiff].
53 53
54 54 ::
55 55
56 56 [extdiff]
57 57 kdiff3 =
58 58
59 59 [diff-tools]
60 60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61 61
62 62 If a program has a graphical interface, it might be interesting to tell
63 63 Mercurial about it. It will prevent the program from being mistakenly
64 64 used in a terminal-only environment (such as an SSH terminal session),
65 65 and will make :hg:`extdiff --per-file` open multiple file diffs at once
66 66 instead of one by one (if you still want to open file diffs one by one,
67 67 you can use the --confirm option).
68 68
69 69 Declaring that a tool has a graphical interface can be done with the
70 70 ``gui`` flag next to where ``diffargs`` are specified:
71 71
72 72 ::
73 73
74 74 [diff-tools]
75 75 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
76 76 kdiff3.gui = true
77 77
78 78 You can use -I/-X and list of file or directory names like normal
79 79 :hg:`diff` command. The extdiff extension makes snapshots of only
80 80 needed files, so running the external diff program will actually be
81 81 pretty fast (at least faster than having to compare the entire tree).
82 82 '''
83 83
84 84 from __future__ import absolute_import
85 85
86 86 import os
87 87 import re
88 88 import shutil
89 89 import stat
90 90 import subprocess
91 91
92 92 from mercurial.i18n import _
93 93 from mercurial.node import (
94 94 nullid,
95 95 short,
96 96 )
97 97 from mercurial import (
98 98 archival,
99 99 cmdutil,
100 100 encoding,
101 101 error,
102 102 filemerge,
103 103 formatter,
104 104 pycompat,
105 105 registrar,
106 106 scmutil,
107 107 util,
108 108 )
109 109 from mercurial.utils import (
110 110 procutil,
111 111 stringutil,
112 112 )
113 113
114 114 cmdtable = {}
115 115 command = registrar.command(cmdtable)
116 116
117 117 configtable = {}
118 118 configitem = registrar.configitem(configtable)
119 119
120 120 configitem(
121 121 b'extdiff', br'opts\..*', default=b'', generic=True,
122 122 )
123 123
124 124 configitem(
125 125 b'extdiff', br'gui\..*', generic=True,
126 126 )
127 127
128 128 configitem(
129 129 b'diff-tools', br'.*\.diffargs$', default=None, generic=True,
130 130 )
131 131
132 132 configitem(
133 133 b'diff-tools', br'.*\.gui$', generic=True,
134 134 )
135 135
136 136 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
137 137 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
138 138 # be specifying the version(s) of Mercurial they are tested with, or
139 139 # leave the attribute unspecified.
140 140 testedwith = b'ships-with-hg-core'
141 141
142 142
143 143 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
144 144 '''snapshot files as of some revision
145 145 if not using snapshot, -I/-X does not work and recursive diff
146 146 in tools like kdiff3 and meld displays too many files.'''
147 147 dirname = os.path.basename(repo.root)
148 148 if dirname == b"":
149 149 dirname = b"root"
150 150 if node is not None:
151 151 dirname = b'%s.%s' % (dirname, short(node))
152 152 base = os.path.join(tmproot, dirname)
153 153 os.mkdir(base)
154 154 fnsandstat = []
155 155
156 156 if node is not None:
157 157 ui.note(
158 158 _(b'making snapshot of %d files from rev %s\n')
159 159 % (len(files), short(node))
160 160 )
161 161 else:
162 162 ui.note(
163 163 _(b'making snapshot of %d files from working directory\n')
164 164 % (len(files))
165 165 )
166 166
167 167 if files:
168 168 repo.ui.setconfig(b"ui", b"archivemeta", False)
169 169
170 170 archival.archive(
171 171 repo,
172 172 base,
173 173 node,
174 174 b'files',
175 175 match=scmutil.matchfiles(repo, files),
176 176 subrepos=listsubrepos,
177 177 )
178 178
179 179 for fn in sorted(files):
180 180 wfn = util.pconvert(fn)
181 181 ui.note(b' %s\n' % wfn)
182 182
183 183 if node is None:
184 184 dest = os.path.join(base, wfn)
185 185
186 186 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
187 187 return dirname, fnsandstat
188 188
189 189
190 190 def formatcmdline(
191 191 cmdline,
192 192 repo_root,
193 193 do3way,
194 194 parent1,
195 195 plabel1,
196 196 parent2,
197 197 plabel2,
198 198 child,
199 199 clabel,
200 200 ):
201 201 # Function to quote file/dir names in the argument string.
202 202 # When not operating in 3-way mode, an empty string is
203 203 # returned for parent2
204 204 replace = {
205 205 b'parent': parent1,
206 206 b'parent1': parent1,
207 207 b'parent2': parent2,
208 208 b'plabel1': plabel1,
209 209 b'plabel2': plabel2,
210 210 b'child': child,
211 211 b'clabel': clabel,
212 212 b'root': repo_root,
213 213 }
214 214
215 215 def quote(match):
216 216 pre = match.group(2)
217 217 key = match.group(3)
218 218 if not do3way and key == b'parent2':
219 219 return pre
220 220 return pre + procutil.shellquote(replace[key])
221 221
222 222 # Match parent2 first, so 'parent1?' will match both parent1 and parent
223 223 regex = (
224 224 br'''(['"]?)([^\s'"$]*)'''
225 225 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
226 226 )
227 227 if not do3way and not re.search(regex, cmdline):
228 228 cmdline += b' $parent1 $child'
229 229 return re.sub(regex, quote, cmdline)
230 230
231 231
232 232 def _systembackground(cmd, environ=None, cwd=None):
233 233 ''' like 'procutil.system', but returns the Popen object directly
234 234 so we don't have to wait on it.
235 235 '''
236 236 env = procutil.shellenviron(environ)
237 237 proc = subprocess.Popen(
238 238 procutil.tonativestr(cmd),
239 239 shell=True,
240 240 close_fds=procutil.closefds,
241 241 env=procutil.tonativeenv(env),
242 242 cwd=pycompat.rapply(procutil.tonativestr, cwd),
243 243 )
244 244 return proc
245 245
246 246
247 247 def _runperfilediff(
248 248 cmdline,
249 249 repo_root,
250 250 ui,
251 251 guitool,
252 252 do3way,
253 253 confirm,
254 254 commonfiles,
255 255 tmproot,
256 256 dir1a,
257 257 dir1b,
258 258 dir2root,
259 259 dir2,
260 260 rev1a,
261 261 rev1b,
262 262 rev2,
263 263 ):
264 264 # Note that we need to sort the list of files because it was
265 265 # built in an "unstable" way and it's annoying to get files in a
266 266 # random order, especially when "confirm" mode is enabled.
267 267 waitprocs = []
268 268 totalfiles = len(commonfiles)
269 269 for idx, commonfile in enumerate(sorted(commonfiles)):
270 270 path1a = os.path.join(tmproot, dir1a, commonfile)
271 271 label1a = commonfile + rev1a
272 272 if not os.path.isfile(path1a):
273 273 path1a = pycompat.osdevnull
274 274
275 275 path1b = b''
276 276 label1b = b''
277 277 if do3way:
278 278 path1b = os.path.join(tmproot, dir1b, commonfile)
279 279 label1b = commonfile + rev1b
280 280 if not os.path.isfile(path1b):
281 281 path1b = pycompat.osdevnull
282 282
283 283 path2 = os.path.join(dir2root, dir2, commonfile)
284 284 label2 = commonfile + rev2
285 285
286 286 if confirm:
287 287 # Prompt before showing this diff
288 288 difffiles = _(b'diff %s (%d of %d)') % (
289 289 commonfile,
290 290 idx + 1,
291 291 totalfiles,
292 292 )
293 293 responses = _(
294 294 b'[Yns?]'
295 295 b'$$ &Yes, show diff'
296 296 b'$$ &No, skip this diff'
297 297 b'$$ &Skip remaining diffs'
298 298 b'$$ &? (display help)'
299 299 )
300 300 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
301 301 if r == 3: # ?
302 302 while r == 3:
303 303 for c, t in ui.extractchoices(responses)[1]:
304 304 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
305 305 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
306 306 if r == 0: # yes
307 307 pass
308 308 elif r == 1: # no
309 309 continue
310 310 elif r == 2: # skip
311 311 break
312 312
313 313 curcmdline = formatcmdline(
314 314 cmdline,
315 315 repo_root,
316 316 do3way=do3way,
317 317 parent1=path1a,
318 318 plabel1=label1a,
319 319 parent2=path1b,
320 320 plabel2=label1b,
321 321 child=path2,
322 322 clabel=label2,
323 323 )
324 324
325 325 if confirm or not guitool:
326 326 # Run the comparison program and wait for it to exit
327 327 # before we show the next file.
328 328 # This is because either we need to wait for confirmation
329 329 # from the user between each invocation, or because, as far
330 330 # as we know, the tool doesn't have a GUI, in which case
331 331 # we can't run multiple CLI programs at the same time.
332 332 ui.debug(
333 333 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
334 334 )
335 335 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
336 336 else:
337 337 # Run the comparison program but don't wait, as we're
338 338 # going to rapid-fire each file diff and then wait on
339 339 # the whole group.
340 340 ui.debug(
341 341 b'running %r in %s (backgrounded)\n'
342 342 % (pycompat.bytestr(curcmdline), tmproot)
343 343 )
344 344 proc = _systembackground(curcmdline, cwd=tmproot)
345 345 waitprocs.append(proc)
346 346
347 347 if waitprocs:
348 348 with ui.timeblockedsection(b'extdiff'):
349 349 for proc in waitprocs:
350 350 proc.wait()
351 351
352 352
353 def diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way):
353 def diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline):
354 354 template = b'hg-%h.patch'
355 355 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
356 356 cmdutil.export(
357 357 repo,
358 358 [repo[node1a].rev(), repo[node2].rev()],
359 359 fm,
360 360 fntemplate=repo.vfs.reljoin(tmproot, template),
361 361 match=matcher,
362 362 )
363 363 label1a = cmdutil.makefilename(repo[node1a], template)
364 364 label2 = cmdutil.makefilename(repo[node2], template)
365 365 dir1a = repo.vfs.reljoin(tmproot, label1a)
366 366 dir2 = repo.vfs.reljoin(tmproot, label2)
367 367 dir1b = None
368 368 label1b = None
369 369 cmdline = formatcmdline(
370 370 cmdline,
371 371 repo.root,
372 do3way=do3way,
373 372 parent1=dir1a,
374 373 plabel1=label1a,
375 374 parent2=dir1b,
376 375 plabel2=label1b,
377 376 child=dir2,
377 # no 3way while comparing patches
378 do3way=False,
378 379 clabel=label2,
379 380 )
380 381 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
381 382 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
382 383 return 1
383 384
384 385
385 386 def diffrevs(
386 387 ui,
387 388 repo,
388 389 node1a,
389 390 node1b,
390 391 node2,
391 392 matcher,
392 393 tmproot,
393 394 cmdline,
394 395 do3way,
395 396 guitool,
396 397 opts,
397 398 ):
398 399
399 400 subrepos = opts.get(b'subrepos')
400 401 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
401 402 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
402 403 if do3way:
403 404 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
404 405 mod_b, add_b, rem_b = (
405 406 set(stb.modified),
406 407 set(stb.added),
407 408 set(stb.removed),
408 409 )
409 410 else:
410 411 mod_b, add_b, rem_b = set(), set(), set()
411 412 modadd = mod_a | add_a | mod_b | add_b
412 413 common = modadd | rem_a | rem_b
413 414 if not common:
414 415 return 0
415 416 # Always make a copy of node1a (and node1b, if applicable)
416 417 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
417 418 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
418 419 rev1a = b'@%d' % repo[node1a].rev()
419 420 if do3way:
420 421 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
421 422 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
422 423 rev1b = b'@%d' % repo[node1b].rev()
423 424 else:
424 425 dir1b = None
425 426 rev1b = b''
426 427
427 428 fnsandstat = []
428 429
429 430 # If node2 in not the wc or there is >1 change, copy it
430 431 dir2root = b''
431 432 rev2 = b''
432 433 if node2:
433 434 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
434 435 rev2 = b'@%d' % repo[node2].rev()
435 436 elif len(common) > 1:
436 437 # we only actually need to get the files to copy back to
437 438 # the working dir in this case (because the other cases
438 439 # are: diffing 2 revisions or single file -- in which case
439 440 # the file is already directly passed to the diff tool).
440 441 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
441 442 else:
442 443 # This lets the diff tool open the changed file directly
443 444 dir2 = b''
444 445 dir2root = repo.root
445 446
446 447 label1a = rev1a
447 448 label1b = rev1b
448 449 label2 = rev2
449 450
450 451 # If only one change, diff the files instead of the directories
451 452 # Handle bogus modifies correctly by checking if the files exist
452 453 if len(common) == 1:
453 454 common_file = util.localpath(common.pop())
454 455 dir1a = os.path.join(tmproot, dir1a, common_file)
455 456 label1a = common_file + rev1a
456 457 if not os.path.isfile(dir1a):
457 458 dir1a = pycompat.osdevnull
458 459 if do3way:
459 460 dir1b = os.path.join(tmproot, dir1b, common_file)
460 461 label1b = common_file + rev1b
461 462 if not os.path.isfile(dir1b):
462 463 dir1b = pycompat.osdevnull
463 464 dir2 = os.path.join(dir2root, dir2, common_file)
464 465 label2 = common_file + rev2
465 466
466 467 if not opts.get(b'per_file'):
467 468 # Run the external tool on the 2 temp directories or the patches
468 469 cmdline = formatcmdline(
469 470 cmdline,
470 471 repo.root,
471 472 do3way=do3way,
472 473 parent1=dir1a,
473 474 plabel1=label1a,
474 475 parent2=dir1b,
475 476 plabel2=label1b,
476 477 child=dir2,
477 478 clabel=label2,
478 479 )
479 480 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
480 481 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
481 482 else:
482 483 # Run the external tool once for each pair of files
483 484 _runperfilediff(
484 485 cmdline,
485 486 repo.root,
486 487 ui,
487 488 guitool=guitool,
488 489 do3way=do3way,
489 490 confirm=opts.get(b'confirm'),
490 491 commonfiles=common,
491 492 tmproot=tmproot,
492 493 dir1a=dir1a,
493 494 dir1b=dir1b,
494 495 dir2root=dir2root,
495 496 dir2=dir2,
496 497 rev1a=rev1a,
497 498 rev1b=rev1b,
498 499 rev2=rev2,
499 500 )
500 501
501 502 for copy_fn, working_fn, st in fnsandstat:
502 503 cpstat = os.lstat(copy_fn)
503 504 # Some tools copy the file and attributes, so mtime may not detect
504 505 # all changes. A size check will detect more cases, but not all.
505 506 # The only certain way to detect every case is to diff all files,
506 507 # which could be expensive.
507 508 # copyfile() carries over the permission, so the mode check could
508 509 # be in an 'elif' branch, but for the case where the file has
509 510 # changed without affecting mtime or size.
510 511 if (
511 512 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
512 513 or cpstat.st_size != st.st_size
513 514 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
514 515 ):
515 516 ui.debug(
516 517 b'file changed while diffing. '
517 518 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
518 519 )
519 520 util.copyfile(copy_fn, working_fn)
520 521
521 522 return 1
522 523
523 524
524 525 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
525 526 '''Do the actual diff:
526 527
527 528 - copy to a temp structure if diffing 2 internal revisions
528 529 - copy to a temp structure if diffing working revision with
529 530 another one and more than 1 file is changed
530 531 - just invoke the diff for a single file in the working dir
531 532 '''
532 533
533 534 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
534 535 revs = opts.get(b'rev')
535 536 change = opts.get(b'change')
536 537 do3way = b'$parent2' in cmdline
537 538
538 539 if change:
539 540 ctx2 = scmutil.revsingle(repo, change, None)
540 541 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
541 542 else:
542 543 ctx1a, ctx2 = scmutil.revpair(repo, revs)
543 544 if not revs:
544 545 ctx1b = repo[None].p2()
545 546 else:
546 547 ctx1b = repo[nullid]
547 548
548 549 node1a = ctx1a.node()
549 550 node1b = ctx1b.node()
550 551 node2 = ctx2.node()
551 552
552 553 # Disable 3-way merge if there is only one parent
553 554 if do3way:
554 555 if node1b == nullid:
555 556 do3way = False
556 557
557 558 matcher = scmutil.match(repo[node2], pats, opts)
558 559
559 560 if opts.get(b'patch'):
560 561 if opts.get(b'subrepos'):
561 562 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
562 563 if opts.get(b'per_file'):
563 564 raise error.Abort(_(b'--patch cannot be used with --per-file'))
564 565 if node2 is None:
565 566 raise error.Abort(_(b'--patch requires two revisions'))
566 567
567 568 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
568 569 try:
569 570 if opts.get(b'patch'):
570 return diffpatch(
571 ui, repo, node1a, node2, tmproot, matcher, cmdline, do3way
572 )
571 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
573 572
574 573 return diffrevs(
575 574 ui,
576 575 repo,
577 576 node1a,
578 577 node1b,
579 578 node2,
580 579 matcher,
581 580 tmproot,
582 581 cmdline,
583 582 do3way,
584 583 guitool,
585 584 opts,
586 585 )
587 586
588 587 finally:
589 588 ui.note(_(b'cleaning up temp directory\n'))
590 589 shutil.rmtree(tmproot)
591 590
592 591
593 592 extdiffopts = (
594 593 [
595 594 (
596 595 b'o',
597 596 b'option',
598 597 [],
599 598 _(b'pass option to comparison program'),
600 599 _(b'OPT'),
601 600 ),
602 601 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
603 602 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
604 603 (
605 604 b'',
606 605 b'per-file',
607 606 False,
608 607 _(b'compare each file instead of revision snapshots'),
609 608 ),
610 609 (
611 610 b'',
612 611 b'confirm',
613 612 False,
614 613 _(b'prompt user before each external program invocation'),
615 614 ),
616 615 (b'', b'patch', None, _(b'compare patches for two revisions')),
617 616 ]
618 617 + cmdutil.walkopts
619 618 + cmdutil.subrepoopts
620 619 )
621 620
622 621
623 622 @command(
624 623 b'extdiff',
625 624 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
626 625 + extdiffopts,
627 626 _(b'hg extdiff [OPT]... [FILE]...'),
628 627 helpcategory=command.CATEGORY_FILE_CONTENTS,
629 628 inferrepo=True,
630 629 )
631 630 def extdiff(ui, repo, *pats, **opts):
632 631 '''use external program to diff repository (or selected files)
633 632
634 633 Show differences between revisions for the specified files, using
635 634 an external program. The default program used is diff, with
636 635 default options "-Npru".
637 636
638 637 To select a different program, use the -p/--program option. The
639 638 program will be passed the names of two directories to compare,
640 639 unless the --per-file option is specified (see below). To pass
641 640 additional options to the program, use -o/--option. These will be
642 641 passed before the names of the directories or files to compare.
643 642
644 643 When two revision arguments are given, then changes are shown
645 644 between those revisions. If only one revision is specified then
646 645 that revision is compared to the working directory, and, when no
647 646 revisions are specified, the working directory files are compared
648 647 to its parent.
649 648
650 649 The --per-file option runs the external program repeatedly on each
651 650 file to diff, instead of once on two directories. By default,
652 651 this happens one by one, where the next file diff is open in the
653 652 external program only once the previous external program (for the
654 653 previous file diff) has exited. If the external program has a
655 654 graphical interface, it can open all the file diffs at once instead
656 655 of one by one. See :hg:`help -e extdiff` for information about how
657 656 to tell Mercurial that a given program has a graphical interface.
658 657
659 658 The --confirm option will prompt the user before each invocation of
660 659 the external program. It is ignored if --per-file isn't specified.
661 660 '''
662 661 opts = pycompat.byteskwargs(opts)
663 662 program = opts.get(b'program')
664 663 option = opts.get(b'option')
665 664 if not program:
666 665 program = b'diff'
667 666 option = option or [b'-Npru']
668 667 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
669 668 return dodiff(ui, repo, cmdline, pats, opts)
670 669
671 670
672 671 class savedcmd(object):
673 672 """use external program to diff repository (or selected files)
674 673
675 674 Show differences between revisions for the specified files, using
676 675 the following program::
677 676
678 677 %(path)s
679 678
680 679 When two revision arguments are given, then changes are shown
681 680 between those revisions. If only one revision is specified then
682 681 that revision is compared to the working directory, and, when no
683 682 revisions are specified, the working directory files are compared
684 683 to its parent.
685 684 """
686 685
687 686 def __init__(self, path, cmdline, isgui):
688 687 # We can't pass non-ASCII through docstrings (and path is
689 688 # in an unknown encoding anyway), but avoid double separators on
690 689 # Windows
691 690 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
692 691 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
693 692 self._cmdline = cmdline
694 693 self._isgui = isgui
695 694
696 695 def __call__(self, ui, repo, *pats, **opts):
697 696 opts = pycompat.byteskwargs(opts)
698 697 options = b' '.join(map(procutil.shellquote, opts[b'option']))
699 698 if options:
700 699 options = b' ' + options
701 700 return dodiff(
702 701 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
703 702 )
704 703
705 704
706 705 def uisetup(ui):
707 706 for cmd, path in ui.configitems(b'extdiff'):
708 707 path = util.expandpath(path)
709 708 if cmd.startswith(b'cmd.'):
710 709 cmd = cmd[4:]
711 710 if not path:
712 711 path = procutil.findexe(cmd)
713 712 if path is None:
714 713 path = filemerge.findexternaltool(ui, cmd) or cmd
715 714 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
716 715 cmdline = procutil.shellquote(path)
717 716 if diffopts:
718 717 cmdline += b' ' + diffopts
719 718 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
720 719 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
721 720 continue
722 721 else:
723 722 if path:
724 723 # case "cmd = path opts"
725 724 cmdline = path
726 725 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
727 726 else:
728 727 # case "cmd ="
729 728 path = procutil.findexe(cmd)
730 729 if path is None:
731 730 path = filemerge.findexternaltool(ui, cmd) or cmd
732 731 cmdline = procutil.shellquote(path)
733 732 diffopts = False
734 733 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
735 734 # look for diff arguments in [diff-tools] then [merge-tools]
736 735 if not diffopts:
737 736 key = cmd + b'.diffargs'
738 737 for section in (b'diff-tools', b'merge-tools'):
739 738 args = ui.config(section, key)
740 739 if args:
741 740 cmdline += b' ' + args
742 741 if isgui is None:
743 742 isgui = ui.configbool(section, cmd + b'.gui') or False
744 743 break
745 744 command(
746 745 cmd,
747 746 extdiffopts[:],
748 747 _(b'hg %s [OPTION]... [FILE]...') % cmd,
749 748 helpcategory=command.CATEGORY_FILE_CONTENTS,
750 749 inferrepo=True,
751 750 )(savedcmd(path, cmdline, isgui))
752 751
753 752
754 753 # tell hggettext to extract docstrings from these functions:
755 754 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now