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