##// END OF EJS Templates
extdiff: add --from/--to and deprecate -r, as was done for `hg diff`...
Martin von Zweigbergk -
r46749:0a4d47f4 default
parent child Browse files
Show More
@@ -1,795 +1,803 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',
122 122 br'opts\..*',
123 123 default=b'',
124 124 generic=True,
125 125 )
126 126
127 127 configitem(
128 128 b'extdiff',
129 129 br'gui\..*',
130 130 generic=True,
131 131 )
132 132
133 133 configitem(
134 134 b'diff-tools',
135 135 br'.*\.diffargs$',
136 136 default=None,
137 137 generic=True,
138 138 )
139 139
140 140 configitem(
141 141 b'diff-tools',
142 142 br'.*\.gui$',
143 143 generic=True,
144 144 )
145 145
146 146 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
147 147 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
148 148 # be specifying the version(s) of Mercurial they are tested with, or
149 149 # leave the attribute unspecified.
150 150 testedwith = b'ships-with-hg-core'
151 151
152 152
153 153 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
154 154 """snapshot files as of some revision
155 155 if not using snapshot, -I/-X does not work and recursive diff
156 156 in tools like kdiff3 and meld displays too many files."""
157 157 dirname = os.path.basename(repo.root)
158 158 if dirname == b"":
159 159 dirname = b"root"
160 160 if node is not None:
161 161 dirname = b'%s.%s' % (dirname, short(node))
162 162 base = os.path.join(tmproot, dirname)
163 163 os.mkdir(base)
164 164 fnsandstat = []
165 165
166 166 if node is not None:
167 167 ui.note(
168 168 _(b'making snapshot of %d files from rev %s\n')
169 169 % (len(files), short(node))
170 170 )
171 171 else:
172 172 ui.note(
173 173 _(b'making snapshot of %d files from working directory\n')
174 174 % (len(files))
175 175 )
176 176
177 177 if files:
178 178 repo.ui.setconfig(b"ui", b"archivemeta", False)
179 179
180 180 archival.archive(
181 181 repo,
182 182 base,
183 183 node,
184 184 b'files',
185 185 match=scmutil.matchfiles(repo, files),
186 186 subrepos=listsubrepos,
187 187 )
188 188
189 189 for fn in sorted(files):
190 190 wfn = util.pconvert(fn)
191 191 ui.note(b' %s\n' % wfn)
192 192
193 193 if node is None:
194 194 dest = os.path.join(base, wfn)
195 195
196 196 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
197 197 return dirname, fnsandstat
198 198
199 199
200 200 def formatcmdline(
201 201 cmdline,
202 202 repo_root,
203 203 do3way,
204 204 parent1,
205 205 plabel1,
206 206 parent2,
207 207 plabel2,
208 208 child,
209 209 clabel,
210 210 ):
211 211 # Function to quote file/dir names in the argument string.
212 212 # When not operating in 3-way mode, an empty string is
213 213 # returned for parent2
214 214 replace = {
215 215 b'parent': parent1,
216 216 b'parent1': parent1,
217 217 b'parent2': parent2,
218 218 b'plabel1': plabel1,
219 219 b'plabel2': plabel2,
220 220 b'child': child,
221 221 b'clabel': clabel,
222 222 b'root': repo_root,
223 223 }
224 224
225 225 def quote(match):
226 226 pre = match.group(2)
227 227 key = match.group(3)
228 228 if not do3way and key == b'parent2':
229 229 return pre
230 230 return pre + procutil.shellquote(replace[key])
231 231
232 232 # Match parent2 first, so 'parent1?' will match both parent1 and parent
233 233 regex = (
234 234 br'''(['"]?)([^\s'"$]*)'''
235 235 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1'
236 236 )
237 237 if not do3way and not re.search(regex, cmdline):
238 238 cmdline += b' $parent1 $child'
239 239 return re.sub(regex, quote, cmdline)
240 240
241 241
242 242 def _systembackground(cmd, environ=None, cwd=None):
243 243 """like 'procutil.system', but returns the Popen object directly
244 244 so we don't have to wait on it.
245 245 """
246 246 env = procutil.shellenviron(environ)
247 247 proc = subprocess.Popen(
248 248 procutil.tonativestr(cmd),
249 249 shell=True,
250 250 close_fds=procutil.closefds,
251 251 env=procutil.tonativeenv(env),
252 252 cwd=pycompat.rapply(procutil.tonativestr, cwd),
253 253 )
254 254 return proc
255 255
256 256
257 257 def _runperfilediff(
258 258 cmdline,
259 259 repo_root,
260 260 ui,
261 261 guitool,
262 262 do3way,
263 263 confirm,
264 264 commonfiles,
265 265 tmproot,
266 266 dir1a,
267 267 dir1b,
268 268 dir2,
269 269 rev1a,
270 270 rev1b,
271 271 rev2,
272 272 ):
273 273 # Note that we need to sort the list of files because it was
274 274 # built in an "unstable" way and it's annoying to get files in a
275 275 # random order, especially when "confirm" mode is enabled.
276 276 waitprocs = []
277 277 totalfiles = len(commonfiles)
278 278 for idx, commonfile in enumerate(sorted(commonfiles)):
279 279 path1a = os.path.join(dir1a, commonfile)
280 280 label1a = commonfile + rev1a
281 281 if not os.path.isfile(path1a):
282 282 path1a = pycompat.osdevnull
283 283
284 284 path1b = b''
285 285 label1b = b''
286 286 if do3way:
287 287 path1b = os.path.join(dir1b, commonfile)
288 288 label1b = commonfile + rev1b
289 289 if not os.path.isfile(path1b):
290 290 path1b = pycompat.osdevnull
291 291
292 292 path2 = os.path.join(dir2, commonfile)
293 293 label2 = commonfile + rev2
294 294
295 295 if confirm:
296 296 # Prompt before showing this diff
297 297 difffiles = _(b'diff %s (%d of %d)') % (
298 298 commonfile,
299 299 idx + 1,
300 300 totalfiles,
301 301 )
302 302 responses = _(
303 303 b'[Yns?]'
304 304 b'$$ &Yes, show diff'
305 305 b'$$ &No, skip this diff'
306 306 b'$$ &Skip remaining diffs'
307 307 b'$$ &? (display help)'
308 308 )
309 309 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
310 310 if r == 3: # ?
311 311 while r == 3:
312 312 for c, t in ui.extractchoices(responses)[1]:
313 313 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
314 314 r = ui.promptchoice(b'%s %s' % (difffiles, responses))
315 315 if r == 0: # yes
316 316 pass
317 317 elif r == 1: # no
318 318 continue
319 319 elif r == 2: # skip
320 320 break
321 321
322 322 curcmdline = formatcmdline(
323 323 cmdline,
324 324 repo_root,
325 325 do3way=do3way,
326 326 parent1=path1a,
327 327 plabel1=label1a,
328 328 parent2=path1b,
329 329 plabel2=label1b,
330 330 child=path2,
331 331 clabel=label2,
332 332 )
333 333
334 334 if confirm or not guitool:
335 335 # Run the comparison program and wait for it to exit
336 336 # before we show the next file.
337 337 # This is because either we need to wait for confirmation
338 338 # from the user between each invocation, or because, as far
339 339 # as we know, the tool doesn't have a GUI, in which case
340 340 # we can't run multiple CLI programs at the same time.
341 341 ui.debug(
342 342 b'running %r in %s\n' % (pycompat.bytestr(curcmdline), tmproot)
343 343 )
344 344 ui.system(curcmdline, cwd=tmproot, blockedtag=b'extdiff')
345 345 else:
346 346 # Run the comparison program but don't wait, as we're
347 347 # going to rapid-fire each file diff and then wait on
348 348 # the whole group.
349 349 ui.debug(
350 350 b'running %r in %s (backgrounded)\n'
351 351 % (pycompat.bytestr(curcmdline), tmproot)
352 352 )
353 353 proc = _systembackground(curcmdline, cwd=tmproot)
354 354 waitprocs.append(proc)
355 355
356 356 if waitprocs:
357 357 with ui.timeblockedsection(b'extdiff'):
358 358 for proc in waitprocs:
359 359 proc.wait()
360 360
361 361
362 362 def diffpatch(ui, repo, node1, node2, tmproot, matcher, cmdline):
363 363 template = b'hg-%h.patch'
364 364 # write patches to temporary files
365 365 with formatter.nullformatter(ui, b'extdiff', {}) as fm:
366 366 cmdutil.export(
367 367 repo,
368 368 [repo[node1].rev(), repo[node2].rev()],
369 369 fm,
370 370 fntemplate=repo.vfs.reljoin(tmproot, template),
371 371 match=matcher,
372 372 )
373 373 label1 = cmdutil.makefilename(repo[node1], template)
374 374 label2 = cmdutil.makefilename(repo[node2], template)
375 375 file1 = repo.vfs.reljoin(tmproot, label1)
376 376 file2 = repo.vfs.reljoin(tmproot, label2)
377 377 cmdline = formatcmdline(
378 378 cmdline,
379 379 repo.root,
380 380 # no 3way while comparing patches
381 381 do3way=False,
382 382 parent1=file1,
383 383 plabel1=label1,
384 384 # while comparing patches, there is no second parent
385 385 parent2=None,
386 386 plabel2=None,
387 387 child=file2,
388 388 clabel=label2,
389 389 )
390 390 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
391 391 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
392 392 return 1
393 393
394 394
395 395 def diffrevs(
396 396 ui,
397 397 repo,
398 398 ctx1a,
399 399 ctx1b,
400 400 ctx2,
401 401 matcher,
402 402 tmproot,
403 403 cmdline,
404 404 do3way,
405 405 guitool,
406 406 opts,
407 407 ):
408 408
409 409 subrepos = opts.get(b'subrepos')
410 410
411 411 # calculate list of files changed between both revs
412 412 st = ctx1a.status(ctx2, matcher, listsubrepos=subrepos)
413 413 mod_a, add_a, rem_a = set(st.modified), set(st.added), set(st.removed)
414 414 if do3way:
415 415 stb = ctx1b.status(ctx2, matcher, listsubrepos=subrepos)
416 416 mod_b, add_b, rem_b = (
417 417 set(stb.modified),
418 418 set(stb.added),
419 419 set(stb.removed),
420 420 )
421 421 else:
422 422 mod_b, add_b, rem_b = set(), set(), set()
423 423 modadd = mod_a | add_a | mod_b | add_b
424 424 common = modadd | rem_a | rem_b
425 425 if not common:
426 426 return 0
427 427
428 428 # Always make a copy of ctx1a (and ctx1b, if applicable)
429 429 # dir1a should contain files which are:
430 430 # * modified or removed from ctx1a to ctx2
431 431 # * modified or added from ctx1b to ctx2
432 432 # (except file added from ctx1a to ctx2 as they were not present in
433 433 # ctx1a)
434 434 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
435 435 dir1a = snapshot(ui, repo, dir1a_files, ctx1a.node(), tmproot, subrepos)[0]
436 436 rev1a = b'' if ctx1a.rev() is None else b'@%d' % ctx1a.rev()
437 437 if do3way:
438 438 # file calculation criteria same as dir1a
439 439 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
440 440 dir1b = snapshot(
441 441 ui, repo, dir1b_files, ctx1b.node(), tmproot, subrepos
442 442 )[0]
443 443 rev1b = b'@%d' % ctx1b.rev()
444 444 else:
445 445 dir1b = None
446 446 rev1b = b''
447 447
448 448 fnsandstat = []
449 449
450 450 # If ctx2 is not the wc or there is >1 change, copy it
451 451 dir2root = b''
452 452 rev2 = b''
453 453 if ctx2.node() is not None:
454 454 dir2 = snapshot(ui, repo, modadd, ctx2.node(), tmproot, subrepos)[0]
455 455 rev2 = b'@%d' % ctx2.rev()
456 456 elif len(common) > 1:
457 457 # we only actually need to get the files to copy back to
458 458 # the working dir in this case (because the other cases
459 459 # are: diffing 2 revisions or single file -- in which case
460 460 # the file is already directly passed to the diff tool).
461 461 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot, subrepos)
462 462 else:
463 463 # This lets the diff tool open the changed file directly
464 464 dir2 = b''
465 465 dir2root = repo.root
466 466
467 467 label1a = rev1a
468 468 label1b = rev1b
469 469 label2 = rev2
470 470
471 471 if not opts.get(b'per_file'):
472 472 # If only one change, diff the files instead of the directories
473 473 # Handle bogus modifies correctly by checking if the files exist
474 474 if len(common) == 1:
475 475 common_file = util.localpath(common.pop())
476 476 dir1a = os.path.join(tmproot, dir1a, common_file)
477 477 label1a = common_file + rev1a
478 478 if not os.path.isfile(dir1a):
479 479 dir1a = pycompat.osdevnull
480 480 if do3way:
481 481 dir1b = os.path.join(tmproot, dir1b, common_file)
482 482 label1b = common_file + rev1b
483 483 if not os.path.isfile(dir1b):
484 484 dir1b = pycompat.osdevnull
485 485 dir2 = os.path.join(dir2root, dir2, common_file)
486 486 label2 = common_file + rev2
487 487
488 488 # Run the external tool on the 2 temp directories or the patches
489 489 cmdline = formatcmdline(
490 490 cmdline,
491 491 repo.root,
492 492 do3way=do3way,
493 493 parent1=dir1a,
494 494 plabel1=label1a,
495 495 parent2=dir1b,
496 496 plabel2=label1b,
497 497 child=dir2,
498 498 clabel=label2,
499 499 )
500 500 ui.debug(b'running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
501 501 ui.system(cmdline, cwd=tmproot, blockedtag=b'extdiff')
502 502 else:
503 503 # Run the external tool once for each pair of files
504 504 _runperfilediff(
505 505 cmdline,
506 506 repo.root,
507 507 ui,
508 508 guitool=guitool,
509 509 do3way=do3way,
510 510 confirm=opts.get(b'confirm'),
511 511 commonfiles=common,
512 512 tmproot=tmproot,
513 513 dir1a=os.path.join(tmproot, dir1a),
514 514 dir1b=os.path.join(tmproot, dir1b) if do3way else None,
515 515 dir2=os.path.join(dir2root, dir2),
516 516 rev1a=rev1a,
517 517 rev1b=rev1b,
518 518 rev2=rev2,
519 519 )
520 520
521 521 for copy_fn, working_fn, st in fnsandstat:
522 522 cpstat = os.lstat(copy_fn)
523 523 # Some tools copy the file and attributes, so mtime may not detect
524 524 # all changes. A size check will detect more cases, but not all.
525 525 # The only certain way to detect every case is to diff all files,
526 526 # which could be expensive.
527 527 # copyfile() carries over the permission, so the mode check could
528 528 # be in an 'elif' branch, but for the case where the file has
529 529 # changed without affecting mtime or size.
530 530 if (
531 531 cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
532 532 or cpstat.st_size != st.st_size
533 533 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)
534 534 ):
535 535 ui.debug(
536 536 b'file changed while diffing. '
537 537 b'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn)
538 538 )
539 539 util.copyfile(copy_fn, working_fn)
540 540
541 541 return 1
542 542
543 543
544 544 def dodiff(ui, repo, cmdline, pats, opts, guitool=False):
545 545 """Do the actual diff:
546 546
547 547 - copy to a temp structure if diffing 2 internal revisions
548 548 - copy to a temp structure if diffing working revision with
549 549 another one and more than 1 file is changed
550 550 - just invoke the diff for a single file in the working dir
551 551 """
552 552
553 553 cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
554 554 revs = opts.get(b'rev')
555 from_rev = opts.get(b'from')
556 to_rev = opts.get(b'to')
555 557 change = opts.get(b'change')
556 558 do3way = b'$parent2' in cmdline
557 559
558 560 if change:
559 561 ctx2 = scmutil.revsingle(repo, change, None)
560 562 ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
563 elif from_rev or to_rev:
564 repo = scmutil.unhidehashlikerevs(
565 repo, [from_rev] + [to_rev], b'nowarn'
566 )
567 ctx1a = scmutil.revsingle(repo, from_rev, None)
568 ctx1b = repo[nullid]
569 ctx2 = scmutil.revsingle(repo, to_rev, None)
561 570 else:
562 571 ctx1a, ctx2 = scmutil.revpair(repo, revs)
563 572 if not revs:
564 573 ctx1b = repo[None].p2()
565 574 else:
566 575 ctx1b = repo[nullid]
567 576
568 577 # Disable 3-way merge if there is only one parent
569 578 if do3way:
570 579 if ctx1b.node() == nullid:
571 580 do3way = False
572 581
573 582 matcher = scmutil.match(ctx2, pats, opts)
574 583
575 584 if opts.get(b'patch'):
576 585 if opts.get(b'subrepos'):
577 586 raise error.Abort(_(b'--patch cannot be used with --subrepos'))
578 587 if opts.get(b'per_file'):
579 588 raise error.Abort(_(b'--patch cannot be used with --per-file'))
580 589 if ctx2.node() is None:
581 590 raise error.Abort(_(b'--patch requires two revisions'))
582 591
583 592 tmproot = pycompat.mkdtemp(prefix=b'extdiff.')
584 593 try:
585 594 if opts.get(b'patch'):
586 595 return diffpatch(
587 596 ui, repo, ctx1a.node(), ctx2.node(), tmproot, matcher, cmdline
588 597 )
589 598
590 599 return diffrevs(
591 600 ui,
592 601 repo,
593 602 ctx1a,
594 603 ctx1b,
595 604 ctx2,
596 605 matcher,
597 606 tmproot,
598 607 cmdline,
599 608 do3way,
600 609 guitool,
601 610 opts,
602 611 )
603 612
604 613 finally:
605 614 ui.note(_(b'cleaning up temp directory\n'))
606 615 shutil.rmtree(tmproot)
607 616
608 617
609 618 extdiffopts = (
610 619 [
611 620 (
612 621 b'o',
613 622 b'option',
614 623 [],
615 624 _(b'pass option to comparison program'),
616 625 _(b'OPT'),
617 626 ),
618 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
627 (b'r', b'rev', [], _(b'revision (DEPRECATED)'), _(b'REV')),
628 (b'', b'from', b'', _(b'revision to diff from'), _(b'REV1')),
629 (b'', b'to', b'', _(b'revision to diff to'), _(b'REV2')),
619 630 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
620 631 (
621 632 b'',
622 633 b'per-file',
623 634 False,
624 635 _(b'compare each file instead of revision snapshots'),
625 636 ),
626 637 (
627 638 b'',
628 639 b'confirm',
629 640 False,
630 641 _(b'prompt user before each external program invocation'),
631 642 ),
632 643 (b'', b'patch', None, _(b'compare patches for two revisions')),
633 644 ]
634 645 + cmdutil.walkopts
635 646 + cmdutil.subrepoopts
636 647 )
637 648
638 649
639 650 @command(
640 651 b'extdiff',
641 652 [
642 653 (b'p', b'program', b'', _(b'comparison program to run'), _(b'CMD')),
643 654 ]
644 655 + extdiffopts,
645 656 _(b'hg extdiff [OPT]... [FILE]...'),
646 657 helpcategory=command.CATEGORY_FILE_CONTENTS,
647 658 inferrepo=True,
648 659 )
649 660 def extdiff(ui, repo, *pats, **opts):
650 661 """use external program to diff repository (or selected files)
651 662
652 663 Show differences between revisions for the specified files, using
653 664 an external program. The default program used is diff, with
654 665 default options "-Npru".
655 666
656 667 To select a different program, use the -p/--program option. The
657 668 program will be passed the names of two directories to compare,
658 669 unless the --per-file option is specified (see below). To pass
659 670 additional options to the program, use -o/--option. These will be
660 671 passed before the names of the directories or files to compare.
661 672
662 When two revision arguments are given, then changes are shown
663 between those revisions. If only one revision is specified then
664 that revision is compared to the working directory, and, when no
665 revisions are specified, the working directory files are compared
666 to its parent.
673 The --from, --to, and --change options work the same way they do for
674 :hg:`diff`.
667 675
668 676 The --per-file option runs the external program repeatedly on each
669 677 file to diff, instead of once on two directories. By default,
670 678 this happens one by one, where the next file diff is open in the
671 679 external program only once the previous external program (for the
672 680 previous file diff) has exited. If the external program has a
673 681 graphical interface, it can open all the file diffs at once instead
674 682 of one by one. See :hg:`help -e extdiff` for information about how
675 683 to tell Mercurial that a given program has a graphical interface.
676 684
677 685 The --confirm option will prompt the user before each invocation of
678 686 the external program. It is ignored if --per-file isn't specified.
679 687 """
680 688 opts = pycompat.byteskwargs(opts)
681 689 program = opts.get(b'program')
682 690 option = opts.get(b'option')
683 691 if not program:
684 692 program = b'diff'
685 693 option = option or [b'-Npru']
686 694 cmdline = b' '.join(map(procutil.shellquote, [program] + option))
687 695 return dodiff(ui, repo, cmdline, pats, opts)
688 696
689 697
690 698 class savedcmd(object):
691 699 """use external program to diff repository (or selected files)
692 700
693 701 Show differences between revisions for the specified files, using
694 702 the following program::
695 703
696 704 %(path)s
697 705
698 706 When two revision arguments are given, then changes are shown
699 707 between those revisions. If only one revision is specified then
700 708 that revision is compared to the working directory, and, when no
701 709 revisions are specified, the working directory files are compared
702 710 to its parent.
703 711 """
704 712
705 713 def __init__(self, path, cmdline, isgui):
706 714 # We can't pass non-ASCII through docstrings (and path is
707 715 # in an unknown encoding anyway), but avoid double separators on
708 716 # Windows
709 717 docpath = stringutil.escapestr(path).replace(b'\\\\', b'\\')
710 718 self.__doc__ %= {'path': pycompat.sysstr(stringutil.uirepr(docpath))}
711 719 self._cmdline = cmdline
712 720 self._isgui = isgui
713 721
714 722 def __call__(self, ui, repo, *pats, **opts):
715 723 opts = pycompat.byteskwargs(opts)
716 724 options = b' '.join(map(procutil.shellquote, opts[b'option']))
717 725 if options:
718 726 options = b' ' + options
719 727 return dodiff(
720 728 ui, repo, self._cmdline + options, pats, opts, guitool=self._isgui
721 729 )
722 730
723 731
724 732 def _gettooldetails(ui, cmd, path):
725 733 """
726 734 returns following things for a
727 735 ```
728 736 [extdiff]
729 737 <cmd> = <path>
730 738 ```
731 739 entry:
732 740
733 741 cmd: command/tool name
734 742 path: path to the tool
735 743 cmdline: the command which should be run
736 744 isgui: whether the tool uses GUI or not
737 745
738 746 Reads all external tools related configs, whether it be extdiff section,
739 747 diff-tools or merge-tools section, or its specified in an old format or
740 748 the latest format.
741 749 """
742 750 path = util.expandpath(path)
743 751 if cmd.startswith(b'cmd.'):
744 752 cmd = cmd[4:]
745 753 if not path:
746 754 path = procutil.findexe(cmd)
747 755 if path is None:
748 756 path = filemerge.findexternaltool(ui, cmd) or cmd
749 757 diffopts = ui.config(b'extdiff', b'opts.' + cmd)
750 758 cmdline = procutil.shellquote(path)
751 759 if diffopts:
752 760 cmdline += b' ' + diffopts
753 761 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
754 762 else:
755 763 if path:
756 764 # case "cmd = path opts"
757 765 cmdline = path
758 766 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
759 767 else:
760 768 # case "cmd ="
761 769 path = procutil.findexe(cmd)
762 770 if path is None:
763 771 path = filemerge.findexternaltool(ui, cmd) or cmd
764 772 cmdline = procutil.shellquote(path)
765 773 diffopts = False
766 774 isgui = ui.configbool(b'extdiff', b'gui.' + cmd)
767 775 # look for diff arguments in [diff-tools] then [merge-tools]
768 776 if not diffopts:
769 777 key = cmd + b'.diffargs'
770 778 for section in (b'diff-tools', b'merge-tools'):
771 779 args = ui.config(section, key)
772 780 if args:
773 781 cmdline += b' ' + args
774 782 if isgui is None:
775 783 isgui = ui.configbool(section, cmd + b'.gui') or False
776 784 break
777 785 return cmd, path, cmdline, isgui
778 786
779 787
780 788 def uisetup(ui):
781 789 for cmd, path in ui.configitems(b'extdiff'):
782 790 if cmd.startswith(b'opts.') or cmd.startswith(b'gui.'):
783 791 continue
784 792 cmd, path, cmdline, isgui = _gettooldetails(ui, cmd, path)
785 793 command(
786 794 cmd,
787 795 extdiffopts[:],
788 796 _(b'hg %s [OPTION]... [FILE]...') % cmd,
789 797 helpcategory=command.CATEGORY_FILE_CONTENTS,
790 798 inferrepo=True,
791 799 )(savedcmd(path, cmdline, isgui))
792 800
793 801
794 802 # tell hggettext to extract docstrings from these functions:
795 803 i18nfunctions = [savedcmd]
@@ -1,554 +1,555 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "extdiff=" >> $HGRCPATH
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo a > a
7 7 $ echo b > b
8 8 $ hg add
9 9 adding a
10 10 adding b
11 11
12 12 Should diff cloned directories:
13 13
14 14 $ hg extdiff -o -r
15 15 Only in a: a
16 16 Only in a: b
17 17 [1]
18 18
19 19 $ cat <<EOF >> $HGRCPATH
20 20 > [extdiff]
21 21 > cmd.falabala = echo
22 22 > opts.falabala = diffing
23 23 > cmd.edspace = echo
24 24 > opts.edspace = "name <user@example.com>"
25 25 > alabalaf =
26 26 > [merge-tools]
27 27 > alabalaf.executable = echo
28 28 > alabalaf.diffargs = diffing
29 29 > EOF
30 30
31 31 $ hg falabala
32 32 diffing a.000000000000 a
33 33 [1]
34 34
35 35 $ hg help falabala
36 36 hg falabala [OPTION]... [FILE]...
37 37
38 38 use external program to diff repository (or selected files)
39 39
40 40 Show differences between revisions for the specified files, using the
41 41 following program:
42 42
43 43 'echo'
44 44
45 45 When two revision arguments are given, then changes are shown between
46 46 those revisions. If only one revision is specified then that revision is
47 47 compared to the working directory, and, when no revisions are specified,
48 48 the working directory files are compared to its parent.
49 49
50 50 options ([+] can be repeated):
51 51
52 52 -o --option OPT [+] pass option to comparison program
53 -r --rev REV [+] revision
53 --from REV1 revision to diff from
54 --to REV2 revision to diff to
54 55 -c --change REV change made by revision
55 56 --per-file compare each file instead of revision snapshots
56 57 --confirm prompt user before each external program invocation
57 58 --patch compare patches for two revisions
58 59 -I --include PATTERN [+] include names matching the given patterns
59 60 -X --exclude PATTERN [+] exclude names matching the given patterns
60 61 -S --subrepos recurse into subrepositories
61 62
62 63 (some details hidden, use --verbose to show complete help)
63 64
64 65 $ hg ci -d '0 0' -mtest1
65 66
66 67 $ echo b >> a
67 68 $ hg ci -d '1 0' -mtest2
68 69
69 70 Should diff cloned files directly:
70 71
71 $ hg falabala -r 0:1
72 $ hg falabala --from 0 --to 1
72 73 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
73 74 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
74 75 [1]
75 76
76 77 Can show diff from working copy:
77 78 $ echo c >> a
78 $ hg falabala -r 'wdir()' -r 1
79 $ hg falabala --to 1
79 80 diffing "*\\extdiff.*\\a" "a.34eed99112ab\\a" (glob) (windows !)
80 81 diffing */extdiff.*/a a.34eed99112ab/a (glob) (no-windows !)
81 82 [1]
82 83 $ hg revert a
83 84 $ rm a.orig
84 85
85 86 Specifying an empty revision should abort.
86 87
87 88 $ hg extdiff -p diff --patch --rev 'ancestor()' --rev 1
88 89 abort: empty revision on one side of range
89 90 [255]
90 91
91 92 Test diff during merge:
92 93
93 94 $ hg update -C 0
94 95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 96 $ echo c >> c
96 97 $ hg add c
97 98 $ hg ci -m "new branch" -d '1 0'
98 99 created new head
99 100 $ hg merge 1
100 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 102 (branch merge, don't forget to commit)
102 103
103 104 Should diff cloned file against wc file:
104 105
105 106 $ hg falabala
106 107 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "*\\a\\a" (glob) (windows !)
107 108 diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) (no-windows !)
108 109 [1]
109 110
110 111
111 112 Test --change option:
112 113
113 114 $ hg ci -d '2 0' -mtest3
114 115
115 116 $ hg falabala -c 1
116 117 diffing "*\\extdiff.*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
117 118 diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
118 119 [1]
119 120
120 121 Check diff are made from the first parent:
121 122
122 123 $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code"
123 124 diffing "*\\extdiff.*\\a.2a13a4d2da36\\a" "a.46c0e4daeb72\\a" (glob) (windows !)
124 125 diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) (no-windows !)
125 126 diff-like tools yield a non-zero exit code
126 127
127 128 issue3153: ensure using extdiff with removed subrepos doesn't crash:
128 129
129 130 $ hg init suba
130 131 $ cd suba
131 132 $ echo suba > suba
132 133 $ hg add
133 134 adding suba
134 135 $ hg ci -m "adding suba file"
135 136 $ cd ..
136 137 $ echo suba=suba > .hgsub
137 138 $ hg add
138 139 adding .hgsub
139 140 $ hg ci -Sm "adding subrepo"
140 141 $ echo > .hgsub
141 142 $ hg ci -m "removing subrepo"
142 $ hg falabala -r 4 -r 5 -S
143 $ hg falabala --from 4 --to 5 -S
143 144 diffing a.398e36faf9c6 a.5ab95fb166c4
144 145 [1]
145 146
146 147 Test --per-file option:
147 148
148 149 $ hg up -q -C 3
149 150 $ echo a2 > a
150 151 $ echo b2 > b
151 152 $ hg ci -d '3 0' -mtestmode1
152 153 created new head
153 154 $ hg falabala -c 6 --per-file
154 155 diffing "*\\extdiff.*\\a.46c0e4daeb72\\a" "a.81906f2b98ac\\a" (glob) (windows !)
155 156 diffing */extdiff.*/a.46c0e4daeb72/a a.81906f2b98ac/a (glob) (no-windows !)
156 157 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
157 158 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
158 159 [1]
159 160
160 161 Test --per-file option for gui tool:
161 162
162 163 $ hg --config extdiff.gui.alabalaf=True alabalaf -c 6 --per-file --debug
163 164 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
164 165 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
165 166 making snapshot of 2 files from rev 46c0e4daeb72
166 167 a
167 168 b
168 169 making snapshot of 2 files from rev 81906f2b98ac
169 170 a
170 171 b
171 172 running '* diffing * *' in * (backgrounded) (glob)
172 173 running '* diffing * *' in * (backgrounded) (glob)
173 174 cleaning up temp directory
174 175 [1]
175 176
176 177 Test --per-file option for gui tool again:
177 178
178 179 $ hg --config merge-tools.alabalaf.gui=True alabalaf -c 6 --per-file --debug
179 180 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
180 181 diffing */extdiff.*/a.46c0e4daeb72/* a.81906f2b98ac/* (glob)
181 182 making snapshot of 2 files from rev 46c0e4daeb72
182 183 a
183 184 b
184 185 making snapshot of 2 files from rev 81906f2b98ac
185 186 a
186 187 b
187 188 running '* diffing * *' in * (backgrounded) (glob)
188 189 running '* diffing * *' in * (backgrounded) (glob)
189 190 cleaning up temp directory
190 191 [1]
191 192
192 193 Test --per-file and --confirm options:
193 194
194 195 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
195 196 > n
196 197 > y
197 198 > EOF
198 199 diff a (1 of 2) [Yns?] n
199 200 diff b (2 of 2) [Yns?] y
200 201 diffing "*\\extdiff.*\\a.46c0e4daeb72\\b" "a.81906f2b98ac\\b" (glob) (windows !)
201 202 diffing */extdiff.*/a.46c0e4daeb72/b a.81906f2b98ac/b (glob) (no-windows !)
202 203 [1]
203 204
204 205 Test --per-file and --confirm options with skipping:
205 206
206 207 $ hg --config ui.interactive=True falabala -c 6 --per-file --confirm <<EOF
207 208 > s
208 209 > EOF
209 210 diff a (1 of 2) [Yns?] s
210 211 [1]
211 212
212 213 issue4463: usage of command line configuration without additional quoting
213 214
214 215 $ cat <<EOF >> $HGRCPATH
215 216 > [extdiff]
216 217 > cmd.4463a = echo
217 218 > opts.4463a = a-naked 'single quoted' "double quoted"
218 219 > 4463b = echo b-naked 'single quoted' "double quoted"
219 220 > echo =
220 221 > EOF
221 222 $ hg update -q -C 0
222 223 $ echo a >> a
223 224
224 225 $ hg --debug 4463a | grep '^running'
225 226 running 'echo a-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
226 227 running 'echo a-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
227 228 $ hg --debug 4463b | grep '^running'
228 229 running 'echo b-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
229 230 running 'echo b-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
230 231 $ hg --debug echo | grep '^running'
231 232 running '*echo* "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
232 233 running '*echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
233 234
234 235 (getting options from other than extdiff section)
235 236
236 237 $ cat <<EOF >> $HGRCPATH
237 238 > [extdiff]
238 239 > # using diff-tools diffargs
239 240 > 4463b2 = echo
240 241 > # using merge-tools diffargs
241 242 > 4463b3 = echo
242 243 > # no diffargs
243 244 > 4463b4 = echo
244 245 > [diff-tools]
245 246 > 4463b2.diffargs = b2-naked 'single quoted' "double quoted"
246 247 > [merge-tools]
247 248 > 4463b3.diffargs = b3-naked 'single quoted' "double quoted"
248 249 > EOF
249 250
250 251 $ hg --debug 4463b2 | grep '^running'
251 252 running 'echo b2-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
252 253 running 'echo b2-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
253 254 $ hg --debug 4463b3 | grep '^running'
254 255 running 'echo b3-naked \'single quoted\' "double quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
255 256 running 'echo b3-naked \'single quoted\' "double quoted" */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
256 257 $ hg --debug 4463b4 | grep '^running'
257 258 running 'echo "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
258 259 running 'echo */a $TESTTMP/a/a' in */extdiff.* (glob) (no-windows !)
259 260 $ hg --debug 4463b4 --option b4-naked --option 'being quoted' | grep '^running'
260 261 running 'echo b4-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
261 262 running "echo b4-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
262 263 $ hg --debug extdiff -p echo --option echo-naked --option 'being quoted' | grep '^running'
263 264 running 'echo echo-naked "being quoted" "*\\a" "*\\a"' in */extdiff.* (glob) (windows !)
264 265 running "echo echo-naked 'being quoted' */a $TESTTMP/a/a" in */extdiff.* (glob) (no-windows !)
265 266
266 267 $ touch 'sp ace'
267 268 $ hg add 'sp ace'
268 269 $ hg ci -m 'sp ace'
269 270 created new head
270 271 $ echo > 'sp ace'
271 272
272 273 Test pre-72a89cf86fcd backward compatibility with half-baked manual quoting
273 274
274 275 $ cat <<EOF >> $HGRCPATH
275 276 > [extdiff]
276 277 > odd =
277 278 > [merge-tools]
278 279 > odd.diffargs = --foo='\$clabel' '\$clabel' "--bar=\$clabel" "\$clabel"
279 280 > odd.executable = echo
280 281 > EOF
281 282
282 283 $ hg --debug odd | grep '^running'
283 284 running '"*\\echo.exe" --foo="sp ace" "sp ace" --bar="sp ace" "sp ace"' in * (glob) (windows !)
284 285 running "*/echo --foo='sp ace' 'sp ace' --bar='sp ace' 'sp ace'" in * (glob) (no-windows !)
285 286
286 287 Empty argument must be quoted
287 288
288 289 $ cat <<EOF >> $HGRCPATH
289 290 > [extdiff]
290 291 > kdiff3 = echo
291 292 > [merge-tools]
292 293 > kdiff3.diffargs=--L1 \$plabel1 --L2 \$clabel \$parent \$child
293 294 > EOF
294 295
295 $ hg --debug kdiff3 -r0 | grep '^running'
296 $ hg --debug kdiff3 --from 0 | grep '^running'
296 297 running 'echo --L1 "@0" --L2 "" a.8a5febb7f867 a' in * (glob) (windows !)
297 298 running "echo --L1 '@0' --L2 '' a.8a5febb7f867 a" in * (glob) (no-windows !)
298 299
299 300
300 301 Test extdiff of multiple files in tmp dir:
301 302
302 303 $ hg update -C 0 > /dev/null
303 304 $ echo changed > a
304 305 $ echo changed > b
305 306 #if execbit
306 307 $ chmod +x b
307 308 #endif
308 309
309 310 Diff in working directory, before:
310 311
311 312 $ hg diff --git
312 313 diff --git a/a b/a
313 314 --- a/a
314 315 +++ b/a
315 316 @@ -1,1 +1,1 @@
316 317 -a
317 318 +changed
318 319 diff --git a/b b/b
319 320 old mode 100644 (execbit !)
320 321 new mode 100755 (execbit !)
321 322 --- a/b
322 323 +++ b/b
323 324 @@ -1,1 +1,1 @@
324 325 -b
325 326 +changed
326 327
327 328
328 329 Edit with extdiff -p:
329 330
330 331 Prepare custom diff/edit tool:
331 332
332 333 $ cat > 'diff tool.py' << EOT
333 334 > #!$PYTHON
334 335 > import time
335 336 > time.sleep(1) # avoid unchanged-timestamp problems
336 337 > open('a/a', 'ab').write(b'edited\n')
337 338 > open('a/b', 'ab').write(b'edited\n')
338 339 > EOT
339 340
340 341 #if execbit
341 342 $ chmod +x 'diff tool.py'
342 343 #endif
343 344
344 345 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
345 346 and start tool
346 347
347 348 #if windows
348 349 $ cat > 'diff tool.bat' << EOF
349 350 > @"$PYTHON" "`pwd`/diff tool.py"
350 351 > EOF
351 352 $ hg extdiff -p "`pwd`/diff tool.bat"
352 353 [1]
353 354 #else
354 355 $ hg extdiff -p "`pwd`/diff tool.py"
355 356 [1]
356 357 #endif
357 358
358 359 Diff in working directory, after:
359 360
360 361 $ hg diff --git
361 362 diff --git a/a b/a
362 363 --- a/a
363 364 +++ b/a
364 365 @@ -1,1 +1,2 @@
365 366 -a
366 367 +changed
367 368 +edited
368 369 diff --git a/b b/b
369 370 old mode 100644 (execbit !)
370 371 new mode 100755 (execbit !)
371 372 --- a/b
372 373 +++ b/b
373 374 @@ -1,1 +1,2 @@
374 375 -b
375 376 +changed
376 377 +edited
377 378
378 379 Test extdiff with --option:
379 380
380 381 $ hg extdiff -p echo -o this -c 1
381 382 this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
382 383 this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
383 384 [1]
384 385
385 386 $ hg falabala -o this -c 1
386 387 diffing this "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
387 388 diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
388 389 [1]
389 390
390 391 Test extdiff's handling of options with spaces in them:
391 392
392 393 $ hg edspace -c 1
393 394 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
394 395 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
395 396 [1]
396 397
397 398 $ hg extdiff -p echo -o "name <user@example.com>" -c 1
398 399 "name <user@example.com>" "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
399 400 name <user@example.com> */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
400 401 [1]
401 402
402 403 Test with revsets:
403 404
404 405 $ hg extdif -p echo -c "rev(1)"
405 406 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
406 407 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
407 408 [1]
408 409
409 410 $ hg extdif -p echo -r "0::1"
410 411 "*\\a.8a5febb7f867\\a" "a.34eed99112ab\\a" (glob) (windows !)
411 412 */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) (no-windows !)
412 413 [1]
413 414
414 415 Fallback to merge-tools.tool.executable|regkey
415 416 $ mkdir dir
416 417 $ cat > 'dir/tool.sh' << 'EOF'
417 418 > #!/bin/sh
418 419 > # Mimic a tool that syncs all attrs, including mtime
419 420 > cp $1/a $2/a
420 421 > touch -r $1/a $2/a
421 422 > chmod +x $2/a
422 423 > echo "** custom diff **"
423 424 > EOF
424 425 #if execbit
425 426 $ chmod +x dir/tool.sh
426 427 #endif
427 428
428 429 Windows can't run *.sh directly, so create a shim executable that can be.
429 430 Without something executable, the next hg command will try to run `tl` instead
430 431 of $tool (and fail).
431 432 #if windows
432 433 $ cat > dir/tool.bat <<EOF
433 434 > @sh -c "`pwd`/dir/tool.sh %1 %2"
434 435 > EOF
435 436 $ tool=`pwd`/dir/tool.bat
436 437 #else
437 438 $ tool=`pwd`/dir/tool.sh
438 439 #endif
439 440
440 441 $ cat a
441 442 changed
442 443 edited
443 444 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
444 445 making snapshot of 2 files from rev * (glob)
445 446 a
446 447 b
447 448 making snapshot of 2 files from working directory
448 449 a
449 450 b
450 451 running '$TESTTMP/a/dir/tool.bat a.* a' in */extdiff.* (glob) (windows !)
451 452 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob) (no-windows !)
452 453 ** custom diff **
453 454 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
454 455 cleaning up temp directory
455 456 [1]
456 457 $ cat a
457 458 a
458 459
459 460 #if execbit
460 461 $ [ -x a ]
461 462
462 463 $ cat > 'dir/tool.sh' << 'EOF'
463 464 > #!/bin/sh
464 465 > chmod -x $2/a
465 466 > echo "** custom diff **"
466 467 > EOF
467 468
468 469 $ hg --debug tl --config extdiff.tl= --config merge-tools.tl.executable=$tool
469 470 making snapshot of 2 files from rev * (glob)
470 471 a
471 472 b
472 473 making snapshot of 2 files from working directory
473 474 a
474 475 b
475 476 running '$TESTTMP/a/dir/tool.sh a.* a' in */extdiff.* (glob)
476 477 ** custom diff **
477 478 file changed while diffing. Overwriting: $TESTTMP/a/a (src: */extdiff.*/a/a) (glob)
478 479 cleaning up temp directory
479 480 [1]
480 481
481 482 $ [ -x a ]
482 483 [1]
483 484 #endif
484 485
485 486 $ cd ..
486 487
487 488 #if symlink
488 489
489 490 Test symlinks handling (issue1909)
490 491
491 492 $ hg init testsymlinks
492 493 $ cd testsymlinks
493 494 $ echo a > a
494 495 $ hg ci -Am adda
495 496 adding a
496 497 $ echo a >> a
497 498 $ ln -s missing linka
498 499 $ hg add linka
499 $ hg falabala -r 0 --traceback
500 $ hg falabala --from 0 --traceback
500 501 diffing testsymlinks.07f494440405 testsymlinks
501 502 [1]
502 503 $ cd ..
503 504
504 505 #endif
505 506
506 507 Test handling of non-ASCII paths in generated docstrings (issue5301)
507 508
508 509 >>> with open("u", "wb") as f:
509 510 ... n = f.write(b"\xa5\xa5")
510 511 $ U=`cat u`
511 512
512 513 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy
513 514 abort: no matches
514 515 (try 'hg help' for a list of topics)
515 516 [255]
516 517
517 518 $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help td > /dev/null
518 519
519 520 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help -k xyzzy
520 521 abort: no matches
521 522 (try 'hg help' for a list of topics)
522 523 [255]
523 524
524 525 $ LC_MESSAGES=ja_JP.UTF-8 hg --config hgext.extdiff= --config extdiff.cmd.td=$U help td \
525 526 > | grep "^ '"
526 527 '\xa5\xa5'
527 528
528 529 $ cd $TESTTMP
529 530
530 531 Test that diffing a single file works, even if that file is new
531 532
532 533 $ hg init testsinglefile
533 534 $ cd testsinglefile
534 535 $ echo a > a
535 536 $ hg add a
536 537 $ hg falabala
537 538 diffing nul "*\\a" (glob) (windows !)
538 539 diffing /dev/null */a (glob) (no-windows !)
539 540 [1]
540 541 $ hg ci -qm a
541 542 $ hg falabala -c .
542 543 diffing nul "*\\a" (glob) (windows !)
543 544 diffing /dev/null */a (glob) (no-windows !)
544 545 [1]
545 546 $ echo a >> a
546 547 $ hg falabala
547 548 diffing "*\\a" "*\\a" (glob) (windows !)
548 549 diffing */a */a (glob) (no-windows !)
549 550 [1]
550 551 $ hg ci -qm 2a
551 552 $ hg falabala -c .
552 553 diffing "*\\a" "*\\a" (glob) (windows !)
553 554 diffing */a */a (glob) (no-windows !)
554 555 [1]
@@ -1,1909 +1,1908 b''
1 1 Test basic extension support
2 2 $ cat > unflush.py <<EOF
3 3 > import sys
4 4 > from mercurial import pycompat
5 5 > if pycompat.ispy3:
6 6 > # no changes required
7 7 > sys.exit(0)
8 8 > with open(sys.argv[1], 'rb') as f:
9 9 > data = f.read()
10 10 > with open(sys.argv[1], 'wb') as f:
11 11 > f.write(data.replace(b', flush=True', b''))
12 12 > EOF
13 13
14 14 $ cat > foobar.py <<EOF
15 15 > import os
16 16 > from mercurial import commands, exthelper, registrar
17 17 >
18 18 > eh = exthelper.exthelper()
19 19 > eh.configitem(b'tests', b'foo', default=b"Foo")
20 20 >
21 21 > uisetup = eh.finaluisetup
22 22 > uipopulate = eh.finaluipopulate
23 23 > reposetup = eh.finalreposetup
24 24 > cmdtable = eh.cmdtable
25 25 > configtable = eh.configtable
26 26 >
27 27 > @eh.uisetup
28 28 > def _uisetup(ui):
29 29 > ui.debug(b"uisetup called [debug]\\n")
30 30 > ui.write(b"uisetup called\\n")
31 31 > ui.status(b"uisetup called [status]\\n")
32 32 > ui.flush()
33 33 > @eh.uipopulate
34 34 > def _uipopulate(ui):
35 35 > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
36 36 > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
37 37 > @eh.reposetup
38 38 > def _reposetup(ui, repo):
39 39 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
40 40 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
41 41 > ui.flush()
42 42 > @eh.command(b'foo', [], b'hg foo')
43 43 > def foo(ui, *args, **kwargs):
44 44 > foo = ui.config(b'tests', b'foo')
45 45 > ui.write(foo)
46 46 > ui.write(b"\\n")
47 47 > @eh.command(b'bar', [], b'hg bar', norepo=True)
48 48 > def bar(ui, *args, **kwargs):
49 49 > ui.write(b"Bar\\n")
50 50 > EOF
51 51 $ abspath=`pwd`/foobar.py
52 52
53 53 $ mkdir barfoo
54 54 $ cp foobar.py barfoo/__init__.py
55 55 $ barfoopath=`pwd`/barfoo
56 56
57 57 $ hg init a
58 58 $ cd a
59 59 $ echo foo > file
60 60 $ hg add file
61 61 $ hg commit -m 'add file'
62 62
63 63 $ echo '[extensions]' >> $HGRCPATH
64 64 $ echo "foobar = $abspath" >> $HGRCPATH
65 65 $ hg foo
66 66 uisetup called
67 67 uisetup called [status]
68 68 uipopulate called (1 times)
69 69 uipopulate called (1 times)
70 70 uipopulate called (1 times)
71 71 reposetup called for a
72 72 ui == repo.ui
73 73 uipopulate called (1 times) (chg !)
74 74 uipopulate called (1 times) (chg !)
75 75 uipopulate called (1 times) (chg !)
76 76 uipopulate called (1 times) (chg !)
77 77 uipopulate called (1 times) (chg !)
78 78 reposetup called for a (chg !)
79 79 ui == repo.ui (chg !)
80 80 Foo
81 81 $ hg foo --quiet
82 82 uisetup called (no-chg !)
83 83 uipopulate called (1 times)
84 84 uipopulate called (1 times)
85 85 uipopulate called (1 times) (chg !)
86 86 uipopulate called (1 times) (chg !)
87 87 uipopulate called (1 times)
88 88 reposetup called for a
89 89 ui == repo.ui
90 90 Foo
91 91 $ hg foo --debug
92 92 uisetup called [debug] (no-chg !)
93 93 uisetup called (no-chg !)
94 94 uisetup called [status] (no-chg !)
95 95 uipopulate called (1 times)
96 96 uipopulate called (1 times)
97 97 uipopulate called (1 times) (chg !)
98 98 uipopulate called (1 times) (chg !)
99 99 uipopulate called (1 times)
100 100 reposetup called for a
101 101 ui == repo.ui
102 102 Foo
103 103
104 104 $ cd ..
105 105 $ hg clone a b
106 106 uisetup called (no-chg !)
107 107 uisetup called [status] (no-chg !)
108 108 uipopulate called (1 times)
109 109 uipopulate called (1 times) (chg !)
110 110 uipopulate called (1 times)
111 111 reposetup called for a
112 112 ui == repo.ui
113 113 uipopulate called (1 times)
114 114 reposetup called for b
115 115 ui == repo.ui
116 116 updating to branch default
117 117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118
119 119 $ hg bar
120 120 uisetup called (no-chg !)
121 121 uisetup called [status] (no-chg !)
122 122 uipopulate called (1 times)
123 123 uipopulate called (1 times) (chg !)
124 124 Bar
125 125 $ echo 'foobar = !' >> $HGRCPATH
126 126
127 127 module/__init__.py-style
128 128
129 129 $ echo "barfoo = $barfoopath" >> $HGRCPATH
130 130 $ cd a
131 131 $ hg foo
132 132 uisetup called
133 133 uisetup called [status]
134 134 uipopulate called (1 times)
135 135 uipopulate called (1 times)
136 136 uipopulate called (1 times)
137 137 reposetup called for a
138 138 ui == repo.ui
139 139 uipopulate called (1 times) (chg !)
140 140 uipopulate called (1 times) (chg !)
141 141 uipopulate called (1 times) (chg !)
142 142 uipopulate called (1 times) (chg !)
143 143 uipopulate called (1 times) (chg !)
144 144 reposetup called for a (chg !)
145 145 ui == repo.ui (chg !)
146 146 Foo
147 147 $ echo 'barfoo = !' >> $HGRCPATH
148 148
149 149 Check that extensions are loaded in phases:
150 150
151 151 $ cat > foo.py <<EOF
152 152 > from __future__ import print_function
153 153 > import os
154 154 > from mercurial import exthelper
155 155 > from mercurial.utils import procutil
156 156 >
157 157 > def write(msg):
158 158 > procutil.stdout.write(msg)
159 159 > procutil.stdout.flush()
160 160 >
161 161 > name = os.path.basename(__file__).rsplit('.', 1)[0]
162 162 > bytesname = name.encode('utf-8')
163 163 > write(b"1) %s imported\n" % bytesname)
164 164 > eh = exthelper.exthelper()
165 165 > @eh.uisetup
166 166 > def _uisetup(ui):
167 167 > write(b"2) %s uisetup\n" % bytesname)
168 168 > @eh.extsetup
169 169 > def _extsetup(ui):
170 170 > write(b"3) %s extsetup\n" % bytesname)
171 171 > @eh.uipopulate
172 172 > def _uipopulate(ui):
173 173 > write(b"4) %s uipopulate\n" % bytesname)
174 174 > @eh.reposetup
175 175 > def _reposetup(ui, repo):
176 176 > write(b"5) %s reposetup\n" % bytesname)
177 177 >
178 178 > extsetup = eh.finalextsetup
179 179 > reposetup = eh.finalreposetup
180 180 > uipopulate = eh.finaluipopulate
181 181 > uisetup = eh.finaluisetup
182 182 > revsetpredicate = eh.revsetpredicate
183 183 >
184 184 > # custom predicate to check registration of functions at loading
185 185 > from mercurial import (
186 186 > smartset,
187 187 > )
188 188 > @eh.revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
189 189 > def custompredicate(repo, subset, x):
190 190 > return smartset.baseset([r for r in subset if r in {0}])
191 191 > EOF
192 192 $ "$PYTHON" $TESTTMP/unflush.py foo.py
193 193
194 194 $ cp foo.py bar.py
195 195 $ echo 'foo = foo.py' >> $HGRCPATH
196 196 $ echo 'bar = bar.py' >> $HGRCPATH
197 197
198 198 Check normal command's load order of extensions and registration of functions
199 199
200 200 On chg server, extension should be first set up by the server. Then
201 201 object-level setup should follow in the worker process.
202 202
203 203 $ hg log -r "foo() and bar()" -q
204 204 1) foo imported
205 205 1) bar imported
206 206 2) foo uisetup
207 207 2) bar uisetup
208 208 3) foo extsetup
209 209 3) bar extsetup
210 210 4) foo uipopulate
211 211 4) bar uipopulate
212 212 4) foo uipopulate
213 213 4) bar uipopulate
214 214 4) foo uipopulate
215 215 4) bar uipopulate
216 216 5) foo reposetup
217 217 5) bar reposetup
218 218 4) foo uipopulate (chg !)
219 219 4) bar uipopulate (chg !)
220 220 4) foo uipopulate (chg !)
221 221 4) bar uipopulate (chg !)
222 222 4) foo uipopulate (chg !)
223 223 4) bar uipopulate (chg !)
224 224 4) foo uipopulate (chg !)
225 225 4) bar uipopulate (chg !)
226 226 4) foo uipopulate (chg !)
227 227 4) bar uipopulate (chg !)
228 228 5) foo reposetup (chg !)
229 229 5) bar reposetup (chg !)
230 230 0:c24b9ac61126
231 231
232 232 Check hgweb's load order of extensions and registration of functions
233 233
234 234 $ cat > hgweb.cgi <<EOF
235 235 > #!$PYTHON
236 236 > from mercurial import demandimport; demandimport.enable()
237 237 > from mercurial.hgweb import hgweb
238 238 > from mercurial.hgweb import wsgicgi
239 239 > application = hgweb(b'.', b'test repo')
240 240 > wsgicgi.launch(application)
241 241 > EOF
242 242 $ . "$TESTDIR/cgienv"
243 243
244 244 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
245 245 > | grep '^[0-9]) ' # ignores HTML output
246 246 1) foo imported
247 247 1) bar imported
248 248 2) foo uisetup
249 249 2) bar uisetup
250 250 3) foo extsetup
251 251 3) bar extsetup
252 252 4) foo uipopulate
253 253 4) bar uipopulate
254 254 4) foo uipopulate
255 255 4) bar uipopulate
256 256 5) foo reposetup
257 257 5) bar reposetup
258 258
259 259 (check that revset predicate foo() and bar() are available)
260 260
261 261 #if msys
262 262 $ PATH_INFO='//shortlog'
263 263 #else
264 264 $ PATH_INFO='/shortlog'
265 265 #endif
266 266 $ export PATH_INFO
267 267 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
268 268 > | grep '<a href="/rev/[0-9a-z]*">'
269 269 <a href="/rev/c24b9ac61126">add file</a>
270 270
271 271 $ echo 'foo = !' >> $HGRCPATH
272 272 $ echo 'bar = !' >> $HGRCPATH
273 273
274 274 Check "from __future__ import absolute_import" support for external libraries
275 275
276 276 (import-checker.py reports issues for some of heredoc python code
277 277 fragments below, because import-checker.py does not know test specific
278 278 package hierarchy. NO_CHECK_* should be used as a limit mark of
279 279 heredoc, in order to make import-checker.py ignore them. For
280 280 simplicity, all python code fragments below are generated with such
281 281 limit mark, regardless of importing module or not.)
282 282
283 283 #if windows
284 284 $ PATHSEP=";"
285 285 #else
286 286 $ PATHSEP=":"
287 287 #endif
288 288 $ export PATHSEP
289 289
290 290 $ mkdir $TESTTMP/libroot
291 291 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
292 292 $ mkdir $TESTTMP/libroot/mod
293 293 $ touch $TESTTMP/libroot/mod/__init__.py
294 294 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
295 295
296 296 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
297 297 > from __future__ import absolute_import, print_function
298 298 > import ambig # should load "libroot/ambig.py"
299 299 > s = ambig.s
300 300 > NO_CHECK_EOF
301 301 $ cat > loadabs.py <<NO_CHECK_EOF
302 302 > import mod.ambigabs as ambigabs
303 303 > def extsetup(ui):
304 304 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
305 305 > NO_CHECK_EOF
306 306 $ "$PYTHON" $TESTTMP/unflush.py loadabs.py
307 307 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
308 308 ambigabs.s=libroot/ambig.py
309 309 $TESTTMP/a
310 310
311 311 #if no-py3
312 312 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
313 313 > from __future__ import print_function
314 314 > import ambig # should load "libroot/mod/ambig.py"
315 315 > s = ambig.s
316 316 > NO_CHECK_EOF
317 317 $ cat > loadrel.py <<NO_CHECK_EOF
318 318 > import mod.ambigrel as ambigrel
319 319 > def extsetup(ui):
320 320 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
321 321 > NO_CHECK_EOF
322 322 $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
323 323 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
324 324 ambigrel.s=libroot/mod/ambig.py
325 325 $TESTTMP/a
326 326 #endif
327 327
328 328 Check absolute/relative import of extension specific modules
329 329
330 330 $ mkdir $TESTTMP/extroot
331 331 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
332 332 > s = b'this is extroot.bar'
333 333 > NO_CHECK_EOF
334 334 $ mkdir $TESTTMP/extroot/sub1
335 335 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
336 336 > s = b'this is extroot.sub1.__init__'
337 337 > NO_CHECK_EOF
338 338 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
339 339 > s = b'this is extroot.sub1.baz'
340 340 > NO_CHECK_EOF
341 341 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
342 342 > from __future__ import absolute_import
343 343 > s = b'this is extroot.__init__'
344 344 > from . import foo
345 345 > def extsetup(ui):
346 346 > ui.write(b'(extroot) ', foo.func(), b'\n')
347 347 > ui.flush()
348 348 > NO_CHECK_EOF
349 349
350 350 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
351 351 > # test absolute import
352 352 > buf = []
353 353 > def func():
354 354 > # "not locals" case
355 355 > import extroot.bar
356 356 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
357 357 > return b'\n(extroot) '.join(buf)
358 358 > # b"fromlist == ('*',)" case
359 359 > from extroot.bar import *
360 360 > buf.append(b'from extroot.bar import *: %s' % s)
361 361 > # "not fromlist" and "if '.' in name" case
362 362 > import extroot.sub1.baz
363 363 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
364 364 > # "not fromlist" and NOT "if '.' in name" case
365 365 > import extroot
366 366 > buf.append(b'import extroot: %s' % extroot.s)
367 367 > # NOT "not fromlist" and NOT "level != -1" case
368 368 > from extroot.bar import s
369 369 > buf.append(b'from extroot.bar import s: %s' % s)
370 370 > NO_CHECK_EOF
371 371 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
372 372 (extroot) from extroot.bar import *: this is extroot.bar
373 373 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
374 374 (extroot) import extroot: this is extroot.__init__
375 375 (extroot) from extroot.bar import s: this is extroot.bar
376 376 (extroot) import extroot.bar in func(): this is extroot.bar
377 377 $TESTTMP/a
378 378
379 379 #if no-py3
380 380 $ rm "$TESTTMP"/extroot/foo.*
381 381 $ rm -Rf "$TESTTMP/extroot/__pycache__"
382 382 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
383 383 > # test relative import
384 384 > buf = []
385 385 > def func():
386 386 > # "not locals" case
387 387 > import bar
388 388 > buf.append('import bar in func(): %s' % bar.s)
389 389 > return '\n(extroot) '.join(buf)
390 390 > # "fromlist == ('*',)" case
391 391 > from bar import *
392 392 > buf.append('from bar import *: %s' % s)
393 393 > # "not fromlist" and "if '.' in name" case
394 394 > import sub1.baz
395 395 > buf.append('import sub1.baz: %s' % sub1.baz.s)
396 396 > # "not fromlist" and NOT "if '.' in name" case
397 397 > import sub1
398 398 > buf.append('import sub1: %s' % sub1.s)
399 399 > # NOT "not fromlist" and NOT "level != -1" case
400 400 > from bar import s
401 401 > buf.append('from bar import s: %s' % s)
402 402 > NO_CHECK_EOF
403 403 $ hg --config extensions.extroot=$TESTTMP/extroot root
404 404 (extroot) from bar import *: this is extroot.bar
405 405 (extroot) import sub1.baz: this is extroot.sub1.baz
406 406 (extroot) import sub1: this is extroot.sub1.__init__
407 407 (extroot) from bar import s: this is extroot.bar
408 408 (extroot) import bar in func(): this is extroot.bar
409 409 $TESTTMP/a
410 410 #endif
411 411
412 412 #if demandimport
413 413
414 414 Examine whether module loading is delayed until actual referring, even
415 415 though module is imported with "absolute_import" feature.
416 416
417 417 Files below in each packages are used for described purpose:
418 418
419 419 - "called": examine whether "from MODULE import ATTR" works correctly
420 420 - "unused": examine whether loading is delayed correctly
421 421 - "used": examine whether "from PACKAGE import MODULE" works correctly
422 422
423 423 Package hierarchy is needed to examine whether demand importing works
424 424 as expected for "from SUB.PACK.AGE import MODULE".
425 425
426 426 Setup "external library" to be imported with "absolute_import"
427 427 feature.
428 428
429 429 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
430 430 $ touch $TESTTMP/extlibroot/__init__.py
431 431 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
432 432 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
433 433
434 434 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
435 435 > def func():
436 436 > return b"this is extlibroot.lsub1.lsub2.called.func()"
437 437 > NO_CHECK_EOF
438 438 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
439 439 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
440 440 > NO_CHECK_EOF
441 441 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
442 442 > detail = b"this is extlibroot.lsub1.lsub2.used"
443 443 > NO_CHECK_EOF
444 444
445 445 Setup sub-package of "external library", which causes instantiation of
446 446 demandmod in "recurse down the module chain" code path. Relative
447 447 importing with "absolute_import" feature isn't tested, because "level
448 448 >=1 " doesn't cause instantiation of demandmod.
449 449
450 450 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
451 451 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
452 452 > detail = b"this is extlibroot.recursedown.abs.used"
453 453 > NO_CHECK_EOF
454 454 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
455 455 > from __future__ import absolute_import
456 456 > from extlibroot.recursedown.abs.used import detail
457 457 > NO_CHECK_EOF
458 458
459 459 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
460 460 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
461 461 > detail = b"this is extlibroot.recursedown.legacy.used"
462 462 > NO_CHECK_EOF
463 463 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
464 464 > # legacy style (level == -1) import
465 465 > from extlibroot.recursedown.legacy.used import detail
466 466 > NO_CHECK_EOF
467 467
468 468 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
469 469 > from __future__ import absolute_import
470 470 > from extlibroot.recursedown.abs import detail as absdetail
471 471 > from .legacy import detail as legacydetail
472 472 > NO_CHECK_EOF
473 473
474 474 Setup package that re-exports an attribute of its submodule as the same
475 475 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
476 476 the submodule 'used' should be somehow accessible. (issue5617)
477 477
478 478 $ mkdir -p $TESTTMP/extlibroot/shadowing
479 479 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
480 480 > detail = b"this is extlibroot.shadowing.used"
481 481 > NO_CHECK_EOF
482 482 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
483 483 > from __future__ import absolute_import
484 484 > from extlibroot.shadowing.used import detail
485 485 > NO_CHECK_EOF
486 486 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
487 487 > from __future__ import absolute_import
488 488 > from .used import detail as used
489 489 > NO_CHECK_EOF
490 490
491 491 Setup extension local modules to be imported with "absolute_import"
492 492 feature.
493 493
494 494 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
495 495 $ touch $TESTTMP/absextroot/xsub1/__init__.py
496 496 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
497 497
498 498 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
499 499 > def func():
500 500 > return b"this is absextroot.xsub1.xsub2.called.func()"
501 501 > NO_CHECK_EOF
502 502 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
503 503 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
504 504 > NO_CHECK_EOF
505 505 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
506 506 > detail = b"this is absextroot.xsub1.xsub2.used"
507 507 > NO_CHECK_EOF
508 508
509 509 Setup extension local modules to examine whether demand importing
510 510 works as expected in "level > 1" case.
511 511
512 512 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
513 513 > detail = b"this is absextroot.relimportee"
514 514 > NO_CHECK_EOF
515 515 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
516 516 > from __future__ import absolute_import
517 517 > from mercurial import pycompat
518 518 > from ... import relimportee
519 519 > detail = b"this relimporter imports %r" % (
520 520 > pycompat.bytestr(relimportee.detail))
521 521 > NO_CHECK_EOF
522 522
523 523 Setup modules, which actually import extension local modules at
524 524 runtime.
525 525
526 526 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
527 527 > from __future__ import absolute_import
528 528 >
529 529 > # import extension local modules absolutely (level = 0)
530 530 > from absextroot.xsub1.xsub2 import used, unused
531 531 > from absextroot.xsub1.xsub2.called import func
532 532 >
533 533 > def getresult():
534 534 > result = []
535 535 > result.append(used.detail)
536 536 > result.append(func())
537 537 > return result
538 538 > NO_CHECK_EOF
539 539
540 540 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
541 541 > from __future__ import absolute_import
542 542 >
543 543 > # import extension local modules relatively (level == 1)
544 544 > from .xsub1.xsub2 import used, unused
545 545 > from .xsub1.xsub2.called import func
546 546 >
547 547 > # import a module, which implies "importing with level > 1"
548 548 > from .xsub1.xsub2 import relimporter
549 549 >
550 550 > def getresult():
551 551 > result = []
552 552 > result.append(used.detail)
553 553 > result.append(func())
554 554 > result.append(relimporter.detail)
555 555 > return result
556 556 > NO_CHECK_EOF
557 557
558 558 Setup main procedure of extension.
559 559
560 560 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
561 561 > from __future__ import absolute_import
562 562 > from mercurial import registrar
563 563 > cmdtable = {}
564 564 > command = registrar.command(cmdtable)
565 565 >
566 566 > # "absolute" and "relative" shouldn't be imported before actual
567 567 > # command execution, because (1) they import same modules, and (2)
568 568 > # preceding import (= instantiate "demandmod" object instead of
569 569 > # real "module" object) might hide problem of succeeding import.
570 570 >
571 571 > @command(b'showabsolute', [], norepo=True)
572 572 > def showabsolute(ui, *args, **opts):
573 573 > from absextroot import absolute
574 574 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
575 575 >
576 576 > @command(b'showrelative', [], norepo=True)
577 577 > def showrelative(ui, *args, **opts):
578 578 > from . import relative
579 579 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
580 580 >
581 581 > # import modules from external library
582 582 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
583 583 > from extlibroot.lsub1.lsub2.called import func as lfunc
584 584 > from extlibroot.recursedown import absdetail, legacydetail
585 585 > from extlibroot.shadowing import proxied
586 586 >
587 587 > def uisetup(ui):
588 588 > result = []
589 589 > result.append(lused.detail)
590 590 > result.append(lfunc())
591 591 > result.append(absdetail)
592 592 > result.append(legacydetail)
593 593 > result.append(proxied.detail)
594 594 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
595 595 > NO_CHECK_EOF
596 596
597 597 Examine module importing.
598 598
599 599 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
600 600 LIB: this is extlibroot.lsub1.lsub2.used
601 601 LIB: this is extlibroot.lsub1.lsub2.called.func()
602 602 LIB: this is extlibroot.recursedown.abs.used
603 603 LIB: this is extlibroot.recursedown.legacy.used
604 604 LIB: this is extlibroot.shadowing.used
605 605 ABS: this is absextroot.xsub1.xsub2.used
606 606 ABS: this is absextroot.xsub1.xsub2.called.func()
607 607
608 608 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
609 609 LIB: this is extlibroot.lsub1.lsub2.used
610 610 LIB: this is extlibroot.lsub1.lsub2.called.func()
611 611 LIB: this is extlibroot.recursedown.abs.used
612 612 LIB: this is extlibroot.recursedown.legacy.used
613 613 LIB: this is extlibroot.shadowing.used
614 614 REL: this is absextroot.xsub1.xsub2.used
615 615 REL: this is absextroot.xsub1.xsub2.called.func()
616 616 REL: this relimporter imports 'this is absextroot.relimportee'
617 617
618 618 Examine whether sub-module is imported relatively as expected.
619 619
620 620 See also issue5208 for detail about example case on Python 3.x.
621 621
622 622 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
623 623 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
624 624
625 625 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
626 626 > text = 'notexist.py at root is loaded unintentionally\n'
627 627 > NO_CHECK_EOF
628 628
629 629 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
630 630 > from mercurial import registrar
631 631 > cmdtable = {}
632 632 > command = registrar.command(cmdtable)
633 633 >
634 634 > # demand import avoids failure of importing notexist here, but only on
635 635 > # Python 2.
636 636 > import extlibroot.lsub1.lsub2.notexist
637 637 >
638 638 > @command(b'checkrelativity', [], norepo=True)
639 639 > def checkrelativity(ui, *args, **opts):
640 640 > try:
641 641 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
642 642 > return 1 # unintentional success
643 643 > except ImportError:
644 644 > pass # intentional failure
645 645 > NO_CHECK_EOF
646 646
647 647 Python 3's lazy importer verifies modules exist before returning the lazy
648 648 module stub. Our custom lazy importer for Python 2 always returns a stub.
649 649
650 650 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true
651 651 *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !)
652 652 hg: unknown command 'checkrelativity' (py3 !)
653 653 (use 'hg help' for a list of commands) (py3 !)
654 654
655 655 #endif
656 656
657 657 (Here, module importing tests are finished. Therefore, use other than
658 658 NO_CHECK_* limit mark for heredoc python files, in order to apply
659 659 import-checker.py or so on their contents)
660 660
661 661 Make sure a broken uisetup doesn't globally break hg:
662 662 $ cat > $TESTTMP/baduisetup.py <<EOF
663 663 > def uisetup(ui):
664 664 > 1 / 0
665 665 > EOF
666 666
667 667 Even though the extension fails during uisetup, hg is still basically usable:
668 668 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
669 669 Traceback (most recent call last):
670 670 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
671 671 uisetup(ui)
672 672 File "$TESTTMP/baduisetup.py", line 2, in uisetup
673 673 1 / 0
674 674 ZeroDivisionError: * by zero (glob)
675 675 *** failed to set up extension baduisetup: * by zero (glob)
676 676 Mercurial Distributed SCM (version *) (glob)
677 677 (see https://mercurial-scm.org for more information)
678 678
679 679 Copyright (C) 2005-* Matt Mackall and others (glob)
680 680 This is free software; see the source for copying conditions. There is NO
681 681 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
682 682
683 683 $ cd ..
684 684
685 685 hide outer repo
686 686 $ hg init
687 687
688 688 $ cat > empty.py <<EOF
689 689 > '''empty cmdtable
690 690 > '''
691 691 > cmdtable = {}
692 692 > EOF
693 693 $ emptypath=`pwd`/empty.py
694 694 $ echo "empty = $emptypath" >> $HGRCPATH
695 695 $ hg help empty
696 696 empty extension - empty cmdtable
697 697
698 698 no commands defined
699 699
700 700
701 701 $ echo 'empty = !' >> $HGRCPATH
702 702
703 703 $ cat > debugextension.py <<EOF
704 704 > '''only debugcommands
705 705 > '''
706 706 > from mercurial import registrar
707 707 > cmdtable = {}
708 708 > command = registrar.command(cmdtable)
709 709 > @command(b'debugfoobar', [], b'hg debugfoobar')
710 710 > def debugfoobar(ui, repo, *args, **opts):
711 711 > "yet another debug command"
712 712 > @command(b'foo', [], b'hg foo')
713 713 > def foo(ui, repo, *args, **opts):
714 714 > """yet another foo command
715 715 > This command has been DEPRECATED since forever.
716 716 > """
717 717 > EOF
718 718 $ debugpath=`pwd`/debugextension.py
719 719 $ echo "debugextension = $debugpath" >> $HGRCPATH
720 720
721 721 $ hg help debugextension
722 722 hg debugextensions
723 723
724 724 show information about active extensions
725 725
726 726 options:
727 727
728 728 -T --template TEMPLATE display with template
729 729
730 730 (some details hidden, use --verbose to show complete help)
731 731
732 732
733 733 $ hg --verbose help debugextension
734 734 hg debugextensions
735 735
736 736 show information about active extensions
737 737
738 738 options:
739 739
740 740 -T --template TEMPLATE display with template
741 741
742 742 global options ([+] can be repeated):
743 743
744 744 -R --repository REPO repository root directory or name of overlay bundle
745 745 file
746 746 --cwd DIR change working directory
747 747 -y --noninteractive do not prompt, automatically pick the first choice for
748 748 all prompts
749 749 -q --quiet suppress output
750 750 -v --verbose enable additional output
751 751 --color TYPE when to colorize (boolean, always, auto, never, or
752 752 debug)
753 753 --config CONFIG [+] set/override config option (use 'section.name=value')
754 754 --debug enable debugging output
755 755 --debugger start debugger
756 756 --encoding ENCODE set the charset encoding (default: ascii)
757 757 --encodingmode MODE set the charset encoding mode (default: strict)
758 758 --traceback always print a traceback on exception
759 759 --time time how long the command takes
760 760 --profile print command execution profile
761 761 --version output version information and exit
762 762 -h --help display help and exit
763 763 --hidden consider hidden changesets
764 764 --pager TYPE when to paginate (boolean, always, auto, or never)
765 765 (default: auto)
766 766
767 767
768 768
769 769
770 770
771 771
772 772 $ hg --debug help debugextension
773 773 hg debugextensions
774 774
775 775 show information about active extensions
776 776
777 777 options:
778 778
779 779 -T --template TEMPLATE display with template
780 780
781 781 global options ([+] can be repeated):
782 782
783 783 -R --repository REPO repository root directory or name of overlay bundle
784 784 file
785 785 --cwd DIR change working directory
786 786 -y --noninteractive do not prompt, automatically pick the first choice for
787 787 all prompts
788 788 -q --quiet suppress output
789 789 -v --verbose enable additional output
790 790 --color TYPE when to colorize (boolean, always, auto, never, or
791 791 debug)
792 792 --config CONFIG [+] set/override config option (use 'section.name=value')
793 793 --debug enable debugging output
794 794 --debugger start debugger
795 795 --encoding ENCODE set the charset encoding (default: ascii)
796 796 --encodingmode MODE set the charset encoding mode (default: strict)
797 797 --traceback always print a traceback on exception
798 798 --time time how long the command takes
799 799 --profile print command execution profile
800 800 --version output version information and exit
801 801 -h --help display help and exit
802 802 --hidden consider hidden changesets
803 803 --pager TYPE when to paginate (boolean, always, auto, or never)
804 804 (default: auto)
805 805
806 806
807 807
808 808
809 809
810 810 $ echo 'debugextension = !' >> $HGRCPATH
811 811
812 812 Asking for help about a deprecated extension should do something useful:
813 813
814 814 $ hg help glog
815 815 'glog' is provided by the following extension:
816 816
817 817 graphlog command to view revision graphs from a shell (DEPRECATED)
818 818
819 819 (use 'hg help extensions' for information on enabling extensions)
820 820
821 821 Extension module help vs command help:
822 822
823 823 $ echo 'extdiff =' >> $HGRCPATH
824 824 $ hg help extdiff
825 825 hg extdiff [OPT]... [FILE]...
826 826
827 827 use external program to diff repository (or selected files)
828 828
829 829 Show differences between revisions for the specified files, using an
830 830 external program. The default program used is diff, with default options
831 831 "-Npru".
832 832
833 833 To select a different program, use the -p/--program option. The program
834 834 will be passed the names of two directories to compare, unless the --per-
835 835 file option is specified (see below). To pass additional options to the
836 836 program, use -o/--option. These will be passed before the names of the
837 837 directories or files to compare.
838 838
839 When two revision arguments are given, then changes are shown between
840 those revisions. If only one revision is specified then that revision is
841 compared to the working directory, and, when no revisions are specified,
842 the working directory files are compared to its parent.
839 The --from, --to, and --change options work the same way they do for 'hg
840 diff'.
843 841
844 842 The --per-file option runs the external program repeatedly on each file to
845 843 diff, instead of once on two directories. By default, this happens one by
846 844 one, where the next file diff is open in the external program only once
847 845 the previous external program (for the previous file diff) has exited. If
848 846 the external program has a graphical interface, it can open all the file
849 847 diffs at once instead of one by one. See 'hg help -e extdiff' for
850 848 information about how to tell Mercurial that a given program has a
851 849 graphical interface.
852 850
853 851 The --confirm option will prompt the user before each invocation of the
854 852 external program. It is ignored if --per-file isn't specified.
855 853
856 854 (use 'hg help -e extdiff' to show help for the extdiff extension)
857 855
858 856 options ([+] can be repeated):
859 857
860 858 -p --program CMD comparison program to run
861 859 -o --option OPT [+] pass option to comparison program
862 -r --rev REV [+] revision
860 --from REV1 revision to diff from
861 --to REV2 revision to diff to
863 862 -c --change REV change made by revision
864 863 --per-file compare each file instead of revision snapshots
865 864 --confirm prompt user before each external program invocation
866 865 --patch compare patches for two revisions
867 866 -I --include PATTERN [+] include names matching the given patterns
868 867 -X --exclude PATTERN [+] exclude names matching the given patterns
869 868 -S --subrepos recurse into subrepositories
870 869
871 870 (some details hidden, use --verbose to show complete help)
872 871
873 872
874 873
875 874
876 875
877 876
878 877
879 878
880 879
881 880
882 881 $ hg help --extension extdiff
883 882 extdiff extension - command to allow external programs to compare revisions
884 883
885 884 The extdiff Mercurial extension allows you to use external programs to compare
886 885 revisions, or revision with working directory. The external diff programs are
887 886 called with a configurable set of options and two non-option arguments: paths
888 887 to directories containing snapshots of files to compare.
889 888
890 889 If there is more than one file being compared and the "child" revision is the
891 890 working directory, any modifications made in the external diff program will be
892 891 copied back to the working directory from the temporary directory.
893 892
894 893 The extdiff extension also allows you to configure new diff commands, so you
895 894 do not need to type 'hg extdiff -p kdiff3' always.
896 895
897 896 [extdiff]
898 897 # add new command that runs GNU diff(1) in 'context diff' mode
899 898 cdiff = gdiff -Nprc5
900 899 ## or the old way:
901 900 #cmd.cdiff = gdiff
902 901 #opts.cdiff = -Nprc5
903 902
904 903 # add new command called meld, runs meld (no need to name twice). If
905 904 # the meld executable is not available, the meld tool in [merge-tools]
906 905 # will be used, if available
907 906 meld =
908 907
909 908 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
910 909 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
911 910 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
912 911 # your .vimrc
913 912 vimdiff = gvim -f "+next" \
914 913 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
915 914
916 915 Tool arguments can include variables that are expanded at runtime:
917 916
918 917 $parent1, $plabel1 - filename, descriptive label of first parent
919 918 $child, $clabel - filename, descriptive label of child revision
920 919 $parent2, $plabel2 - filename, descriptive label of second parent
921 920 $root - repository root
922 921 $parent is an alias for $parent1.
923 922
924 923 The extdiff extension will look in your [diff-tools] and [merge-tools]
925 924 sections for diff tool arguments, when none are specified in [extdiff].
926 925
927 926 [extdiff]
928 927 kdiff3 =
929 928
930 929 [diff-tools]
931 930 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
932 931
933 932 If a program has a graphical interface, it might be interesting to tell
934 933 Mercurial about it. It will prevent the program from being mistakenly used in
935 934 a terminal-only environment (such as an SSH terminal session), and will make
936 935 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
937 936 (if you still want to open file diffs one by one, you can use the --confirm
938 937 option).
939 938
940 939 Declaring that a tool has a graphical interface can be done with the "gui"
941 940 flag next to where "diffargs" are specified:
942 941
943 942 [diff-tools]
944 943 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
945 944 kdiff3.gui = true
946 945
947 946 You can use -I/-X and list of file or directory names like normal 'hg diff'
948 947 command. The extdiff extension makes snapshots of only needed files, so
949 948 running the external diff program will actually be pretty fast (at least
950 949 faster than having to compare the entire tree).
951 950
952 951 list of commands:
953 952
954 953 extdiff use external program to diff repository (or selected files)
955 954
956 955 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
957 956
958 957
959 958
960 959
961 960
962 961
963 962
964 963
965 964
966 965
967 966
968 967
969 968
970 969
971 970
972 971
973 972 $ echo 'extdiff = !' >> $HGRCPATH
974 973
975 974 Test help topic with same name as extension
976 975
977 976 $ cat > multirevs.py <<EOF
978 977 > from mercurial import commands, registrar
979 978 > cmdtable = {}
980 979 > command = registrar.command(cmdtable)
981 980 > """multirevs extension
982 981 > Big multi-line module docstring."""
983 982 > @command(b'multirevs', [], b'ARG', norepo=True)
984 983 > def multirevs(ui, repo, arg, *args, **opts):
985 984 > """multirevs command"""
986 985 > EOF
987 986 $ echo "multirevs = multirevs.py" >> $HGRCPATH
988 987
989 988 $ hg help multirevs | tail
990 989 used):
991 990
992 991 hg update :@
993 992
994 993 - Show diff between tags 1.3 and 1.5 (this works because the first and the
995 994 last revisions of the revset are used):
996 995
997 996 hg diff -r 1.3::1.5
998 997
999 998 use 'hg help -c multirevs' to see help for the multirevs command
1000 999
1001 1000
1002 1001
1003 1002
1004 1003
1005 1004
1006 1005 $ hg help -c multirevs
1007 1006 hg multirevs ARG
1008 1007
1009 1008 multirevs command
1010 1009
1011 1010 (some details hidden, use --verbose to show complete help)
1012 1011
1013 1012
1014 1013
1015 1014 $ hg multirevs
1016 1015 hg multirevs: invalid arguments
1017 1016 hg multirevs ARG
1018 1017
1019 1018 multirevs command
1020 1019
1021 1020 (use 'hg multirevs -h' to show more help)
1022 1021 [255]
1023 1022
1024 1023
1025 1024
1026 1025 $ echo "multirevs = !" >> $HGRCPATH
1027 1026
1028 1027 Issue811: Problem loading extensions twice (by site and by user)
1029 1028
1030 1029 $ cat <<EOF >> $HGRCPATH
1031 1030 > mq =
1032 1031 > strip =
1033 1032 > hgext.mq =
1034 1033 > hgext/mq =
1035 1034 > EOF
1036 1035
1037 1036 Show extensions:
1038 1037 (note that mq force load strip, also checking it's not loaded twice)
1039 1038
1040 1039 #if no-extraextensions
1041 1040 $ hg debugextensions
1042 1041 mq
1043 1042 strip
1044 1043 #endif
1045 1044
1046 1045 For extensions, which name matches one of its commands, help
1047 1046 message should ask '-v -e' to get list of built-in aliases
1048 1047 along with extension help itself
1049 1048
1050 1049 $ mkdir $TESTTMP/d
1051 1050 $ cat > $TESTTMP/d/dodo.py <<EOF
1052 1051 > """
1053 1052 > This is an awesome 'dodo' extension. It does nothing and
1054 1053 > writes 'Foo foo'
1055 1054 > """
1056 1055 > from mercurial import commands, registrar
1057 1056 > cmdtable = {}
1058 1057 > command = registrar.command(cmdtable)
1059 1058 > @command(b'dodo', [], b'hg dodo')
1060 1059 > def dodo(ui, *args, **kwargs):
1061 1060 > """Does nothing"""
1062 1061 > ui.write(b"I do nothing. Yay\\n")
1063 1062 > @command(b'foofoo', [], b'hg foofoo')
1064 1063 > def foofoo(ui, *args, **kwargs):
1065 1064 > """Writes 'Foo foo'"""
1066 1065 > ui.write(b"Foo foo\\n")
1067 1066 > EOF
1068 1067 $ dodopath=$TESTTMP/d/dodo.py
1069 1068
1070 1069 $ echo "dodo = $dodopath" >> $HGRCPATH
1071 1070
1072 1071 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
1073 1072 $ hg help -e dodo
1074 1073 dodo extension -
1075 1074
1076 1075 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1077 1076
1078 1077 list of commands:
1079 1078
1080 1079 dodo Does nothing
1081 1080 foofoo Writes 'Foo foo'
1082 1081
1083 1082 (use 'hg help -v -e dodo' to show built-in aliases and global options)
1084 1083
1085 1084 Make sure that '-v -e' prints list of built-in aliases along with
1086 1085 extension help itself
1087 1086 $ hg help -v -e dodo
1088 1087 dodo extension -
1089 1088
1090 1089 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1091 1090
1092 1091 list of commands:
1093 1092
1094 1093 dodo Does nothing
1095 1094 foofoo Writes 'Foo foo'
1096 1095
1097 1096 global options ([+] can be repeated):
1098 1097
1099 1098 -R --repository REPO repository root directory or name of overlay bundle
1100 1099 file
1101 1100 --cwd DIR change working directory
1102 1101 -y --noninteractive do not prompt, automatically pick the first choice for
1103 1102 all prompts
1104 1103 -q --quiet suppress output
1105 1104 -v --verbose enable additional output
1106 1105 --color TYPE when to colorize (boolean, always, auto, never, or
1107 1106 debug)
1108 1107 --config CONFIG [+] set/override config option (use 'section.name=value')
1109 1108 --debug enable debugging output
1110 1109 --debugger start debugger
1111 1110 --encoding ENCODE set the charset encoding (default: ascii)
1112 1111 --encodingmode MODE set the charset encoding mode (default: strict)
1113 1112 --traceback always print a traceback on exception
1114 1113 --time time how long the command takes
1115 1114 --profile print command execution profile
1116 1115 --version output version information and exit
1117 1116 -h --help display help and exit
1118 1117 --hidden consider hidden changesets
1119 1118 --pager TYPE when to paginate (boolean, always, auto, or never)
1120 1119 (default: auto)
1121 1120
1122 1121 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1123 1122 $ hg help -v dodo
1124 1123 hg dodo
1125 1124
1126 1125 Does nothing
1127 1126
1128 1127 (use 'hg help -e dodo' to show help for the dodo extension)
1129 1128
1130 1129 options:
1131 1130
1132 1131 --mq operate on patch repository
1133 1132
1134 1133 global options ([+] can be repeated):
1135 1134
1136 1135 -R --repository REPO repository root directory or name of overlay bundle
1137 1136 file
1138 1137 --cwd DIR change working directory
1139 1138 -y --noninteractive do not prompt, automatically pick the first choice for
1140 1139 all prompts
1141 1140 -q --quiet suppress output
1142 1141 -v --verbose enable additional output
1143 1142 --color TYPE when to colorize (boolean, always, auto, never, or
1144 1143 debug)
1145 1144 --config CONFIG [+] set/override config option (use 'section.name=value')
1146 1145 --debug enable debugging output
1147 1146 --debugger start debugger
1148 1147 --encoding ENCODE set the charset encoding (default: ascii)
1149 1148 --encodingmode MODE set the charset encoding mode (default: strict)
1150 1149 --traceback always print a traceback on exception
1151 1150 --time time how long the command takes
1152 1151 --profile print command execution profile
1153 1152 --version output version information and exit
1154 1153 -h --help display help and exit
1155 1154 --hidden consider hidden changesets
1156 1155 --pager TYPE when to paginate (boolean, always, auto, or never)
1157 1156 (default: auto)
1158 1157
1159 1158 In case when extension name doesn't match any of its commands,
1160 1159 help message should ask for '-v' to get list of built-in aliases
1161 1160 along with extension help
1162 1161 $ cat > $TESTTMP/d/dudu.py <<EOF
1163 1162 > """
1164 1163 > This is an awesome 'dudu' extension. It does something and
1165 1164 > also writes 'Beep beep'
1166 1165 > """
1167 1166 > from mercurial import commands, registrar
1168 1167 > cmdtable = {}
1169 1168 > command = registrar.command(cmdtable)
1170 1169 > @command(b'something', [], b'hg something')
1171 1170 > def something(ui, *args, **kwargs):
1172 1171 > """Does something"""
1173 1172 > ui.write(b"I do something. Yaaay\\n")
1174 1173 > @command(b'beep', [], b'hg beep')
1175 1174 > def beep(ui, *args, **kwargs):
1176 1175 > """Writes 'Beep beep'"""
1177 1176 > ui.write(b"Beep beep\\n")
1178 1177 > EOF
1179 1178 $ dudupath=$TESTTMP/d/dudu.py
1180 1179
1181 1180 $ echo "dudu = $dudupath" >> $HGRCPATH
1182 1181
1183 1182 $ hg help -e dudu
1184 1183 dudu extension -
1185 1184
1186 1185 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1187 1186 beep'
1188 1187
1189 1188 list of commands:
1190 1189
1191 1190 beep Writes 'Beep beep'
1192 1191 something Does something
1193 1192
1194 1193 (use 'hg help -v dudu' to show built-in aliases and global options)
1195 1194
1196 1195 In case when extension name doesn't match any of its commands,
1197 1196 help options '-v' and '-v -e' should be equivalent
1198 1197 $ hg help -v dudu
1199 1198 dudu extension -
1200 1199
1201 1200 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1202 1201 beep'
1203 1202
1204 1203 list of commands:
1205 1204
1206 1205 beep Writes 'Beep beep'
1207 1206 something Does something
1208 1207
1209 1208 global options ([+] can be repeated):
1210 1209
1211 1210 -R --repository REPO repository root directory or name of overlay bundle
1212 1211 file
1213 1212 --cwd DIR change working directory
1214 1213 -y --noninteractive do not prompt, automatically pick the first choice for
1215 1214 all prompts
1216 1215 -q --quiet suppress output
1217 1216 -v --verbose enable additional output
1218 1217 --color TYPE when to colorize (boolean, always, auto, never, or
1219 1218 debug)
1220 1219 --config CONFIG [+] set/override config option (use 'section.name=value')
1221 1220 --debug enable debugging output
1222 1221 --debugger start debugger
1223 1222 --encoding ENCODE set the charset encoding (default: ascii)
1224 1223 --encodingmode MODE set the charset encoding mode (default: strict)
1225 1224 --traceback always print a traceback on exception
1226 1225 --time time how long the command takes
1227 1226 --profile print command execution profile
1228 1227 --version output version information and exit
1229 1228 -h --help display help and exit
1230 1229 --hidden consider hidden changesets
1231 1230 --pager TYPE when to paginate (boolean, always, auto, or never)
1232 1231 (default: auto)
1233 1232
1234 1233 $ hg help -v -e dudu
1235 1234 dudu extension -
1236 1235
1237 1236 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1238 1237 beep'
1239 1238
1240 1239 list of commands:
1241 1240
1242 1241 beep Writes 'Beep beep'
1243 1242 something Does something
1244 1243
1245 1244 global options ([+] can be repeated):
1246 1245
1247 1246 -R --repository REPO repository root directory or name of overlay bundle
1248 1247 file
1249 1248 --cwd DIR change working directory
1250 1249 -y --noninteractive do not prompt, automatically pick the first choice for
1251 1250 all prompts
1252 1251 -q --quiet suppress output
1253 1252 -v --verbose enable additional output
1254 1253 --color TYPE when to colorize (boolean, always, auto, never, or
1255 1254 debug)
1256 1255 --config CONFIG [+] set/override config option (use 'section.name=value')
1257 1256 --debug enable debugging output
1258 1257 --debugger start debugger
1259 1258 --encoding ENCODE set the charset encoding (default: ascii)
1260 1259 --encodingmode MODE set the charset encoding mode (default: strict)
1261 1260 --traceback always print a traceback on exception
1262 1261 --time time how long the command takes
1263 1262 --profile print command execution profile
1264 1263 --version output version information and exit
1265 1264 -h --help display help and exit
1266 1265 --hidden consider hidden changesets
1267 1266 --pager TYPE when to paginate (boolean, always, auto, or never)
1268 1267 (default: auto)
1269 1268
1270 1269 Disabled extension commands:
1271 1270
1272 1271 $ ORGHGRCPATH=$HGRCPATH
1273 1272 $ HGRCPATH=
1274 1273 $ export HGRCPATH
1275 1274 $ hg help email
1276 1275 'email' is provided by the following extension:
1277 1276
1278 1277 patchbomb command to send changesets as (a series of) patch emails
1279 1278
1280 1279 (use 'hg help extensions' for information on enabling extensions)
1281 1280
1282 1281
1283 1282 $ hg qdel
1284 1283 hg: unknown command 'qdel'
1285 1284 'qdelete' is provided by the following extension:
1286 1285
1287 1286 mq manage a stack of patches
1288 1287
1289 1288 (use 'hg help extensions' for information on enabling extensions)
1290 1289 [255]
1291 1290
1292 1291
1293 1292 $ hg churn
1294 1293 hg: unknown command 'churn'
1295 1294 'churn' is provided by the following extension:
1296 1295
1297 1296 churn command to display statistics about repository history
1298 1297
1299 1298 (use 'hg help extensions' for information on enabling extensions)
1300 1299 [255]
1301 1300
1302 1301
1303 1302
1304 1303 Disabled extensions:
1305 1304
1306 1305 $ hg help churn
1307 1306 churn extension - command to display statistics about repository history
1308 1307
1309 1308 (use 'hg help extensions' for information on enabling extensions)
1310 1309
1311 1310 $ hg help patchbomb
1312 1311 patchbomb extension - command to send changesets as (a series of) patch emails
1313 1312
1314 1313 The series is started off with a "[PATCH 0 of N]" introduction, which
1315 1314 describes the series as a whole.
1316 1315
1317 1316 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1318 1317 line of the changeset description as the subject text. The message contains
1319 1318 two or three body parts:
1320 1319
1321 1320 - The changeset description.
1322 1321 - [Optional] The result of running diffstat on the patch.
1323 1322 - The patch itself, as generated by 'hg export'.
1324 1323
1325 1324 Each message refers to the first in the series using the In-Reply-To and
1326 1325 References headers, so they will show up as a sequence in threaded mail and
1327 1326 news readers, and in mail archives.
1328 1327
1329 1328 To configure other defaults, add a section like this to your configuration
1330 1329 file:
1331 1330
1332 1331 [email]
1333 1332 from = My Name <my@email>
1334 1333 to = recipient1, recipient2, ...
1335 1334 cc = cc1, cc2, ...
1336 1335 bcc = bcc1, bcc2, ...
1337 1336 reply-to = address1, address2, ...
1338 1337
1339 1338 Use "[patchbomb]" as configuration section name if you need to override global
1340 1339 "[email]" address settings.
1341 1340
1342 1341 Then you can use the 'hg email' command to mail a series of changesets as a
1343 1342 patchbomb.
1344 1343
1345 1344 You can also either configure the method option in the email section to be a
1346 1345 sendmail compatible mailer or fill out the [smtp] section so that the
1347 1346 patchbomb extension can automatically send patchbombs directly from the
1348 1347 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1349 1348
1350 1349 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1351 1350 supply one via configuration or the command line. You can override this to
1352 1351 never prompt by configuring an empty value:
1353 1352
1354 1353 [email]
1355 1354 cc =
1356 1355
1357 1356 You can control the default inclusion of an introduction message with the
1358 1357 "patchbomb.intro" configuration option. The configuration is always
1359 1358 overwritten by command line flags like --intro and --desc:
1360 1359
1361 1360 [patchbomb]
1362 1361 intro=auto # include introduction message if more than 1 patch (default)
1363 1362 intro=never # never include an introduction message
1364 1363 intro=always # always include an introduction message
1365 1364
1366 1365 You can specify a template for flags to be added in subject prefixes. Flags
1367 1366 specified by --flag option are exported as "{flags}" keyword:
1368 1367
1369 1368 [patchbomb]
1370 1369 flagtemplate = "{separate(' ',
1371 1370 ifeq(branch, 'default', '', branch|upper),
1372 1371 flags)}"
1373 1372
1374 1373 You can set patchbomb to always ask for confirmation by setting
1375 1374 "patchbomb.confirm" to true.
1376 1375
1377 1376 (use 'hg help extensions' for information on enabling extensions)
1378 1377
1379 1378
1380 1379 Broken disabled extension and command:
1381 1380
1382 1381 $ mkdir hgext
1383 1382 $ echo > hgext/__init__.py
1384 1383 $ cat > hgext/broken.py <<NO_CHECK_EOF
1385 1384 > "broken extension'
1386 1385 > NO_CHECK_EOF
1387 1386 $ cat > path.py <<EOF
1388 1387 > import os
1389 1388 > import sys
1390 1389 > sys.path.insert(0, os.environ['HGEXTPATH'])
1391 1390 > EOF
1392 1391 $ HGEXTPATH=`pwd`
1393 1392 $ export HGEXTPATH
1394 1393
1395 1394 $ hg --config extensions.path=./path.py help broken
1396 1395 broken extension - (no help text available)
1397 1396
1398 1397 (use 'hg help extensions' for information on enabling extensions)
1399 1398
1400 1399
1401 1400 $ cat > hgext/forest.py <<EOF
1402 1401 > cmdtable = None
1403 1402 > @command()
1404 1403 > def f():
1405 1404 > pass
1406 1405 > @command(123)
1407 1406 > def g():
1408 1407 > pass
1409 1408 > EOF
1410 1409 $ hg --config extensions.path=./path.py help foo
1411 1410 abort: no such help topic: foo
1412 1411 (try 'hg help --keyword foo')
1413 1412 [255]
1414 1413
1415 1414 $ cat > throw.py <<EOF
1416 1415 > from mercurial import commands, registrar, util
1417 1416 > cmdtable = {}
1418 1417 > command = registrar.command(cmdtable)
1419 1418 > class Bogon(Exception): pass
1420 1419 > # NB: version should be bytes; simulating extension not ported to py3
1421 1420 > __version__ = '1.0.0'
1422 1421 > @command(b'throw', [], b'hg throw', norepo=True)
1423 1422 > def throw(ui, **opts):
1424 1423 > """throws an exception"""
1425 1424 > raise Bogon()
1426 1425 > EOF
1427 1426
1428 1427 Test extension without proper byteification of key attributes doesn't crash when
1429 1428 accessed.
1430 1429
1431 1430 $ hg version -v --config extensions.throw=throw.py | grep '^ '
1432 1431 throw external 1.0.0
1433 1432
1434 1433 No declared supported version, extension complains:
1435 1434 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1436 1435 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1437 1436 ** which supports versions unknown of Mercurial.
1438 1437 ** Please disable "throw" and try your action again.
1439 1438 ** If that fixes the bug please report it to the extension author.
1440 1439 ** Python * (glob)
1441 1440 ** Mercurial Distributed SCM * (glob)
1442 1441 ** Extensions loaded: throw 1.0.0
1443 1442
1444 1443 empty declaration of supported version, extension complains (but doesn't choke if
1445 1444 the value is improperly a str instead of bytes):
1446 1445 $ echo "testedwith = ''" >> throw.py
1447 1446 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1448 1447 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1449 1448 ** which supports versions unknown of Mercurial.
1450 1449 ** Please disable "throw" and try your action again.
1451 1450 ** If that fixes the bug please report it to the extension author.
1452 1451 ** Python * (glob)
1453 1452 ** Mercurial Distributed SCM (*) (glob)
1454 1453 ** Extensions loaded: throw 1.0.0
1455 1454
1456 1455 If the extension specifies a buglink, show that (but don't choke if the value is
1457 1456 improperly a str instead of bytes):
1458 1457 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1459 1458 $ rm -f throw.pyc throw.pyo
1460 1459 $ rm -Rf __pycache__
1461 1460 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1462 1461 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1463 1462 ** which supports versions unknown of Mercurial.
1464 1463 ** Please disable "throw" and try your action again.
1465 1464 ** If that fixes the bug please report it to http://example.com/bts
1466 1465 ** Python * (glob)
1467 1466 ** Mercurial Distributed SCM (*) (glob)
1468 1467 ** Extensions loaded: throw 1.0.0
1469 1468
1470 1469 If the extensions declare outdated versions, accuse the older extension first:
1471 1470 $ echo "from mercurial import util" >> older.py
1472 1471 $ echo "util.version = lambda:b'2.2'" >> older.py
1473 1472 $ echo "testedwith = b'1.9.3'" >> older.py
1474 1473 $ echo "testedwith = b'2.1.1'" >> throw.py
1475 1474 $ rm -f throw.pyc throw.pyo
1476 1475 $ rm -Rf __pycache__
1477 1476 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1478 1477 > throw 2>&1 | egrep '^\*\*'
1479 1478 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1480 1479 ** which supports versions 1.9 of Mercurial.
1481 1480 ** Please disable "older" and try your action again.
1482 1481 ** If that fixes the bug please report it to the extension author.
1483 1482 ** Python * (glob)
1484 1483 ** Mercurial Distributed SCM (version 2.2)
1485 1484 ** Extensions loaded: older, throw 1.0.0
1486 1485
1487 1486 One extension only tested with older, one only with newer versions:
1488 1487 $ echo "util.version = lambda:b'2.1'" >> older.py
1489 1488 $ rm -f older.pyc older.pyo
1490 1489 $ rm -Rf __pycache__
1491 1490 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1492 1491 > throw 2>&1 | egrep '^\*\*'
1493 1492 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1494 1493 ** which supports versions 1.9 of Mercurial.
1495 1494 ** Please disable "older" and try your action again.
1496 1495 ** If that fixes the bug please report it to the extension author.
1497 1496 ** Python * (glob)
1498 1497 ** Mercurial Distributed SCM (version 2.1)
1499 1498 ** Extensions loaded: older, throw 1.0.0
1500 1499
1501 1500 Older extension is tested with current version, the other only with newer:
1502 1501 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1503 1502 $ rm -f older.pyc older.pyo
1504 1503 $ rm -Rf __pycache__
1505 1504 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1506 1505 > throw 2>&1 | egrep '^\*\*'
1507 1506 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1508 1507 ** which supports versions 2.1 of Mercurial.
1509 1508 ** Please disable "throw" and try your action again.
1510 1509 ** If that fixes the bug please report it to http://example.com/bts
1511 1510 ** Python * (glob)
1512 1511 ** Mercurial Distributed SCM (version 1.9.3)
1513 1512 ** Extensions loaded: older, throw 1.0.0
1514 1513
1515 1514 Ability to point to a different point
1516 1515 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1517 1516 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1518 1517 ** unknown exception encountered, please report by visiting
1519 1518 ** Your Local Goat Lenders
1520 1519 ** Python * (glob)
1521 1520 ** Mercurial Distributed SCM (*) (glob)
1522 1521 ** Extensions loaded: older, throw 1.0.0
1523 1522
1524 1523 Declare the version as supporting this hg version, show regular bts link:
1525 1524 $ hgver=`hg debuginstall -T '{hgver}'`
1526 1525 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1527 1526 $ if [ -z "$hgver" ]; then
1528 1527 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1529 1528 > fi
1530 1529 $ rm -f throw.pyc throw.pyo
1531 1530 $ rm -Rf __pycache__
1532 1531 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1533 1532 ** unknown exception encountered, please report by visiting
1534 1533 ** https://mercurial-scm.org/wiki/BugTracker
1535 1534 ** Python * (glob)
1536 1535 ** Mercurial Distributed SCM (*) (glob)
1537 1536 ** Extensions loaded: throw 1.0.0
1538 1537
1539 1538 Patch version is ignored during compatibility check
1540 1539 $ echo "testedwith = b'3.2'" >> throw.py
1541 1540 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1542 1541 $ rm -f throw.pyc throw.pyo
1543 1542 $ rm -Rf __pycache__
1544 1543 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1545 1544 ** unknown exception encountered, please report by visiting
1546 1545 ** https://mercurial-scm.org/wiki/BugTracker
1547 1546 ** Python * (glob)
1548 1547 ** Mercurial Distributed SCM (*) (glob)
1549 1548 ** Extensions loaded: throw 1.0.0
1550 1549
1551 1550 Test version number support in 'hg version':
1552 1551 $ echo '__version__ = (1, 2, 3)' >> throw.py
1553 1552 $ rm -f throw.pyc throw.pyo
1554 1553 $ rm -Rf __pycache__
1555 1554 $ hg version -v
1556 1555 Mercurial Distributed SCM (version *) (glob)
1557 1556 (see https://mercurial-scm.org for more information)
1558 1557
1559 1558 Copyright (C) 2005-* Matt Mackall and others (glob)
1560 1559 This is free software; see the source for copying conditions. There is NO
1561 1560 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1562 1561
1563 1562 Enabled extensions:
1564 1563
1565 1564
1566 1565 $ hg version -v --config extensions.throw=throw.py
1567 1566 Mercurial Distributed SCM (version *) (glob)
1568 1567 (see https://mercurial-scm.org for more information)
1569 1568
1570 1569 Copyright (C) 2005-* Matt Mackall and others (glob)
1571 1570 This is free software; see the source for copying conditions. There is NO
1572 1571 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1573 1572
1574 1573 Enabled extensions:
1575 1574
1576 1575 throw external 1.2.3
1577 1576 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1578 1577 $ rm -f throw.pyc throw.pyo
1579 1578 $ rm -Rf __pycache__
1580 1579 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1581 1580 Mercurial Distributed SCM (version *) (glob)
1582 1581 (see https://mercurial-scm.org for more information)
1583 1582
1584 1583 Copyright (C) 2005-* Matt Mackall and others (glob)
1585 1584 This is free software; see the source for copying conditions. There is NO
1586 1585 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1587 1586
1588 1587 Enabled extensions:
1589 1588
1590 1589 strip internal
1591 1590 throw external 1.twentythree
1592 1591
1593 1592 $ hg version -q --config extensions.throw=throw.py
1594 1593 Mercurial Distributed SCM (version *) (glob)
1595 1594
1596 1595 Test template output:
1597 1596
1598 1597 $ hg version --config extensions.strip= -T'{extensions}'
1599 1598 strip
1600 1599
1601 1600 Test JSON output of version:
1602 1601
1603 1602 $ hg version -Tjson
1604 1603 [
1605 1604 {
1606 1605 "extensions": [],
1607 1606 "ver": "*" (glob)
1608 1607 }
1609 1608 ]
1610 1609
1611 1610 $ hg version --config extensions.throw=throw.py -Tjson
1612 1611 [
1613 1612 {
1614 1613 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1615 1614 "ver": "3.2.2"
1616 1615 }
1617 1616 ]
1618 1617
1619 1618 $ hg version --config extensions.strip= -Tjson
1620 1619 [
1621 1620 {
1622 1621 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1623 1622 "ver": "*" (glob)
1624 1623 }
1625 1624 ]
1626 1625
1627 1626 Test template output of version:
1628 1627
1629 1628 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1630 1629 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1631 1630 strip (internal)
1632 1631 throw 1.twentythree (external)
1633 1632
1634 1633 Refuse to load extensions with minimum version requirements
1635 1634
1636 1635 $ cat > minversion1.py << EOF
1637 1636 > from mercurial import util
1638 1637 > util.version = lambda: b'3.5.2'
1639 1638 > minimumhgversion = b'3.6'
1640 1639 > EOF
1641 1640 $ hg --config extensions.minversion=minversion1.py version
1642 1641 (third party extension minversion requires version 3.6 or newer of Mercurial (current: 3.5.2); disabling)
1643 1642 Mercurial Distributed SCM (version 3.5.2)
1644 1643 (see https://mercurial-scm.org for more information)
1645 1644
1646 1645 Copyright (C) 2005-* Matt Mackall and others (glob)
1647 1646 This is free software; see the source for copying conditions. There is NO
1648 1647 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1649 1648
1650 1649 $ cat > minversion2.py << EOF
1651 1650 > from mercurial import util
1652 1651 > util.version = lambda: b'3.6'
1653 1652 > minimumhgversion = b'3.7'
1654 1653 > EOF
1655 1654 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1656 1655 (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
1657 1656
1658 1657 Can load version that is only off by point release
1659 1658
1660 1659 $ cat > minversion2.py << EOF
1661 1660 > from mercurial import util
1662 1661 > util.version = lambda: b'3.6.1'
1663 1662 > minimumhgversion = b'3.6'
1664 1663 > EOF
1665 1664 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1666 1665 [1]
1667 1666
1668 1667 Can load minimum version identical to current
1669 1668
1670 1669 $ cat > minversion3.py << EOF
1671 1670 > from mercurial import util
1672 1671 > util.version = lambda: b'3.5'
1673 1672 > minimumhgversion = b'3.5'
1674 1673 > EOF
1675 1674 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1676 1675 [1]
1677 1676
1678 1677 Restore HGRCPATH
1679 1678
1680 1679 $ HGRCPATH=$ORGHGRCPATH
1681 1680 $ export HGRCPATH
1682 1681
1683 1682 Commands handling multiple repositories at a time should invoke only
1684 1683 "reposetup()" of extensions enabling in the target repository.
1685 1684
1686 1685 $ mkdir reposetup-test
1687 1686 $ cd reposetup-test
1688 1687
1689 1688 $ cat > $TESTTMP/reposetuptest.py <<EOF
1690 1689 > from mercurial import extensions
1691 1690 > def reposetup(ui, repo):
1692 1691 > ui.write(b'reposetup() for %s\n' % (repo.root))
1693 1692 > ui.flush()
1694 1693 > EOF
1695 1694 $ hg init src
1696 1695 $ echo a > src/a
1697 1696 $ hg -R src commit -Am '#0 at src/a'
1698 1697 adding a
1699 1698 $ echo '[extensions]' >> src/.hg/hgrc
1700 1699 $ echo '# enable extension locally' >> src/.hg/hgrc
1701 1700 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1702 1701 $ hg -R src status
1703 1702 reposetup() for $TESTTMP/reposetup-test/src
1704 1703 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1705 1704
1706 1705 #if no-extraextensions
1707 1706 $ hg --cwd src debugextensions
1708 1707 reposetup() for $TESTTMP/reposetup-test/src
1709 1708 dodo (untested!)
1710 1709 dudu (untested!)
1711 1710 mq
1712 1711 reposetuptest (untested!)
1713 1712 strip
1714 1713 #endif
1715 1714
1716 1715 $ hg clone -U src clone-dst1
1717 1716 reposetup() for $TESTTMP/reposetup-test/src
1718 1717 $ hg init push-dst1
1719 1718 $ hg -q -R src push push-dst1
1720 1719 reposetup() for $TESTTMP/reposetup-test/src
1721 1720 $ hg init pull-src1
1722 1721 $ hg -q -R pull-src1 pull src
1723 1722 reposetup() for $TESTTMP/reposetup-test/src
1724 1723
1725 1724 $ cat <<EOF >> $HGRCPATH
1726 1725 > [extensions]
1727 1726 > # disable extension globally and explicitly
1728 1727 > reposetuptest = !
1729 1728 > EOF
1730 1729 $ hg clone -U src clone-dst2
1731 1730 reposetup() for $TESTTMP/reposetup-test/src
1732 1731 $ hg init push-dst2
1733 1732 $ hg -q -R src push push-dst2
1734 1733 reposetup() for $TESTTMP/reposetup-test/src
1735 1734 $ hg init pull-src2
1736 1735 $ hg -q -R pull-src2 pull src
1737 1736 reposetup() for $TESTTMP/reposetup-test/src
1738 1737
1739 1738 $ cat <<EOF >> $HGRCPATH
1740 1739 > [extensions]
1741 1740 > # enable extension globally
1742 1741 > reposetuptest = $TESTTMP/reposetuptest.py
1743 1742 > EOF
1744 1743 $ hg clone -U src clone-dst3
1745 1744 reposetup() for $TESTTMP/reposetup-test/src
1746 1745 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1747 1746 $ hg init push-dst3
1748 1747 reposetup() for $TESTTMP/reposetup-test/push-dst3
1749 1748 $ hg -q -R src push push-dst3
1750 1749 reposetup() for $TESTTMP/reposetup-test/src
1751 1750 reposetup() for $TESTTMP/reposetup-test/push-dst3
1752 1751 $ hg init pull-src3
1753 1752 reposetup() for $TESTTMP/reposetup-test/pull-src3
1754 1753 $ hg -q -R pull-src3 pull src
1755 1754 reposetup() for $TESTTMP/reposetup-test/pull-src3
1756 1755 reposetup() for $TESTTMP/reposetup-test/src
1757 1756
1758 1757 $ echo '[extensions]' >> src/.hg/hgrc
1759 1758 $ echo '# disable extension locally' >> src/.hg/hgrc
1760 1759 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1761 1760 $ hg clone -U src clone-dst4
1762 1761 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1763 1762 $ hg init push-dst4
1764 1763 reposetup() for $TESTTMP/reposetup-test/push-dst4
1765 1764 $ hg -q -R src push push-dst4
1766 1765 reposetup() for $TESTTMP/reposetup-test/push-dst4
1767 1766 $ hg init pull-src4
1768 1767 reposetup() for $TESTTMP/reposetup-test/pull-src4
1769 1768 $ hg -q -R pull-src4 pull src
1770 1769 reposetup() for $TESTTMP/reposetup-test/pull-src4
1771 1770
1772 1771 disabling in command line overlays with all configuration
1773 1772 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1774 1773 $ hg --config extensions.reposetuptest=! init push-dst5
1775 1774 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1776 1775 $ hg --config extensions.reposetuptest=! init pull-src5
1777 1776 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1778 1777
1779 1778 $ cat <<EOF >> $HGRCPATH
1780 1779 > [extensions]
1781 1780 > # disable extension globally and explicitly
1782 1781 > reposetuptest = !
1783 1782 > EOF
1784 1783 $ hg init parent
1785 1784 $ hg init parent/sub1
1786 1785 $ echo 1 > parent/sub1/1
1787 1786 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1788 1787 adding 1
1789 1788 $ hg init parent/sub2
1790 1789 $ hg init parent/sub2/sub21
1791 1790 $ echo 21 > parent/sub2/sub21/21
1792 1791 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1793 1792 adding 21
1794 1793 $ cat > parent/sub2/.hgsub <<EOF
1795 1794 > sub21 = sub21
1796 1795 > EOF
1797 1796 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1798 1797 adding .hgsub
1799 1798 $ hg init parent/sub3
1800 1799 $ echo 3 > parent/sub3/3
1801 1800 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1802 1801 adding 3
1803 1802 $ cat > parent/.hgsub <<EOF
1804 1803 > sub1 = sub1
1805 1804 > sub2 = sub2
1806 1805 > sub3 = sub3
1807 1806 > EOF
1808 1807 $ hg -R parent commit -Am '#0 at parent'
1809 1808 adding .hgsub
1810 1809 $ echo '[extensions]' >> parent/.hg/hgrc
1811 1810 $ echo '# enable extension locally' >> parent/.hg/hgrc
1812 1811 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1813 1812 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1814 1813 $ hg -R parent status -S -A
1815 1814 reposetup() for $TESTTMP/reposetup-test/parent
1816 1815 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1817 1816 C .hgsub
1818 1817 C .hgsubstate
1819 1818 C sub1/1
1820 1819 C sub2/.hgsub
1821 1820 C sub2/.hgsubstate
1822 1821 C sub2/sub21/21
1823 1822 C sub3/3
1824 1823
1825 1824 $ cd ..
1826 1825
1827 1826 Prohibit registration of commands that don't use @command (issue5137)
1828 1827
1829 1828 $ hg init deprecated
1830 1829 $ cd deprecated
1831 1830
1832 1831 $ cat <<EOF > deprecatedcmd.py
1833 1832 > def deprecatedcmd(repo, ui):
1834 1833 > pass
1835 1834 > cmdtable = {
1836 1835 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1837 1836 > }
1838 1837 > EOF
1839 1838 $ cat <<EOF > .hg/hgrc
1840 1839 > [extensions]
1841 1840 > deprecatedcmd = `pwd`/deprecatedcmd.py
1842 1841 > mq = !
1843 1842 > hgext.mq = !
1844 1843 > hgext/mq = !
1845 1844 > EOF
1846 1845
1847 1846 $ hg deprecatedcmd > /dev/null
1848 1847 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1849 1848 *** (use @command decorator to register 'deprecatedcmd')
1850 1849 hg: unknown command 'deprecatedcmd'
1851 1850 (use 'hg help' for a list of commands)
1852 1851 [255]
1853 1852
1854 1853 the extension shouldn't be loaded at all so the mq works:
1855 1854
1856 1855 $ hg qseries --config extensions.mq= > /dev/null
1857 1856 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1858 1857 *** (use @command decorator to register 'deprecatedcmd')
1859 1858
1860 1859 $ cd ..
1861 1860
1862 1861 Test synopsis and docstring extending
1863 1862
1864 1863 $ hg init exthelp
1865 1864 $ cat > exthelp.py <<EOF
1866 1865 > from mercurial import commands, extensions
1867 1866 > def exbookmarks(orig, *args, **opts):
1868 1867 > return orig(*args, **opts)
1869 1868 > def uisetup(ui):
1870 1869 > synopsis = b' GREPME [--foo] [-x]'
1871 1870 > docstring = '''
1872 1871 > GREPME make sure that this is in the help!
1873 1872 > '''
1874 1873 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1875 1874 > synopsis, docstring)
1876 1875 > EOF
1877 1876 $ abspath=`pwd`/exthelp.py
1878 1877 $ echo '[extensions]' >> $HGRCPATH
1879 1878 $ echo "exthelp = $abspath" >> $HGRCPATH
1880 1879 $ cd exthelp
1881 1880 $ hg help bookmarks | grep GREPME
1882 1881 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1883 1882 GREPME make sure that this is in the help!
1884 1883 $ cd ..
1885 1884
1886 1885 Prohibit the use of unicode strings as the default value of options
1887 1886
1888 1887 $ hg init $TESTTMP/opt-unicode-default
1889 1888
1890 1889 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1891 1890 > from __future__ import print_function
1892 1891 > from mercurial import registrar
1893 1892 > cmdtable = {}
1894 1893 > command = registrar.command(cmdtable)
1895 1894 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1896 1895 > def ext(*args, **opts):
1897 1896 > print(opts[b'opt'], flush=True)
1898 1897 > EOF
1899 1898 $ "$PYTHON" $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1900 1899 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1901 1900 > [extensions]
1902 1901 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1903 1902 > EOF
1904 1903 $ hg -R $TESTTMP/opt-unicode-default dummy
1905 1904 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1906 1905 *** (use b'' to make it byte string)
1907 1906 hg: unknown command 'dummy'
1908 1907 (did you mean summary?)
1909 1908 [255]
General Comments 0
You need to be logged in to leave comments. Login now