##// END OF EJS Templates
extdiff: remove dir2root and pass full path as dir2 in _runperfilediff()...
Pulkit Goyal -
r45958:1bed1b00 default
parent child Browse files
Show More
@@ -1,763 +1,761 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 dir2root,
259 258 dir2,
260 259 rev1a,
261 260 rev1b,
262 261 rev2,
263 262 ):
264 263 # Note that we need to sort the list of files because it was
265 264 # built in an "unstable" way and it's annoying to get files in a
266 265 # random order, especially when "confirm" mode is enabled.
267 266 waitprocs = []
268 267 totalfiles = len(commonfiles)
269 268 for idx, commonfile in enumerate(sorted(commonfiles)):
270 269 path1a = os.path.join(dir1a, commonfile)
271 270 label1a = commonfile + rev1a
272 271 if not os.path.isfile(path1a):
273 272 path1a = pycompat.osdevnull
274 273
275 274 path1b = b''
276 275 label1b = b''
277 276 if do3way:
278 277 path1b = os.path.join(dir1b, commonfile)
279 278 label1b = commonfile + rev1b
280 279 if not os.path.isfile(path1b):
281 280 path1b = pycompat.osdevnull
282 281
283 path2 = os.path.join(dir2root, dir2, commonfile)
282 path2 = os.path.join(dir2, commonfile)
284 283 label2 = commonfile + rev2
285 284
286 285 if confirm:
287 286 # Prompt before showing this diff
288 287 difffiles = _(b'diff %s (%d of %d)') % (
289 288 commonfile,
290 289 idx + 1,
291 290 totalfiles,
292 291 )
293 292 responses = _(
294 293 b'[Yns?]'
295 294 b'$$ &Yes, show diff'
296 295 b'$$ &No, skip this diff'
297 296 b'$$ &Skip remaining diffs'
298 297 b'$$ &? (display help)'
299 298 )
300 299 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
301 300 if r == 3: # ?
302 301 while r == 3:
303 302 for c, t in ui.extractchoices(responses)[1]:
304 303 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
305 304 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
306 305 if r == 0: # yes
307 306 pass
308 307 elif r == 1: # no
309 308 continue
310 309 elif r == 2: # skip
311 310 break
312 311
313 312 curcmdline = formatcmdline(
314 313 cmdline,
315 314 repo_root,
316 315 do3way=do3way,
317 316 parent1=path1a,
318 317 plabel1=label1a,
319 318 parent2=path1b,
320 319 plabel2=label1b,
321 320 child=path2,
322 321 clabel=label2,
323 322 )
324 323
325 324 if confirm or not guitool:
326 325 # Run the comparison program and wait for it to exit
327 326 # before we show the next file.
328 327 # This is because either we need to wait for confirmation
329 328 # from the user between each invocation, or because, as far
330 329 # as we know, the tool doesn't have a GUI, in which case
331 330 # we can't run multiple CLI programs at the same time.
332 331 ui.debug(
333 332 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
334 333 )
335 334 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
336 335 else:
337 336 # Run the comparison program but don't wait, as we're
338 337 # going to rapid-fire each file diff and then wait on
339 338 # the whole group.
340 339 ui.debug(
341 340 b'running %r in %s (backgrounded)\n'
342 341 % (pycompat.bytestr(curcmdline), tmproot)
343 342 )
344 343 proc = _systembackground(curcmdline, cwd=tmproot)
345 344 waitprocs.append(proc)
346 345
347 346 if waitprocs:
348 347 with ui.timeblockedsection(b'extdiff'):
349 348 for proc in waitprocs:
350 349 proc.wait()
351 350
352 351
353 352 def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline):
354 353 template = b'hg-%h.patch'
355 354 # write patches to temporary files
356 355 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
357 356 cmdutil.export(
358 357 repo,
359 358 [repo[node1].rev(), repo[node2].rev()],
360 359 fm,
361 360 fntemplate=repo.vfs.reljoin(tmproot, template),
362 361 match=matcher,
363 362 )
364 363 label1 = cmdutil.makefilename(repo[node1], template)
365 364 label2 = cmdutil.makefilename(repo[node2], template)
366 365 file1 = repo.vfs.reljoin(tmproot, label1)
367 366 file2 = repo.vfs.reljoin(tmproot, label2)
368 367 cmdline = formatcmdline(
369 368 cmdline,
370 369 repo.root,
371 370 # no 3way while comparing patches
372 371 do3way=False,
373 372 parent1=file1,
374 373 plabel1=label1,
375 374 # while comparing patches, there is no second parent
376 375 parent2=None,
377 376 plabel2=None,
378 377 child=file2,
379 378 clabel=label2,
380 379 )
381 380 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
382 381 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
383 382 return 1
384 383
385 384
386 385 def diffrevs(
387 386 ui,
388 387 repo,
389 388 node1a,
390 389 node1b,
391 390 node2,
392 391 matcher,
393 392 tmproot,
394 393 cmdline,
395 394 do3way,
396 395 guitool,
397 396 opts,
398 397 ):
399 398
400 399 subrepos = opts.get(b'subrepos')
401 400
402 401 # calculate list of files changed between both revs
403 402 st = repo.status(node1a, node2, matcher, listsubrepos=subrepos)
404 403 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
405 404 if do3way:
406 405 stb = repo.status(node1b, node2, matcher, listsubrepos=subrepos)
407 406 mod_b, add_b, rem_b = (
408 407 set(stb.modified),
409 408 set(stb.added),
410 409 set(stb.removed),
411 410 )
412 411 else:
413 412 mod_b, add_b, rem_b = set(), set(), set()
414 413 modadd = mod_a | add_a | mod_b | add_b
415 414 common = modadd | rem_a | rem_b
416 415 if not common:
417 416 return 0
418 417
419 418 # Always make a copy of node1a (and node1b, if applicable)
420 419 # dir1a should contain files which are:
421 420 # * modified or removed from node1a to node2
422 421 # * modified or added from node1b to node2
423 422 # (except file added from node1a to node2 as they were not present in
424 423 # node1a)
425 424 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
426 425 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
427 426 rev1a = b'@%d' % repo[node1a].rev()
428 427 if do3way:
429 428 # file calculation criteria same as dir1a
430 429 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
431 430 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot, subrepos)[0]
432 431 rev1b = b'@%d' % repo[node1b].rev()
433 432 else:
434 433 dir1b = None
435 434 rev1b = b''
436 435
437 436 fnsandstat = []
438 437
439 438 # If node2 in not the wc or there is >1 change, copy it
440 439 dir2root = b''
441 440 rev2 = b''
442 441 if node2:
443 442 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
444 443 rev2 = b'@%d' % repo[node2].rev()
445 444 elif len(common) > 1:
446 445 # we only actually need to get the files to copy back to
447 446 # the working dir in this case (because the other cases
448 447 # are: diffing 2 revisions or single file -- in which case
449 448 # the file is already directly passed to the diff tool).
450 449 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
451 450 else:
452 451 # This lets the diff tool open the changed file directly
453 452 dir2 = b''
454 453 dir2root = repo.root
455 454
456 455 label1a = rev1a
457 456 label1b = rev1b
458 457 label2 = rev2
459 458
460 459 # If only one change, diff the files instead of the directories
461 460 # Handle bogus modifies correctly by checking if the files exist
462 461 if len(common) == 1:
463 462 common_file = util.localpath(common.pop())
464 463 dir1a = os.path.join(tmproot, dir1a, common_file)
465 464 label1a = common_file + rev1a
466 465 if not os.path.isfile(dir1a):
467 466 dir1a = pycompat.osdevnull
468 467 if do3way:
469 468 dir1b = os.path.join(tmproot, dir1b, common_file)
470 469 label1b = common_file + rev1b
471 470 if not os.path.isfile(dir1b):
472 471 dir1b = pycompat.osdevnull
473 472 dir2 = os.path.join(dir2root, dir2, common_file)
474 473 label2 = common_file + rev2
475 474
476 475 if not opts.get(b'per_file'):
477 476 # Run the external tool on the 2 temp directories or the patches
478 477 cmdline = formatcmdline(
479 478 cmdline,
480 479 repo.root,
481 480 do3way=do3way,
482 481 parent1=dir1a,
483 482 plabel1=label1a,
484 483 parent2=dir1b,
485 484 plabel2=label1b,
486 485 child=dir2,
487 486 clabel=label2,
488 487 )
489 488 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
490 489 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
491 490 else:
492 491 # Run the external tool once for each pair of files
493 492 _runperfilediff(
494 493 cmdline,
495 494 repo.root,
496 495 ui,
497 496 guitool=guitool,
498 497 do3way=do3way,
499 498 confirm=opts.get(b'confirm'),
500 499 commonfiles=common,
501 500 tmproot=tmproot,
502 501 dir1a=os.path.join(tmproot, dir1a),
503 502 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
504 dir2root=dir2root,
505 dir2=dir2,
503 dir2=os.path.join(dir2root, dir2),
506 504 rev1a=rev1a,
507 505 rev1b=rev1b,
508 506 rev2=rev2,
509 507 )
510 508
511 509 for copy_fn, working_fn, st in fnsandstat:
512 510 cpstat = os.lstat(copy_fn)
513 511 # Some tools copy the file and attributes, so mtime may not detect
514 512 # all changes. A size check will detect more cases, but not all.
515 513 # The only certain way to detect every case is to diff all files,
516 514 # which could be expensive.
517 515 # copyfile() carries over the permission, so the mode check could
518 516 # be in an 'elif' branch, but for the case where the file has
519 517 # changed without affecting mtime or size.
520 518 if (
521 519 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
522 520 or cpstat.st_size != st.st_size
523 521 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
524 522 ):
525 523 ui.debug(
526 524 b'file changed while diffing. '
527 525 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
528 526 )
529 527 util.copyfile(copy_fn, working_fn)
530 528
531 529 return 1
532 530
533 531
534 532 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
535 533 '''Do the actual diff:
536 534
537 535 - copy to a temp structure if diffing 2 internal revisions
538 536 - copy to a temp structure if diffing working revision with
539 537 another one and more than 1 file is changed
540 538 - just invoke the diff for a single file in the working dir
541 539 '''
542 540
543 541 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
544 542 revs = opts.get(b'rev')
545 543 change = opts.get(b'change')
546 544 do3way = b'$parent2' in cmdline
547 545
548 546 if change:
549 547 ctx2 = scmutil.revsingle(repo, change, None)
550 548 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
551 549 else:
552 550 ctx1a, ctx2 = scmutil.revpair(repo, revs)
553 551 if not revs:
554 552 ctx1b = repo[None].p2()
555 553 else:
556 554 ctx1b = repo[nullid]
557 555
558 556 node1a = ctx1a.node()
559 557 node1b = ctx1b.node()
560 558 node2 = ctx2.node()
561 559
562 560 # Disable 3-way merge if there is only one parent
563 561 if do3way:
564 562 if node1b == nullid:
565 563 do3way = False
566 564
567 565 matcher = scmutil.match(repo[node2], pats, opts)
568 566
569 567 if opts.get(b'patch'):
570 568 if opts.get(b'subrepos'):
571 569 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
572 570 if opts.get(b'per_file'):
573 571 raise error.Abort(_(b'--patch cannot be used with --per-file'))
574 572 if node2 is None:
575 573 raise error.Abort(_(b'--patch requires two revisions'))
576 574
577 575 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
578 576 try:
579 577 if opts.get(b'patch'):
580 578 return diffpatch(ui, repo, node1a, node2, tmproot, matcher, cmdline)
581 579
582 580 return diffrevs(
583 581 ui,
584 582 repo,
585 583 node1a,
586 584 node1b,
587 585 node2,
588 586 matcher,
589 587 tmproot,
590 588 cmdline,
591 589 do3way,
592 590 guitool,
593 591 opts,
594 592 )
595 593
596 594 finally:
597 595 ui.note(_(b'cleaning up temp directory\n'))
598 596 shutil.rmtree(tmproot)
599 597
600 598
601 599 extdiffopts = (
602 600 [
603 601 (
604 602 b'o',
605 603 b'option',
606 604 [],
607 605 _(b'pass option to comparison program'),
608 606 _(b'OPT'),
609 607 ),
610 608 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
611 609 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
612 610 (
613 611 b'',
614 612 b'per-file',
615 613 False,
616 614 _(b'compare each file instead of revision snapshots'),
617 615 ),
618 616 (
619 617 b'',
620 618 b'confirm',
621 619 False,
622 620 _(b'prompt user before each external program invocation'),
623 621 ),
624 622 (b'', b'patch', None, _(b'compare patches for two revisions')),
625 623 ]
626 624 + cmdutil.walkopts
627 625 + cmdutil.subrepoopts
628 626 )
629 627
630 628
631 629 @command(
632 630 b'extdiff',
633 631 [(b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),]
634 632 + extdiffopts,
635 633 _(b'hg extdiff [OPT]... [FILE]...'),
636 634 helpcategory=command.CATEGORY_FILE_CONTENTS,
637 635 inferrepo=True,
638 636 )
639 637 def extdiff(ui, repo, *pats, **opts):
640 638 '''use external program to diff repository (or selected files)
641 639
642 640 Show differences between revisions for the specified files, using
643 641 an external program. The default program used is diff, with
644 642 default options "-Npru".
645 643
646 644 To select a different program, use the -p/--program option. The
647 645 program will be passed the names of two directories to compare,
648 646 unless the --per-file option is specified (see below). To pass
649 647 additional options to the program, use -o/--option. These will be
650 648 passed before the names of the directories or files to compare.
651 649
652 650 When two revision arguments are given, then changes are shown
653 651 between those revisions. If only one revision is specified then
654 652 that revision is compared to the working directory, and, when no
655 653 revisions are specified, the working directory files are compared
656 654 to its parent.
657 655
658 656 The --per-file option runs the external program repeatedly on each
659 657 file to diff, instead of once on two directories. By default,
660 658 this happens one by one, where the next file diff is open in the
661 659 external program only once the previous external program (for the
662 660 previous file diff) has exited. If the external program has a
663 661 graphical interface, it can open all the file diffs at once instead
664 662 of one by one. See :hg:`help -e extdiff` for information about how
665 663 to tell Mercurial that a given program has a graphical interface.
666 664
667 665 The --confirm option will prompt the user before each invocation of
668 666 the external program. It is ignored if --per-file isn't specified.
669 667 '''
670 668 opts = pycompat.byteskwargs(opts)
671 669 program = opts.get(b'program')
672 670 option = opts.get(b'option')
673 671 if not program:
674 672 program = b'diff'
675 673 option = option or [b'-Npru']
676 674 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
677 675 return dodiff(ui, repo, cmdline, pats, opts)
678 676
679 677
680 678 class savedcmd(object):
681 679 """use external program to diff repository (or selected files)
682 680
683 681 Show differences between revisions for the specified files, using
684 682 the following program::
685 683
686 684 %(path)s
687 685
688 686 When two revision arguments are given, then changes are shown
689 687 between those revisions. If only one revision is specified then
690 688 that revision is compared to the working directory, and, when no
691 689 revisions are specified, the working directory files are compared
692 690 to its parent.
693 691 """
694 692
695 693 def __init__(self, path, cmdline, isgui):
696 694 # We can't pass non-ASCII through docstrings (and path is
697 695 # in an unknown encoding anyway), but avoid double separators on
698 696 # Windows
699 697 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
700 698 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
701 699 self._cmdline = cmdline
702 700 self._isgui = isgui
703 701
704 702 def __call__(self, ui, repo, *pats, **opts):
705 703 opts = pycompat.byteskwargs(opts)
706 704 options = b' '.join(map(procutil.shellquote, opts[b'option']))
707 705 if options:
708 706 options = b' ' + options
709 707 return dodiff(
710 708 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
711 709 )
712 710
713 711
714 712 def uisetup(ui):
715 713 for cmd, path in ui.configitems(b'extdiff'):
716 714 path = util.expandpath(path)
717 715 if cmd.startswith(b'cmd.'):
718 716 cmd = cmd[4:]
719 717 if not path:
720 718 path = procutil.findexe(cmd)
721 719 if path is None:
722 720 path = filemerge.findexternaltool(ui, cmd) or cmd
723 721 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
724 722 cmdline = procutil.shellquote(path)
725 723 if diffopts:
726 724 cmdline += b' ' + diffopts
727 725 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
728 726 elif cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
729 727 continue
730 728 else:
731 729 if path:
732 730 # case "cmd = path opts"
733 731 cmdline = path
734 732 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
735 733 else:
736 734 # case "cmd ="
737 735 path = procutil.findexe(cmd)
738 736 if path is None:
739 737 path = filemerge.findexternaltool(ui, cmd) or cmd
740 738 cmdline = procutil.shellquote(path)
741 739 diffopts = False
742 740 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
743 741 # look for diff arguments in [diff-tools] then [merge-tools]
744 742 if not diffopts:
745 743 key = cmd + b'.diffargs'
746 744 for section in (b'diff-tools', b'merge-tools'):
747 745 args = ui.config(section, key)
748 746 if args:
749 747 cmdline += b' ' + args
750 748 if isgui is None:
751 749 isgui = ui.configbool(section, cmd + b'.gui') or False
752 750 break
753 751 command(
754 752 cmd,
755 753 extdiffopts[:],
756 754 _(b'hg %s [OPTION]... [FILE]...') % cmd,
757 755 helpcategory=command.CATEGORY_FILE_CONTENTS,
758 756 inferrepo=True,
759 757 )(savedcmd(path, cmdline, isgui))
760 758
761 759
762 760 # tell hggettext to extract docstrings from these functions:
763 761 i18nfunctions = [savedcmd]
General Comments 0
You need to be logged in to leave comments. Login now