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