##// END OF EJS Templates
extensions: peek command table of disabled extensions without importing...
Yuya Nishihara -
r38180:bdf344ae default
parent child Browse files
Show More
@@ -1,735 +1,770
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from __future__ import absolute_import
9 9
10 import ast
11 import collections
10 12 import functools
11 13 import imp
12 14 import inspect
13 15 import os
14 16
15 17 from .i18n import (
16 18 _,
17 19 gettext,
18 20 )
19 21
20 22 from . import (
21 23 cmdutil,
22 24 configitems,
23 25 error,
24 26 pycompat,
25 27 util,
26 28 )
27 29
28 30 from .utils import (
29 31 stringutil,
30 32 )
31 33
32 34 _extensions = {}
33 35 _disabledextensions = {}
34 36 _aftercallbacks = {}
35 37 _order = []
36 38 _builtin = {
37 39 'hbisect',
38 40 'bookmarks',
39 41 'color',
40 42 'parentrevspec',
41 43 'progress',
42 44 'interhg',
43 45 'inotify',
44 46 'hgcia'
45 47 }
46 48
47 49 def extensions(ui=None):
48 50 if ui:
49 51 def enabled(name):
50 52 for format in ['%s', 'hgext.%s']:
51 53 conf = ui.config('extensions', format % name)
52 54 if conf is not None and not conf.startswith('!'):
53 55 return True
54 56 else:
55 57 enabled = lambda name: True
56 58 for name in _order:
57 59 module = _extensions[name]
58 60 if module and enabled(name):
59 61 yield name, module
60 62
61 63 def find(name):
62 64 '''return module with given extension name'''
63 65 mod = None
64 66 try:
65 67 mod = _extensions[name]
66 68 except KeyError:
67 69 for k, v in _extensions.iteritems():
68 70 if k.endswith('.' + name) or k.endswith('/' + name):
69 71 mod = v
70 72 break
71 73 if not mod:
72 74 raise KeyError(name)
73 75 return mod
74 76
75 77 def loadpath(path, module_name):
76 78 module_name = module_name.replace('.', '_')
77 79 path = util.normpath(util.expandpath(path))
78 80 module_name = pycompat.fsdecode(module_name)
79 81 path = pycompat.fsdecode(path)
80 82 if os.path.isdir(path):
81 83 # module/__init__.py style
82 84 d, f = os.path.split(path)
83 85 fd, fpath, desc = imp.find_module(f, [d])
84 86 return imp.load_module(module_name, fd, fpath, desc)
85 87 else:
86 88 try:
87 89 return imp.load_source(module_name, path)
88 90 except IOError as exc:
89 91 if not exc.filename:
90 92 exc.filename = path # python does not fill this
91 93 raise
92 94
93 95 def _importh(name):
94 96 """import and return the <name> module"""
95 97 mod = __import__(pycompat.sysstr(name))
96 98 components = name.split('.')
97 99 for comp in components[1:]:
98 100 mod = getattr(mod, comp)
99 101 return mod
100 102
101 103 def _importext(name, path=None, reportfunc=None):
102 104 if path:
103 105 # the module will be loaded in sys.modules
104 106 # choose an unique name so that it doesn't
105 107 # conflicts with other modules
106 108 mod = loadpath(path, 'hgext.%s' % name)
107 109 else:
108 110 try:
109 111 mod = _importh("hgext.%s" % name)
110 112 except ImportError as err:
111 113 if reportfunc:
112 114 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
113 115 try:
114 116 mod = _importh("hgext3rd.%s" % name)
115 117 except ImportError as err:
116 118 if reportfunc:
117 119 reportfunc(err, "hgext3rd.%s" % name, name)
118 120 mod = _importh(name)
119 121 return mod
120 122
121 123 def _reportimporterror(ui, err, failed, next):
122 124 # note: this ui.debug happens before --debug is processed,
123 125 # Use --config ui.debug=1 to see them.
124 126 ui.debug('could not import %s (%s): trying %s\n'
125 127 % (failed, stringutil.forcebytestr(err), next))
126 128 if ui.debugflag:
127 129 ui.traceback()
128 130
129 131 def _rejectunicode(name, xs):
130 132 if isinstance(xs, (list, set, tuple)):
131 133 for x in xs:
132 134 _rejectunicode(name, x)
133 135 elif isinstance(xs, dict):
134 136 for k, v in xs.items():
135 137 _rejectunicode(name, k)
136 138 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
137 139 elif isinstance(xs, type(u'')):
138 140 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
139 141 hint="use b'' to make it byte string")
140 142
141 143 # attributes set by registrar.command
142 144 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
143 145
144 146 def _validatecmdtable(ui, cmdtable):
145 147 """Check if extension commands have required attributes"""
146 148 for c, e in cmdtable.iteritems():
147 149 f = e[0]
148 150 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
149 151 if not missing:
150 152 continue
151 153 raise error.ProgrammingError(
152 154 'missing attributes: %s' % ', '.join(missing),
153 155 hint="use @command decorator to register '%s'" % c)
154 156
155 157 def _validatetables(ui, mod):
156 158 """Sanity check for loadable tables provided by extension module"""
157 159 for t in ['cmdtable', 'colortable', 'configtable']:
158 160 _rejectunicode(t, getattr(mod, t, {}))
159 161 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
160 162 'templatefilter', 'templatefunc', 'templatekeyword']:
161 163 o = getattr(mod, t, None)
162 164 if o:
163 165 _rejectunicode(t, o._table)
164 166 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
165 167
166 168 def load(ui, name, path):
167 169 if name.startswith('hgext.') or name.startswith('hgext/'):
168 170 shortname = name[6:]
169 171 else:
170 172 shortname = name
171 173 if shortname in _builtin:
172 174 return None
173 175 if shortname in _extensions:
174 176 return _extensions[shortname]
175 177 _extensions[shortname] = None
176 178 mod = _importext(name, path, bind(_reportimporterror, ui))
177 179
178 180 # Before we do anything with the extension, check against minimum stated
179 181 # compatibility. This gives extension authors a mechanism to have their
180 182 # extensions short circuit when loaded with a known incompatible version
181 183 # of Mercurial.
182 184 minver = getattr(mod, 'minimumhgversion', None)
183 185 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
184 186 ui.warn(_('(third party extension %s requires version %s or newer '
185 187 'of Mercurial; disabling)\n') % (shortname, minver))
186 188 return
187 189 _validatetables(ui, mod)
188 190
189 191 _extensions[shortname] = mod
190 192 _order.append(shortname)
191 193 for fn in _aftercallbacks.get(shortname, []):
192 194 fn(loaded=True)
193 195 return mod
194 196
195 197 def _runuisetup(name, ui):
196 198 uisetup = getattr(_extensions[name], 'uisetup', None)
197 199 if uisetup:
198 200 try:
199 201 uisetup(ui)
200 202 except Exception as inst:
201 203 ui.traceback(force=True)
202 204 msg = stringutil.forcebytestr(inst)
203 205 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
204 206 return False
205 207 return True
206 208
207 209 def _runextsetup(name, ui):
208 210 extsetup = getattr(_extensions[name], 'extsetup', None)
209 211 if extsetup:
210 212 try:
211 213 try:
212 214 extsetup(ui)
213 215 except TypeError:
214 216 if pycompat.getargspec(extsetup).args:
215 217 raise
216 218 extsetup() # old extsetup with no ui argument
217 219 except Exception as inst:
218 220 ui.traceback(force=True)
219 221 msg = stringutil.forcebytestr(inst)
220 222 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
221 223 return False
222 224 return True
223 225
224 226 def loadall(ui, whitelist=None):
225 227 result = ui.configitems("extensions")
226 228 if whitelist is not None:
227 229 result = [(k, v) for (k, v) in result if k in whitelist]
228 230 newindex = len(_order)
229 231 for (name, path) in result:
230 232 if path:
231 233 if path[0:1] == '!':
232 234 _disabledextensions[name] = path[1:]
233 235 continue
234 236 try:
235 237 load(ui, name, path)
236 238 except Exception as inst:
237 239 msg = stringutil.forcebytestr(inst)
238 240 if path:
239 241 ui.warn(_("*** failed to import extension %s from %s: %s\n")
240 242 % (name, path, msg))
241 243 else:
242 244 ui.warn(_("*** failed to import extension %s: %s\n")
243 245 % (name, msg))
244 246 if isinstance(inst, error.Hint) and inst.hint:
245 247 ui.warn(_("*** (%s)\n") % inst.hint)
246 248 ui.traceback()
247 249 # list of (objname, loadermod, loadername) tuple:
248 250 # - objname is the name of an object in extension module,
249 251 # from which extra information is loaded
250 252 # - loadermod is the module where loader is placed
251 253 # - loadername is the name of the function,
252 254 # which takes (ui, extensionname, extraobj) arguments
253 255 #
254 256 # This one is for the list of item that must be run before running any setup
255 257 earlyextraloaders = [
256 258 ('configtable', configitems, 'loadconfigtable'),
257 259 ]
258 260 _loadextra(ui, newindex, earlyextraloaders)
259 261
260 262 broken = set()
261 263 for name in _order[newindex:]:
262 264 if not _runuisetup(name, ui):
263 265 broken.add(name)
264 266
265 267 for name in _order[newindex:]:
266 268 if name in broken:
267 269 continue
268 270 if not _runextsetup(name, ui):
269 271 broken.add(name)
270 272
271 273 for name in broken:
272 274 _extensions[name] = None
273 275
274 276 # Call aftercallbacks that were never met.
275 277 for shortname in _aftercallbacks:
276 278 if shortname in _extensions:
277 279 continue
278 280
279 281 for fn in _aftercallbacks[shortname]:
280 282 fn(loaded=False)
281 283
282 284 # loadall() is called multiple times and lingering _aftercallbacks
283 285 # entries could result in double execution. See issue4646.
284 286 _aftercallbacks.clear()
285 287
286 288 # delay importing avoids cyclic dependency (especially commands)
287 289 from . import (
288 290 color,
289 291 commands,
290 292 filemerge,
291 293 fileset,
292 294 revset,
293 295 templatefilters,
294 296 templatefuncs,
295 297 templatekw,
296 298 )
297 299
298 300 # list of (objname, loadermod, loadername) tuple:
299 301 # - objname is the name of an object in extension module,
300 302 # from which extra information is loaded
301 303 # - loadermod is the module where loader is placed
302 304 # - loadername is the name of the function,
303 305 # which takes (ui, extensionname, extraobj) arguments
304 306 extraloaders = [
305 307 ('cmdtable', commands, 'loadcmdtable'),
306 308 ('colortable', color, 'loadcolortable'),
307 309 ('filesetpredicate', fileset, 'loadpredicate'),
308 310 ('internalmerge', filemerge, 'loadinternalmerge'),
309 311 ('revsetpredicate', revset, 'loadpredicate'),
310 312 ('templatefilter', templatefilters, 'loadfilter'),
311 313 ('templatefunc', templatefuncs, 'loadfunction'),
312 314 ('templatekeyword', templatekw, 'loadkeyword'),
313 315 ]
314 316 _loadextra(ui, newindex, extraloaders)
315 317
316 318 def _loadextra(ui, newindex, extraloaders):
317 319 for name in _order[newindex:]:
318 320 module = _extensions[name]
319 321 if not module:
320 322 continue # loading this module failed
321 323
322 324 for objname, loadermod, loadername in extraloaders:
323 325 extraobj = getattr(module, objname, None)
324 326 if extraobj is not None:
325 327 getattr(loadermod, loadername)(ui, name, extraobj)
326 328
327 329 def afterloaded(extension, callback):
328 330 '''Run the specified function after a named extension is loaded.
329 331
330 332 If the named extension is already loaded, the callback will be called
331 333 immediately.
332 334
333 335 If the named extension never loads, the callback will be called after
334 336 all extensions have been loaded.
335 337
336 338 The callback receives the named argument ``loaded``, which is a boolean
337 339 indicating whether the dependent extension actually loaded.
338 340 '''
339 341
340 342 if extension in _extensions:
341 343 # Report loaded as False if the extension is disabled
342 344 loaded = (_extensions[extension] is not None)
343 345 callback(loaded=loaded)
344 346 else:
345 347 _aftercallbacks.setdefault(extension, []).append(callback)
346 348
347 349 def bind(func, *args):
348 350 '''Partial function application
349 351
350 352 Returns a new function that is the partial application of args and kwargs
351 353 to func. For example,
352 354
353 355 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
354 356 assert callable(func)
355 357 def closure(*a, **kw):
356 358 return func(*(args + a), **kw)
357 359 return closure
358 360
359 361 def _updatewrapper(wrap, origfn, unboundwrapper):
360 362 '''Copy and add some useful attributes to wrapper'''
361 363 try:
362 364 wrap.__name__ = origfn.__name__
363 365 except AttributeError:
364 366 pass
365 367 wrap.__module__ = getattr(origfn, '__module__')
366 368 wrap.__doc__ = getattr(origfn, '__doc__')
367 369 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
368 370 wrap._origfunc = origfn
369 371 wrap._unboundwrapper = unboundwrapper
370 372
371 373 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
372 374 '''Wrap the command named `command' in table
373 375
374 376 Replace command in the command table with wrapper. The wrapped command will
375 377 be inserted into the command table specified by the table argument.
376 378
377 379 The wrapper will be called like
378 380
379 381 wrapper(orig, *args, **kwargs)
380 382
381 383 where orig is the original (wrapped) function, and *args, **kwargs
382 384 are the arguments passed to it.
383 385
384 386 Optionally append to the command synopsis and docstring, used for help.
385 387 For example, if your extension wraps the ``bookmarks`` command to add the
386 388 flags ``--remote`` and ``--all`` you might call this function like so:
387 389
388 390 synopsis = ' [-a] [--remote]'
389 391 docstring = """
390 392
391 393 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
392 394 flags to the bookmarks command. Either flag will show the remote bookmarks
393 395 known to the repository; ``--remote`` will also suppress the output of the
394 396 local bookmarks.
395 397 """
396 398
397 399 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
398 400 synopsis, docstring)
399 401 '''
400 402 assert callable(wrapper)
401 403 aliases, entry = cmdutil.findcmd(command, table)
402 404 for alias, e in table.iteritems():
403 405 if e is entry:
404 406 key = alias
405 407 break
406 408
407 409 origfn = entry[0]
408 410 wrap = functools.partial(util.checksignature(wrapper),
409 411 util.checksignature(origfn))
410 412 _updatewrapper(wrap, origfn, wrapper)
411 413 if docstring is not None:
412 414 wrap.__doc__ += docstring
413 415
414 416 newentry = list(entry)
415 417 newentry[0] = wrap
416 418 if synopsis is not None:
417 419 newentry[2] += synopsis
418 420 table[key] = tuple(newentry)
419 421 return entry
420 422
421 423 def wrapfilecache(cls, propname, wrapper):
422 424 """Wraps a filecache property.
423 425
424 426 These can't be wrapped using the normal wrapfunction.
425 427 """
426 428 propname = pycompat.sysstr(propname)
427 429 assert callable(wrapper)
428 430 for currcls in cls.__mro__:
429 431 if propname in currcls.__dict__:
430 432 origfn = currcls.__dict__[propname].func
431 433 assert callable(origfn)
432 434 def wrap(*args, **kwargs):
433 435 return wrapper(origfn, *args, **kwargs)
434 436 currcls.__dict__[propname].func = wrap
435 437 break
436 438
437 439 if currcls is object:
438 440 raise AttributeError(r"type '%s' has no property '%s'" % (
439 441 cls, propname))
440 442
441 443 class wrappedfunction(object):
442 444 '''context manager for temporarily wrapping a function'''
443 445
444 446 def __init__(self, container, funcname, wrapper):
445 447 assert callable(wrapper)
446 448 self._container = container
447 449 self._funcname = funcname
448 450 self._wrapper = wrapper
449 451
450 452 def __enter__(self):
451 453 wrapfunction(self._container, self._funcname, self._wrapper)
452 454
453 455 def __exit__(self, exctype, excvalue, traceback):
454 456 unwrapfunction(self._container, self._funcname, self._wrapper)
455 457
456 458 def wrapfunction(container, funcname, wrapper):
457 459 '''Wrap the function named funcname in container
458 460
459 461 Replace the funcname member in the given container with the specified
460 462 wrapper. The container is typically a module, class, or instance.
461 463
462 464 The wrapper will be called like
463 465
464 466 wrapper(orig, *args, **kwargs)
465 467
466 468 where orig is the original (wrapped) function, and *args, **kwargs
467 469 are the arguments passed to it.
468 470
469 471 Wrapping methods of the repository object is not recommended since
470 472 it conflicts with extensions that extend the repository by
471 473 subclassing. All extensions that need to extend methods of
472 474 localrepository should use this subclassing trick: namely,
473 475 reposetup() should look like
474 476
475 477 def reposetup(ui, repo):
476 478 class myrepo(repo.__class__):
477 479 def whatever(self, *args, **kwargs):
478 480 [...extension stuff...]
479 481 super(myrepo, self).whatever(*args, **kwargs)
480 482 [...extension stuff...]
481 483
482 484 repo.__class__ = myrepo
483 485
484 486 In general, combining wrapfunction() with subclassing does not
485 487 work. Since you cannot control what other extensions are loaded by
486 488 your end users, you should play nicely with others by using the
487 489 subclass trick.
488 490 '''
489 491 assert callable(wrapper)
490 492
491 493 origfn = getattr(container, funcname)
492 494 assert callable(origfn)
493 495 if inspect.ismodule(container):
494 496 # origfn is not an instance or class method. "partial" can be used.
495 497 # "partial" won't insert a frame in traceback.
496 498 wrap = functools.partial(wrapper, origfn)
497 499 else:
498 500 # "partial" cannot be safely used. Emulate its effect by using "bind".
499 501 # The downside is one more frame in traceback.
500 502 wrap = bind(wrapper, origfn)
501 503 _updatewrapper(wrap, origfn, wrapper)
502 504 setattr(container, funcname, wrap)
503 505 return origfn
504 506
505 507 def unwrapfunction(container, funcname, wrapper=None):
506 508 '''undo wrapfunction
507 509
508 510 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
509 511 from the chain of wrappers.
510 512
511 513 Return the removed wrapper.
512 514 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
513 515 wrapper is not None but is not found in the wrapper chain.
514 516 '''
515 517 chain = getwrapperchain(container, funcname)
516 518 origfn = chain.pop()
517 519 if wrapper is None:
518 520 wrapper = chain[0]
519 521 chain.remove(wrapper)
520 522 setattr(container, funcname, origfn)
521 523 for w in reversed(chain):
522 524 wrapfunction(container, funcname, w)
523 525 return wrapper
524 526
525 527 def getwrapperchain(container, funcname):
526 528 '''get a chain of wrappers of a function
527 529
528 530 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
529 531
530 532 The wrapper functions are the ones passed to wrapfunction, whose first
531 533 argument is origfunc.
532 534 '''
533 535 result = []
534 536 fn = getattr(container, funcname)
535 537 while fn:
536 538 assert callable(fn)
537 539 result.append(getattr(fn, '_unboundwrapper', fn))
538 540 fn = getattr(fn, '_origfunc', None)
539 541 return result
540 542
541 543 def _disabledpaths(strip_init=False):
542 544 '''find paths of disabled extensions. returns a dict of {name: path}
543 545 removes /__init__.py from packages if strip_init is True'''
544 546 import hgext
545 547 extpath = os.path.dirname(
546 548 os.path.abspath(pycompat.fsencode(hgext.__file__)))
547 549 try: # might not be a filesystem path
548 550 files = os.listdir(extpath)
549 551 except OSError:
550 552 return {}
551 553
552 554 exts = {}
553 555 for e in files:
554 556 if e.endswith('.py'):
555 557 name = e.rsplit('.', 1)[0]
556 558 path = os.path.join(extpath, e)
557 559 else:
558 560 name = e
559 561 path = os.path.join(extpath, e, '__init__.py')
560 562 if not os.path.exists(path):
561 563 continue
562 564 if strip_init:
563 565 path = os.path.dirname(path)
564 566 if name in exts or name in _order or name == '__init__':
565 567 continue
566 568 exts[name] = path
567 569 for name, path in _disabledextensions.iteritems():
568 570 # If no path was provided for a disabled extension (e.g. "color=!"),
569 571 # don't replace the path we already found by the scan above.
570 572 if path:
571 573 exts[name] = path
572 574 return exts
573 575
574 576 def _moduledoc(file):
575 577 '''return the top-level python documentation for the given file
576 578
577 579 Loosely inspired by pydoc.source_synopsis(), but rewritten to
578 580 handle triple quotes and to return the whole text instead of just
579 581 the synopsis'''
580 582 result = []
581 583
582 584 line = file.readline()
583 585 while line[:1] == '#' or not line.strip():
584 586 line = file.readline()
585 587 if not line:
586 588 break
587 589
588 590 start = line[:3]
589 591 if start == '"""' or start == "'''":
590 592 line = line[3:]
591 593 while line:
592 594 if line.rstrip().endswith(start):
593 595 line = line.split(start)[0]
594 596 if line:
595 597 result.append(line)
596 598 break
597 599 elif not line:
598 600 return None # unmatched delimiter
599 601 result.append(line)
600 602 line = file.readline()
601 603 else:
602 604 return None
603 605
604 606 return ''.join(result)
605 607
606 608 def _disabledhelp(path):
607 609 '''retrieve help synopsis of a disabled extension (without importing)'''
608 610 try:
609 611 file = open(path)
610 612 except IOError:
611 613 return
612 614 else:
613 615 doc = _moduledoc(file)
614 616 file.close()
615 617
616 618 if doc: # extracting localized synopsis
617 619 return gettext(doc)
618 620 else:
619 621 return _('(no help text available)')
620 622
621 623 def disabled():
622 624 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
623 625 try:
624 626 from hgext import __index__
625 627 return dict((name, gettext(desc))
626 628 for name, desc in __index__.docs.iteritems()
627 629 if name not in _order)
628 630 except (ImportError, AttributeError):
629 631 pass
630 632
631 633 paths = _disabledpaths()
632 634 if not paths:
633 635 return {}
634 636
635 637 exts = {}
636 638 for name, path in paths.iteritems():
637 639 doc = _disabledhelp(path)
638 640 if doc:
639 641 exts[name] = doc.splitlines()[0]
640 642
641 643 return exts
642 644
643 645 def disabledext(name):
644 646 '''find a specific disabled extension from hgext. returns desc'''
645 647 try:
646 648 from hgext import __index__
647 649 if name in _order: # enabled
648 650 return
649 651 else:
650 652 return gettext(__index__.docs.get(name))
651 653 except (ImportError, AttributeError):
652 654 pass
653 655
654 656 paths = _disabledpaths()
655 657 if name in paths:
656 658 return _disabledhelp(paths[name])
657 659
660 def _walkcommand(node):
661 """Scan @command() decorators in the tree starting at node"""
662 todo = collections.deque([node])
663 while todo:
664 node = todo.popleft()
665 if not isinstance(node, ast.FunctionDef):
666 todo.extend(ast.iter_child_nodes(node))
667 continue
668 for d in node.decorator_list:
669 if not isinstance(d, ast.Call):
670 continue
671 if not isinstance(d.func, ast.Name):
672 continue
673 if d.func.id != r'command':
674 continue
675 yield d
676
677 def _disabledcmdtable(path):
678 """Construct a dummy command table without loading the extension module
679
680 This may raise IOError or SyntaxError.
681 """
682 with open(path, 'rb') as src:
683 root = ast.parse(src.read(), path)
684 cmdtable = {}
685 for node in _walkcommand(root):
686 if not node.args:
687 continue
688 a = node.args[0]
689 if isinstance(a, ast.Str):
690 name = pycompat.sysbytes(a.s)
691 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
692 name = a.s
693 else:
694 continue
695 cmdtable[name] = (None, [], b'')
696 return cmdtable
697
658 698 def _finddisabledcmd(ui, cmd, name, path, strict):
659 699 try:
660 mod = loadpath(path, 'hgext.%s' % name)
661 except Exception:
700 cmdtable = _disabledcmdtable(path)
701 except (IOError, SyntaxError):
662 702 return
663 703 try:
664 aliases, entry = cmdutil.findcmd(cmd,
665 getattr(mod, 'cmdtable', {}), strict)
704 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
666 705 except (error.AmbiguousCommand, error.UnknownCommand):
667 706 return
668 except Exception:
669 ui.warn(_('warning: error finding commands in %s\n') % path)
670 ui.traceback()
671 return
672 707 for c in aliases:
673 708 if c.startswith(cmd):
674 709 cmd = c
675 710 break
676 711 else:
677 712 cmd = aliases[0]
678 doc = gettext(pycompat.getdoc(mod))
713 doc = _disabledhelp(path)
679 714 return (cmd, name, doc)
680 715
681 716 def disabledcmd(ui, cmd, strict=False):
682 '''import disabled extensions until cmd is found.
717 '''find cmd from disabled extensions without importing.
683 718 returns (cmdname, extname, doc)'''
684 719
685 paths = _disabledpaths(strip_init=True)
720 paths = _disabledpaths()
686 721 if not paths:
687 722 raise error.UnknownCommand(cmd)
688 723
689 724 ext = None
690 725 # first, search for an extension with the same name as the command
691 726 path = paths.pop(cmd, None)
692 727 if path:
693 728 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
694 729 if not ext:
695 730 # otherwise, interrogate each extension until there's a match
696 731 for name, path in paths.iteritems():
697 732 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
698 733 if ext:
699 734 break
700 735 if ext:
701 736 return ext
702 737
703 738 raise error.UnknownCommand(cmd)
704 739
705 740 def enabled(shortname=True):
706 741 '''return a dict of {name: desc} of extensions'''
707 742 exts = {}
708 743 for ename, ext in extensions():
709 744 doc = (gettext(ext.__doc__) or _('(no help text available)'))
710 745 if shortname:
711 746 ename = ename.split('.')[-1]
712 747 exts[ename] = doc.splitlines()[0].strip()
713 748
714 749 return exts
715 750
716 751 def notloaded():
717 752 '''return short names of extensions that failed to load'''
718 753 return [name for name, mod in _extensions.iteritems() if mod is None]
719 754
720 755 def moduleversion(module):
721 756 '''return version information from given module as a string'''
722 757 if (util.safehasattr(module, 'getversion')
723 758 and callable(module.getversion)):
724 759 version = module.getversion()
725 760 elif util.safehasattr(module, '__version__'):
726 761 version = module.__version__
727 762 else:
728 763 version = ''
729 764 if isinstance(version, (list, tuple)):
730 765 version = '.'.join(pycompat.bytestr(o) for o in version)
731 766 return version
732 767
733 768 def ismoduleinternal(module):
734 769 exttestedwith = getattr(module, 'testedwith', None)
735 770 return exttestedwith == "ships-with-hg-core"
@@ -1,1729 +1,1734
1 1 Test basic extension support
2 2
3 3 $ cat > foobar.py <<EOF
4 4 > import os
5 5 > from mercurial import commands, registrar
6 6 > cmdtable = {}
7 7 > command = registrar.command(cmdtable)
8 8 > configtable = {}
9 9 > configitem = registrar.configitem(configtable)
10 10 > configitem(b'tests', b'foo', default=b"Foo")
11 11 > def uisetup(ui):
12 12 > ui.write(b"uisetup called\\n")
13 13 > ui.flush()
14 14 > def reposetup(ui, repo):
15 15 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
16 16 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
17 17 > ui.flush()
18 18 > @command(b'foo', [], b'hg foo')
19 19 > def foo(ui, *args, **kwargs):
20 20 > foo = ui.config(b'tests', b'foo')
21 21 > ui.write(foo)
22 22 > ui.write(b"\\n")
23 23 > @command(b'bar', [], b'hg bar', norepo=True)
24 24 > def bar(ui, *args, **kwargs):
25 25 > ui.write(b"Bar\\n")
26 26 > EOF
27 27 $ abspath=`pwd`/foobar.py
28 28
29 29 $ mkdir barfoo
30 30 $ cp foobar.py barfoo/__init__.py
31 31 $ barfoopath=`pwd`/barfoo
32 32
33 33 $ hg init a
34 34 $ cd a
35 35 $ echo foo > file
36 36 $ hg add file
37 37 $ hg commit -m 'add file'
38 38
39 39 $ echo '[extensions]' >> $HGRCPATH
40 40 $ echo "foobar = $abspath" >> $HGRCPATH
41 41 $ hg foo
42 42 uisetup called
43 43 reposetup called for a
44 44 ui == repo.ui
45 45 reposetup called for a (chg !)
46 46 ui == repo.ui (chg !)
47 47 Foo
48 48
49 49 $ cd ..
50 50 $ hg clone a b
51 51 uisetup called (no-chg !)
52 52 reposetup called for a
53 53 ui == repo.ui
54 54 reposetup called for b
55 55 ui == repo.ui
56 56 updating to branch default
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58
59 59 $ hg bar
60 60 uisetup called (no-chg !)
61 61 Bar
62 62 $ echo 'foobar = !' >> $HGRCPATH
63 63
64 64 module/__init__.py-style
65 65
66 66 $ echo "barfoo = $barfoopath" >> $HGRCPATH
67 67 $ cd a
68 68 $ hg foo
69 69 uisetup called
70 70 reposetup called for a
71 71 ui == repo.ui
72 72 reposetup called for a (chg !)
73 73 ui == repo.ui (chg !)
74 74 Foo
75 75 $ echo 'barfoo = !' >> $HGRCPATH
76 76
77 77 Check that extensions are loaded in phases:
78 78
79 79 $ cat > foo.py <<EOF
80 80 > import os
81 81 > name = os.path.basename(__file__).rsplit('.', 1)[0]
82 82 > print("1) %s imported" % name)
83 83 > def uisetup(ui):
84 84 > print("2) %s uisetup" % name)
85 85 > def extsetup():
86 86 > print("3) %s extsetup" % name)
87 87 > def reposetup(ui, repo):
88 88 > print("4) %s reposetup" % name)
89 89 >
90 90 > # custom predicate to check registration of functions at loading
91 91 > from mercurial import (
92 92 > registrar,
93 93 > smartset,
94 94 > )
95 95 > revsetpredicate = registrar.revsetpredicate()
96 96 > @revsetpredicate(name, safe=True) # safe=True for query via hgweb
97 97 > def custompredicate(repo, subset, x):
98 98 > return smartset.baseset([r for r in subset if r in {0}])
99 99 > EOF
100 100
101 101 $ cp foo.py bar.py
102 102 $ echo 'foo = foo.py' >> $HGRCPATH
103 103 $ echo 'bar = bar.py' >> $HGRCPATH
104 104
105 105 Check normal command's load order of extensions and registration of functions
106 106
107 107 $ hg log -r "foo() and bar()" -q
108 108 1) foo imported
109 109 1) bar imported
110 110 2) foo uisetup
111 111 2) bar uisetup
112 112 3) foo extsetup
113 113 3) bar extsetup
114 114 4) foo reposetup
115 115 4) bar reposetup
116 116 0:c24b9ac61126
117 117
118 118 Check hgweb's load order of extensions and registration of functions
119 119
120 120 $ cat > hgweb.cgi <<EOF
121 121 > #!$PYTHON
122 122 > from mercurial import demandimport; demandimport.enable()
123 123 > from mercurial.hgweb import hgweb
124 124 > from mercurial.hgweb import wsgicgi
125 125 > application = hgweb('.', 'test repo')
126 126 > wsgicgi.launch(application)
127 127 > EOF
128 128 $ . "$TESTDIR/cgienv"
129 129
130 130 $ PATH_INFO='/' SCRIPT_NAME='' $PYTHON hgweb.cgi \
131 131 > | grep '^[0-9]) ' # ignores HTML output
132 132 1) foo imported
133 133 1) bar imported
134 134 2) foo uisetup
135 135 2) bar uisetup
136 136 3) foo extsetup
137 137 3) bar extsetup
138 138 4) foo reposetup
139 139 4) bar reposetup
140 140
141 141 (check that revset predicate foo() and bar() are available)
142 142
143 143 #if msys
144 144 $ PATH_INFO='//shortlog'
145 145 #else
146 146 $ PATH_INFO='/shortlog'
147 147 #endif
148 148 $ export PATH_INFO
149 149 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' $PYTHON hgweb.cgi \
150 150 > | grep '<a href="/rev/[0-9a-z]*">'
151 151 <a href="/rev/c24b9ac61126">add file</a>
152 152
153 153 $ echo 'foo = !' >> $HGRCPATH
154 154 $ echo 'bar = !' >> $HGRCPATH
155 155
156 156 Check "from __future__ import absolute_import" support for external libraries
157 157
158 158 #if windows
159 159 $ PATHSEP=";"
160 160 #else
161 161 $ PATHSEP=":"
162 162 #endif
163 163 $ export PATHSEP
164 164
165 165 $ mkdir $TESTTMP/libroot
166 166 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
167 167 $ mkdir $TESTTMP/libroot/mod
168 168 $ touch $TESTTMP/libroot/mod/__init__.py
169 169 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
170 170
171 171 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<EOF
172 172 > from __future__ import absolute_import
173 173 > import ambig # should load "libroot/ambig.py"
174 174 > s = ambig.s
175 175 > EOF
176 176 $ cat > loadabs.py <<EOF
177 177 > import mod.ambigabs as ambigabs
178 178 > def extsetup():
179 179 > print('ambigabs.s=%s' % ambigabs.s)
180 180 > EOF
181 181 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
182 182 ambigabs.s=libroot/ambig.py
183 183 $TESTTMP/a
184 184
185 185 #if no-py3k
186 186 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<EOF
187 187 > import ambig # should load "libroot/mod/ambig.py"
188 188 > s = ambig.s
189 189 > EOF
190 190 $ cat > loadrel.py <<EOF
191 191 > import mod.ambigrel as ambigrel
192 192 > def extsetup():
193 193 > print('ambigrel.s=%s' % ambigrel.s)
194 194 > EOF
195 195 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
196 196 ambigrel.s=libroot/mod/ambig.py
197 197 $TESTTMP/a
198 198 #endif
199 199
200 200 Check absolute/relative import of extension specific modules
201 201
202 202 $ mkdir $TESTTMP/extroot
203 203 $ cat > $TESTTMP/extroot/bar.py <<EOF
204 204 > s = 'this is extroot.bar'
205 205 > EOF
206 206 $ mkdir $TESTTMP/extroot/sub1
207 207 $ cat > $TESTTMP/extroot/sub1/__init__.py <<EOF
208 208 > s = 'this is extroot.sub1.__init__'
209 209 > EOF
210 210 $ cat > $TESTTMP/extroot/sub1/baz.py <<EOF
211 211 > s = 'this is extroot.sub1.baz'
212 212 > EOF
213 213 $ cat > $TESTTMP/extroot/__init__.py <<EOF
214 214 > s = 'this is extroot.__init__'
215 215 > import foo
216 216 > def extsetup(ui):
217 217 > ui.write('(extroot) ', foo.func(), '\n')
218 218 > ui.flush()
219 219 > EOF
220 220
221 221 $ cat > $TESTTMP/extroot/foo.py <<EOF
222 222 > # test absolute import
223 223 > buf = []
224 224 > def func():
225 225 > # "not locals" case
226 226 > import extroot.bar
227 227 > buf.append('import extroot.bar in func(): %s' % extroot.bar.s)
228 228 > return '\n(extroot) '.join(buf)
229 229 > # "fromlist == ('*',)" case
230 230 > from extroot.bar import *
231 231 > buf.append('from extroot.bar import *: %s' % s)
232 232 > # "not fromlist" and "if '.' in name" case
233 233 > import extroot.sub1.baz
234 234 > buf.append('import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
235 235 > # "not fromlist" and NOT "if '.' in name" case
236 236 > import extroot
237 237 > buf.append('import extroot: %s' % extroot.s)
238 238 > # NOT "not fromlist" and NOT "level != -1" case
239 239 > from extroot.bar import s
240 240 > buf.append('from extroot.bar import s: %s' % s)
241 241 > EOF
242 242 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
243 243 (extroot) from extroot.bar import *: this is extroot.bar
244 244 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
245 245 (extroot) import extroot: this is extroot.__init__
246 246 (extroot) from extroot.bar import s: this is extroot.bar
247 247 (extroot) import extroot.bar in func(): this is extroot.bar
248 248 $TESTTMP/a
249 249
250 250 #if no-py3k
251 251 $ rm "$TESTTMP"/extroot/foo.*
252 252 $ rm -Rf "$TESTTMP/extroot/__pycache__"
253 253 $ cat > $TESTTMP/extroot/foo.py <<EOF
254 254 > # test relative import
255 255 > buf = []
256 256 > def func():
257 257 > # "not locals" case
258 258 > import bar
259 259 > buf.append('import bar in func(): %s' % bar.s)
260 260 > return '\n(extroot) '.join(buf)
261 261 > # "fromlist == ('*',)" case
262 262 > from bar import *
263 263 > buf.append('from bar import *: %s' % s)
264 264 > # "not fromlist" and "if '.' in name" case
265 265 > import sub1.baz
266 266 > buf.append('import sub1.baz: %s' % sub1.baz.s)
267 267 > # "not fromlist" and NOT "if '.' in name" case
268 268 > import sub1
269 269 > buf.append('import sub1: %s' % sub1.s)
270 270 > # NOT "not fromlist" and NOT "level != -1" case
271 271 > from bar import s
272 272 > buf.append('from bar import s: %s' % s)
273 273 > EOF
274 274 $ hg --config extensions.extroot=$TESTTMP/extroot root
275 275 (extroot) from bar import *: this is extroot.bar
276 276 (extroot) import sub1.baz: this is extroot.sub1.baz
277 277 (extroot) import sub1: this is extroot.sub1.__init__
278 278 (extroot) from bar import s: this is extroot.bar
279 279 (extroot) import bar in func(): this is extroot.bar
280 280 $TESTTMP/a
281 281 #endif
282 282
283 283 #if demandimport
284 284
285 285 Examine whether module loading is delayed until actual referring, even
286 286 though module is imported with "absolute_import" feature.
287 287
288 288 Files below in each packages are used for described purpose:
289 289
290 290 - "called": examine whether "from MODULE import ATTR" works correctly
291 291 - "unused": examine whether loading is delayed correctly
292 292 - "used": examine whether "from PACKAGE import MODULE" works correctly
293 293
294 294 Package hierarchy is needed to examine whether demand importing works
295 295 as expected for "from SUB.PACK.AGE import MODULE".
296 296
297 297 Setup "external library" to be imported with "absolute_import"
298 298 feature.
299 299
300 300 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
301 301 $ touch $TESTTMP/extlibroot/__init__.py
302 302 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
303 303 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
304 304
305 305 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<EOF
306 306 > def func():
307 307 > return "this is extlibroot.lsub1.lsub2.called.func()"
308 308 > EOF
309 309 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<EOF
310 310 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
311 311 > EOF
312 312 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<EOF
313 313 > detail = "this is extlibroot.lsub1.lsub2.used"
314 314 > EOF
315 315
316 316 Setup sub-package of "external library", which causes instantiation of
317 317 demandmod in "recurse down the module chain" code path. Relative
318 318 importing with "absolute_import" feature isn't tested, because "level
319 319 >=1 " doesn't cause instantiation of demandmod.
320 320
321 321 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
322 322 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<EOF
323 323 > detail = "this is extlibroot.recursedown.abs.used"
324 324 > EOF
325 325 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<EOF
326 326 > from __future__ import absolute_import
327 327 > from extlibroot.recursedown.abs.used import detail
328 328 > EOF
329 329
330 330 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
331 331 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<EOF
332 332 > detail = "this is extlibroot.recursedown.legacy.used"
333 333 > EOF
334 334 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<EOF
335 335 > # legacy style (level == -1) import
336 336 > from extlibroot.recursedown.legacy.used import detail
337 337 > EOF
338 338
339 339 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<EOF
340 340 > from __future__ import absolute_import
341 341 > from extlibroot.recursedown.abs import detail as absdetail
342 342 > from .legacy import detail as legacydetail
343 343 > EOF
344 344
345 345 Setup package that re-exports an attribute of its submodule as the same
346 346 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
347 347 the submodule 'used' should be somehow accessible. (issue5617)
348 348
349 349 $ mkdir -p $TESTTMP/extlibroot/shadowing
350 350 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<EOF
351 351 > detail = "this is extlibroot.shadowing.used"
352 352 > EOF
353 353 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<EOF
354 354 > from __future__ import absolute_import
355 355 > from extlibroot.shadowing.used import detail
356 356 > EOF
357 357 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<EOF
358 358 > from __future__ import absolute_import
359 359 > from .used import detail as used
360 360 > EOF
361 361
362 362 Setup extension local modules to be imported with "absolute_import"
363 363 feature.
364 364
365 365 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
366 366 $ touch $TESTTMP/absextroot/xsub1/__init__.py
367 367 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
368 368
369 369 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<EOF
370 370 > def func():
371 371 > return "this is absextroot.xsub1.xsub2.called.func()"
372 372 > EOF
373 373 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<EOF
374 374 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
375 375 > EOF
376 376 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<EOF
377 377 > detail = "this is absextroot.xsub1.xsub2.used"
378 378 > EOF
379 379
380 380 Setup extension local modules to examine whether demand importing
381 381 works as expected in "level > 1" case.
382 382
383 383 $ cat > $TESTTMP/absextroot/relimportee.py <<EOF
384 384 > detail = "this is absextroot.relimportee"
385 385 > EOF
386 386 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<EOF
387 387 > from __future__ import absolute_import
388 388 > from ... import relimportee
389 389 > detail = "this relimporter imports %r" % (relimportee.detail)
390 390 > EOF
391 391
392 392 Setup modules, which actually import extension local modules at
393 393 runtime.
394 394
395 395 $ cat > $TESTTMP/absextroot/absolute.py << EOF
396 396 > from __future__ import absolute_import
397 397 >
398 398 > # import extension local modules absolutely (level = 0)
399 399 > from absextroot.xsub1.xsub2 import used, unused
400 400 > from absextroot.xsub1.xsub2.called import func
401 401 >
402 402 > def getresult():
403 403 > result = []
404 404 > result.append(used.detail)
405 405 > result.append(func())
406 406 > return result
407 407 > EOF
408 408
409 409 $ cat > $TESTTMP/absextroot/relative.py << EOF
410 410 > from __future__ import absolute_import
411 411 >
412 412 > # import extension local modules relatively (level == 1)
413 413 > from .xsub1.xsub2 import used, unused
414 414 > from .xsub1.xsub2.called import func
415 415 >
416 416 > # import a module, which implies "importing with level > 1"
417 417 > from .xsub1.xsub2 import relimporter
418 418 >
419 419 > def getresult():
420 420 > result = []
421 421 > result.append(used.detail)
422 422 > result.append(func())
423 423 > result.append(relimporter.detail)
424 424 > return result
425 425 > EOF
426 426
427 427 Setup main procedure of extension.
428 428
429 429 $ cat > $TESTTMP/absextroot/__init__.py <<EOF
430 430 > from __future__ import absolute_import
431 431 > from mercurial import registrar
432 432 > cmdtable = {}
433 433 > command = registrar.command(cmdtable)
434 434 >
435 435 > # "absolute" and "relative" shouldn't be imported before actual
436 436 > # command execution, because (1) they import same modules, and (2)
437 437 > # preceding import (= instantiate "demandmod" object instead of
438 438 > # real "module" object) might hide problem of succeeding import.
439 439 >
440 440 > @command(b'showabsolute', [], norepo=True)
441 441 > def showabsolute(ui, *args, **opts):
442 442 > from absextroot import absolute
443 443 > ui.write(b'ABS: %s\n' % '\nABS: '.join(absolute.getresult()))
444 444 >
445 445 > @command(b'showrelative', [], norepo=True)
446 446 > def showrelative(ui, *args, **opts):
447 447 > from . import relative
448 448 > ui.write(b'REL: %s\n' % '\nREL: '.join(relative.getresult()))
449 449 >
450 450 > # import modules from external library
451 451 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
452 452 > from extlibroot.lsub1.lsub2.called import func as lfunc
453 453 > from extlibroot.recursedown import absdetail, legacydetail
454 454 > from extlibroot.shadowing import proxied
455 455 >
456 456 > def uisetup(ui):
457 457 > result = []
458 458 > result.append(lused.detail)
459 459 > result.append(lfunc())
460 460 > result.append(absdetail)
461 461 > result.append(legacydetail)
462 462 > result.append(proxied.detail)
463 463 > ui.write(b'LIB: %s\n' % '\nLIB: '.join(result))
464 464 > EOF
465 465
466 466 Examine module importing.
467 467
468 468 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
469 469 LIB: this is extlibroot.lsub1.lsub2.used
470 470 LIB: this is extlibroot.lsub1.lsub2.called.func()
471 471 LIB: this is extlibroot.recursedown.abs.used
472 472 LIB: this is extlibroot.recursedown.legacy.used
473 473 LIB: this is extlibroot.shadowing.used
474 474 ABS: this is absextroot.xsub1.xsub2.used
475 475 ABS: this is absextroot.xsub1.xsub2.called.func()
476 476
477 477 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
478 478 LIB: this is extlibroot.lsub1.lsub2.used
479 479 LIB: this is extlibroot.lsub1.lsub2.called.func()
480 480 LIB: this is extlibroot.recursedown.abs.used
481 481 LIB: this is extlibroot.recursedown.legacy.used
482 482 LIB: this is extlibroot.shadowing.used
483 483 REL: this is absextroot.xsub1.xsub2.used
484 484 REL: this is absextroot.xsub1.xsub2.called.func()
485 485 REL: this relimporter imports 'this is absextroot.relimportee'
486 486
487 487 Examine whether sub-module is imported relatively as expected.
488 488
489 489 See also issue5208 for detail about example case on Python 3.x.
490 490
491 491 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
492 492 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
493 493
494 494 $ cat > $TESTTMP/notexist.py <<EOF
495 495 > text = 'notexist.py at root is loaded unintentionally\n'
496 496 > EOF
497 497
498 498 $ cat > $TESTTMP/checkrelativity.py <<EOF
499 499 > from mercurial import registrar
500 500 > cmdtable = {}
501 501 > command = registrar.command(cmdtable)
502 502 >
503 503 > # demand import avoids failure of importing notexist here
504 504 > import extlibroot.lsub1.lsub2.notexist
505 505 >
506 506 > @command(b'checkrelativity', [], norepo=True)
507 507 > def checkrelativity(ui, *args, **opts):
508 508 > try:
509 509 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
510 510 > return 1 # unintentional success
511 511 > except ImportError:
512 512 > pass # intentional failure
513 513 > EOF
514 514
515 515 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
516 516
517 517 #endif
518 518
519 519 Make sure a broken uisetup doesn't globally break hg:
520 520 $ cat > $TESTTMP/baduisetup.py <<EOF
521 521 > def uisetup(ui):
522 522 > 1/0
523 523 > EOF
524 524
525 525 Even though the extension fails during uisetup, hg is still basically usable:
526 526 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
527 527 Traceback (most recent call last):
528 528 File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
529 529 uisetup(ui)
530 530 File "$TESTTMP/baduisetup.py", line 2, in uisetup
531 531 1/0
532 532 ZeroDivisionError: integer division or modulo by zero
533 533 *** failed to set up extension baduisetup: integer division or modulo by zero
534 534 Mercurial Distributed SCM (version *) (glob)
535 535 (see https://mercurial-scm.org for more information)
536 536
537 537 Copyright (C) 2005-* Matt Mackall and others (glob)
538 538 This is free software; see the source for copying conditions. There is NO
539 539 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
540 540
541 541 $ cd ..
542 542
543 543 hide outer repo
544 544 $ hg init
545 545
546 546 $ cat > empty.py <<EOF
547 547 > '''empty cmdtable
548 548 > '''
549 549 > cmdtable = {}
550 550 > EOF
551 551 $ emptypath=`pwd`/empty.py
552 552 $ echo "empty = $emptypath" >> $HGRCPATH
553 553 $ hg help empty
554 554 empty extension - empty cmdtable
555 555
556 556 no commands defined
557 557
558 558
559 559 $ echo 'empty = !' >> $HGRCPATH
560 560
561 561 $ cat > debugextension.py <<EOF
562 562 > '''only debugcommands
563 563 > '''
564 564 > from mercurial import registrar
565 565 > cmdtable = {}
566 566 > command = registrar.command(cmdtable)
567 567 > @command(b'debugfoobar', [], b'hg debugfoobar')
568 568 > def debugfoobar(ui, repo, *args, **opts):
569 569 > "yet another debug command"
570 570 > pass
571 571 > @command(b'foo', [], b'hg foo')
572 572 > def foo(ui, repo, *args, **opts):
573 573 > """yet another foo command
574 574 > This command has been DEPRECATED since forever.
575 575 > """
576 576 > pass
577 577 > EOF
578 578 $ debugpath=`pwd`/debugextension.py
579 579 $ echo "debugextension = $debugpath" >> $HGRCPATH
580 580
581 581 $ hg help debugextension
582 582 hg debugextensions
583 583
584 584 show information about active extensions
585 585
586 586 options:
587 587
588 588 (some details hidden, use --verbose to show complete help)
589 589
590 590
591 591 $ hg --verbose help debugextension
592 592 hg debugextensions
593 593
594 594 show information about active extensions
595 595
596 596 options:
597 597
598 598 -T --template TEMPLATE display with template (EXPERIMENTAL)
599 599
600 600 global options ([+] can be repeated):
601 601
602 602 -R --repository REPO repository root directory or name of overlay bundle
603 603 file
604 604 --cwd DIR change working directory
605 605 -y --noninteractive do not prompt, automatically pick the first choice for
606 606 all prompts
607 607 -q --quiet suppress output
608 608 -v --verbose enable additional output
609 609 --color TYPE when to colorize (boolean, always, auto, never, or
610 610 debug)
611 611 --config CONFIG [+] set/override config option (use 'section.name=value')
612 612 --debug enable debugging output
613 613 --debugger start debugger
614 614 --encoding ENCODE set the charset encoding (default: ascii)
615 615 --encodingmode MODE set the charset encoding mode (default: strict)
616 616 --traceback always print a traceback on exception
617 617 --time time how long the command takes
618 618 --profile print command execution profile
619 619 --version output version information and exit
620 620 -h --help display help and exit
621 621 --hidden consider hidden changesets
622 622 --pager TYPE when to paginate (boolean, always, auto, or never)
623 623 (default: auto)
624 624
625 625
626 626
627 627
628 628
629 629
630 630 $ hg --debug help debugextension
631 631 hg debugextensions
632 632
633 633 show information about active extensions
634 634
635 635 options:
636 636
637 637 -T --template TEMPLATE display with template (EXPERIMENTAL)
638 638
639 639 global options ([+] can be repeated):
640 640
641 641 -R --repository REPO repository root directory or name of overlay bundle
642 642 file
643 643 --cwd DIR change working directory
644 644 -y --noninteractive do not prompt, automatically pick the first choice for
645 645 all prompts
646 646 -q --quiet suppress output
647 647 -v --verbose enable additional output
648 648 --color TYPE when to colorize (boolean, always, auto, never, or
649 649 debug)
650 650 --config CONFIG [+] set/override config option (use 'section.name=value')
651 651 --debug enable debugging output
652 652 --debugger start debugger
653 653 --encoding ENCODE set the charset encoding (default: ascii)
654 654 --encodingmode MODE set the charset encoding mode (default: strict)
655 655 --traceback always print a traceback on exception
656 656 --time time how long the command takes
657 657 --profile print command execution profile
658 658 --version output version information and exit
659 659 -h --help display help and exit
660 660 --hidden consider hidden changesets
661 661 --pager TYPE when to paginate (boolean, always, auto, or never)
662 662 (default: auto)
663 663
664 664
665 665
666 666
667 667
668 668 $ echo 'debugextension = !' >> $HGRCPATH
669 669
670 670 Asking for help about a deprecated extension should do something useful:
671 671
672 672 $ hg help glog
673 673 'glog' is provided by the following extension:
674 674
675 675 graphlog command to view revision graphs from a shell (DEPRECATED)
676 676
677 677 (use 'hg help extensions' for information on enabling extensions)
678 678
679 679 Extension module help vs command help:
680 680
681 681 $ echo 'extdiff =' >> $HGRCPATH
682 682 $ hg help extdiff
683 683 hg extdiff [OPT]... [FILE]...
684 684
685 685 use external program to diff repository (or selected files)
686 686
687 687 Show differences between revisions for the specified files, using an
688 688 external program. The default program used is diff, with default options
689 689 "-Npru".
690 690
691 691 To select a different program, use the -p/--program option. The program
692 692 will be passed the names of two directories to compare. To pass additional
693 693 options to the program, use -o/--option. These will be passed before the
694 694 names of the directories to compare.
695 695
696 696 When two revision arguments are given, then changes are shown between
697 697 those revisions. If only one revision is specified then that revision is
698 698 compared to the working directory, and, when no revisions are specified,
699 699 the working directory files are compared to its parent.
700 700
701 701 (use 'hg help -e extdiff' to show help for the extdiff extension)
702 702
703 703 options ([+] can be repeated):
704 704
705 705 -p --program CMD comparison program to run
706 706 -o --option OPT [+] pass option to comparison program
707 707 -r --rev REV [+] revision
708 708 -c --change REV change made by revision
709 709 --patch compare patches for two revisions
710 710 -I --include PATTERN [+] include names matching the given patterns
711 711 -X --exclude PATTERN [+] exclude names matching the given patterns
712 712 -S --subrepos recurse into subrepositories
713 713
714 714 (some details hidden, use --verbose to show complete help)
715 715
716 716
717 717
718 718
719 719
720 720
721 721
722 722
723 723
724 724
725 725 $ hg help --extension extdiff
726 726 extdiff extension - command to allow external programs to compare revisions
727 727
728 728 The extdiff Mercurial extension allows you to use external programs to compare
729 729 revisions, or revision with working directory. The external diff programs are
730 730 called with a configurable set of options and two non-option arguments: paths
731 731 to directories containing snapshots of files to compare.
732 732
733 733 If there is more than one file being compared and the "child" revision is the
734 734 working directory, any modifications made in the external diff program will be
735 735 copied back to the working directory from the temporary directory.
736 736
737 737 The extdiff extension also allows you to configure new diff commands, so you
738 738 do not need to type 'hg extdiff -p kdiff3' always.
739 739
740 740 [extdiff]
741 741 # add new command that runs GNU diff(1) in 'context diff' mode
742 742 cdiff = gdiff -Nprc5
743 743 ## or the old way:
744 744 #cmd.cdiff = gdiff
745 745 #opts.cdiff = -Nprc5
746 746
747 747 # add new command called meld, runs meld (no need to name twice). If
748 748 # the meld executable is not available, the meld tool in [merge-tools]
749 749 # will be used, if available
750 750 meld =
751 751
752 752 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
753 753 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
754 754 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
755 755 # your .vimrc
756 756 vimdiff = gvim -f "+next" \
757 757 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
758 758
759 759 Tool arguments can include variables that are expanded at runtime:
760 760
761 761 $parent1, $plabel1 - filename, descriptive label of first parent
762 762 $child, $clabel - filename, descriptive label of child revision
763 763 $parent2, $plabel2 - filename, descriptive label of second parent
764 764 $root - repository root
765 765 $parent is an alias for $parent1.
766 766
767 767 The extdiff extension will look in your [diff-tools] and [merge-tools]
768 768 sections for diff tool arguments, when none are specified in [extdiff].
769 769
770 770 [extdiff]
771 771 kdiff3 =
772 772
773 773 [diff-tools]
774 774 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
775 775
776 776 You can use -I/-X and list of file or directory names like normal 'hg diff'
777 777 command. The extdiff extension makes snapshots of only needed files, so
778 778 running the external diff program will actually be pretty fast (at least
779 779 faster than having to compare the entire tree).
780 780
781 781 list of commands:
782 782
783 783 extdiff use external program to diff repository (or selected files)
784 784
785 785 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
786 786
787 787
788 788
789 789
790 790
791 791
792 792
793 793
794 794
795 795
796 796
797 797
798 798
799 799
800 800
801 801
802 802 $ echo 'extdiff = !' >> $HGRCPATH
803 803
804 804 Test help topic with same name as extension
805 805
806 806 $ cat > multirevs.py <<EOF
807 807 > from mercurial import commands, registrar
808 808 > cmdtable = {}
809 809 > command = registrar.command(cmdtable)
810 810 > """multirevs extension
811 811 > Big multi-line module docstring."""
812 812 > @command(b'multirevs', [], b'ARG', norepo=True)
813 813 > def multirevs(ui, repo, arg, *args, **opts):
814 814 > """multirevs command"""
815 815 > pass
816 816 > EOF
817 817 $ echo "multirevs = multirevs.py" >> $HGRCPATH
818 818
819 819 $ hg help multirevs | tail
820 820 used):
821 821
822 822 hg update :@
823 823
824 824 - Show diff between tags 1.3 and 1.5 (this works because the first and the
825 825 last revisions of the revset are used):
826 826
827 827 hg diff -r 1.3::1.5
828 828
829 829 use 'hg help -c multirevs' to see help for the multirevs command
830 830
831 831
832 832
833 833
834 834
835 835
836 836 $ hg help -c multirevs
837 837 hg multirevs ARG
838 838
839 839 multirevs command
840 840
841 841 (some details hidden, use --verbose to show complete help)
842 842
843 843
844 844
845 845 $ hg multirevs
846 846 hg multirevs: invalid arguments
847 847 hg multirevs ARG
848 848
849 849 multirevs command
850 850
851 851 (use 'hg multirevs -h' to show more help)
852 852 [255]
853 853
854 854
855 855
856 856 $ echo "multirevs = !" >> $HGRCPATH
857 857
858 858 Issue811: Problem loading extensions twice (by site and by user)
859 859
860 860 $ cat <<EOF >> $HGRCPATH
861 861 > mq =
862 862 > strip =
863 863 > hgext.mq =
864 864 > hgext/mq =
865 865 > EOF
866 866
867 867 Show extensions:
868 868 (note that mq force load strip, also checking it's not loaded twice)
869 869
870 870 #if no-extraextensions
871 871 $ hg debugextensions
872 872 mq
873 873 strip
874 874 #endif
875 875
876 876 For extensions, which name matches one of its commands, help
877 877 message should ask '-v -e' to get list of built-in aliases
878 878 along with extension help itself
879 879
880 880 $ mkdir $TESTTMP/d
881 881 $ cat > $TESTTMP/d/dodo.py <<EOF
882 882 > """
883 883 > This is an awesome 'dodo' extension. It does nothing and
884 884 > writes 'Foo foo'
885 885 > """
886 886 > from mercurial import commands, registrar
887 887 > cmdtable = {}
888 888 > command = registrar.command(cmdtable)
889 889 > @command(b'dodo', [], b'hg dodo')
890 890 > def dodo(ui, *args, **kwargs):
891 891 > """Does nothing"""
892 892 > ui.write(b"I do nothing. Yay\\n")
893 893 > @command(b'foofoo', [], b'hg foofoo')
894 894 > def foofoo(ui, *args, **kwargs):
895 895 > """Writes 'Foo foo'"""
896 896 > ui.write(b"Foo foo\\n")
897 897 > EOF
898 898 $ dodopath=$TESTTMP/d/dodo.py
899 899
900 900 $ echo "dodo = $dodopath" >> $HGRCPATH
901 901
902 902 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
903 903 $ hg help -e dodo
904 904 dodo extension -
905 905
906 906 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
907 907
908 908 list of commands:
909 909
910 910 dodo Does nothing
911 911 foofoo Writes 'Foo foo'
912 912
913 913 (use 'hg help -v -e dodo' to show built-in aliases and global options)
914 914
915 915 Make sure that '-v -e' prints list of built-in aliases along with
916 916 extension help itself
917 917 $ hg help -v -e dodo
918 918 dodo extension -
919 919
920 920 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
921 921
922 922 list of commands:
923 923
924 924 dodo Does nothing
925 925 foofoo Writes 'Foo foo'
926 926
927 927 global options ([+] can be repeated):
928 928
929 929 -R --repository REPO repository root directory or name of overlay bundle
930 930 file
931 931 --cwd DIR change working directory
932 932 -y --noninteractive do not prompt, automatically pick the first choice for
933 933 all prompts
934 934 -q --quiet suppress output
935 935 -v --verbose enable additional output
936 936 --color TYPE when to colorize (boolean, always, auto, never, or
937 937 debug)
938 938 --config CONFIG [+] set/override config option (use 'section.name=value')
939 939 --debug enable debugging output
940 940 --debugger start debugger
941 941 --encoding ENCODE set the charset encoding (default: ascii)
942 942 --encodingmode MODE set the charset encoding mode (default: strict)
943 943 --traceback always print a traceback on exception
944 944 --time time how long the command takes
945 945 --profile print command execution profile
946 946 --version output version information and exit
947 947 -h --help display help and exit
948 948 --hidden consider hidden changesets
949 949 --pager TYPE when to paginate (boolean, always, auto, or never)
950 950 (default: auto)
951 951
952 952 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
953 953 $ hg help -v dodo
954 954 hg dodo
955 955
956 956 Does nothing
957 957
958 958 (use 'hg help -e dodo' to show help for the dodo extension)
959 959
960 960 options:
961 961
962 962 --mq operate on patch repository
963 963
964 964 global options ([+] can be repeated):
965 965
966 966 -R --repository REPO repository root directory or name of overlay bundle
967 967 file
968 968 --cwd DIR change working directory
969 969 -y --noninteractive do not prompt, automatically pick the first choice for
970 970 all prompts
971 971 -q --quiet suppress output
972 972 -v --verbose enable additional output
973 973 --color TYPE when to colorize (boolean, always, auto, never, or
974 974 debug)
975 975 --config CONFIG [+] set/override config option (use 'section.name=value')
976 976 --debug enable debugging output
977 977 --debugger start debugger
978 978 --encoding ENCODE set the charset encoding (default: ascii)
979 979 --encodingmode MODE set the charset encoding mode (default: strict)
980 980 --traceback always print a traceback on exception
981 981 --time time how long the command takes
982 982 --profile print command execution profile
983 983 --version output version information and exit
984 984 -h --help display help and exit
985 985 --hidden consider hidden changesets
986 986 --pager TYPE when to paginate (boolean, always, auto, or never)
987 987 (default: auto)
988 988
989 989 In case when extension name doesn't match any of its commands,
990 990 help message should ask for '-v' to get list of built-in aliases
991 991 along with extension help
992 992 $ cat > $TESTTMP/d/dudu.py <<EOF
993 993 > """
994 994 > This is an awesome 'dudu' extension. It does something and
995 995 > also writes 'Beep beep'
996 996 > """
997 997 > from mercurial import commands, registrar
998 998 > cmdtable = {}
999 999 > command = registrar.command(cmdtable)
1000 1000 > @command(b'something', [], b'hg something')
1001 1001 > def something(ui, *args, **kwargs):
1002 1002 > """Does something"""
1003 1003 > ui.write(b"I do something. Yaaay\\n")
1004 1004 > @command(b'beep', [], b'hg beep')
1005 1005 > def beep(ui, *args, **kwargs):
1006 1006 > """Writes 'Beep beep'"""
1007 1007 > ui.write(b"Beep beep\\n")
1008 1008 > EOF
1009 1009 $ dudupath=$TESTTMP/d/dudu.py
1010 1010
1011 1011 $ echo "dudu = $dudupath" >> $HGRCPATH
1012 1012
1013 1013 $ hg help -e dudu
1014 1014 dudu extension -
1015 1015
1016 1016 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1017 1017 beep'
1018 1018
1019 1019 list of commands:
1020 1020
1021 1021 beep Writes 'Beep beep'
1022 1022 something Does something
1023 1023
1024 1024 (use 'hg help -v dudu' to show built-in aliases and global options)
1025 1025
1026 1026 In case when extension name doesn't match any of its commands,
1027 1027 help options '-v' and '-v -e' should be equivalent
1028 1028 $ hg help -v dudu
1029 1029 dudu extension -
1030 1030
1031 1031 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1032 1032 beep'
1033 1033
1034 1034 list of commands:
1035 1035
1036 1036 beep Writes 'Beep beep'
1037 1037 something Does something
1038 1038
1039 1039 global options ([+] can be repeated):
1040 1040
1041 1041 -R --repository REPO repository root directory or name of overlay bundle
1042 1042 file
1043 1043 --cwd DIR change working directory
1044 1044 -y --noninteractive do not prompt, automatically pick the first choice for
1045 1045 all prompts
1046 1046 -q --quiet suppress output
1047 1047 -v --verbose enable additional output
1048 1048 --color TYPE when to colorize (boolean, always, auto, never, or
1049 1049 debug)
1050 1050 --config CONFIG [+] set/override config option (use 'section.name=value')
1051 1051 --debug enable debugging output
1052 1052 --debugger start debugger
1053 1053 --encoding ENCODE set the charset encoding (default: ascii)
1054 1054 --encodingmode MODE set the charset encoding mode (default: strict)
1055 1055 --traceback always print a traceback on exception
1056 1056 --time time how long the command takes
1057 1057 --profile print command execution profile
1058 1058 --version output version information and exit
1059 1059 -h --help display help and exit
1060 1060 --hidden consider hidden changesets
1061 1061 --pager TYPE when to paginate (boolean, always, auto, or never)
1062 1062 (default: auto)
1063 1063
1064 1064 $ hg help -v -e dudu
1065 1065 dudu extension -
1066 1066
1067 1067 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1068 1068 beep'
1069 1069
1070 1070 list of commands:
1071 1071
1072 1072 beep Writes 'Beep beep'
1073 1073 something Does something
1074 1074
1075 1075 global options ([+] can be repeated):
1076 1076
1077 1077 -R --repository REPO repository root directory or name of overlay bundle
1078 1078 file
1079 1079 --cwd DIR change working directory
1080 1080 -y --noninteractive do not prompt, automatically pick the first choice for
1081 1081 all prompts
1082 1082 -q --quiet suppress output
1083 1083 -v --verbose enable additional output
1084 1084 --color TYPE when to colorize (boolean, always, auto, never, or
1085 1085 debug)
1086 1086 --config CONFIG [+] set/override config option (use 'section.name=value')
1087 1087 --debug enable debugging output
1088 1088 --debugger start debugger
1089 1089 --encoding ENCODE set the charset encoding (default: ascii)
1090 1090 --encodingmode MODE set the charset encoding mode (default: strict)
1091 1091 --traceback always print a traceback on exception
1092 1092 --time time how long the command takes
1093 1093 --profile print command execution profile
1094 1094 --version output version information and exit
1095 1095 -h --help display help and exit
1096 1096 --hidden consider hidden changesets
1097 1097 --pager TYPE when to paginate (boolean, always, auto, or never)
1098 1098 (default: auto)
1099 1099
1100 1100 Disabled extension commands:
1101 1101
1102 1102 $ ORGHGRCPATH=$HGRCPATH
1103 1103 $ HGRCPATH=
1104 1104 $ export HGRCPATH
1105 1105 $ hg help email
1106 1106 'email' is provided by the following extension:
1107 1107
1108 1108 patchbomb command to send changesets as (a series of) patch emails
1109 1109
1110 1110 (use 'hg help extensions' for information on enabling extensions)
1111 1111
1112 1112
1113 1113 $ hg qdel
1114 1114 hg: unknown command 'qdel'
1115 1115 'qdelete' is provided by the following extension:
1116 1116
1117 1117 mq manage a stack of patches
1118 1118
1119 1119 (use 'hg help extensions' for information on enabling extensions)
1120 1120 [255]
1121 1121
1122 1122
1123 1123 $ hg churn
1124 1124 hg: unknown command 'churn'
1125 1125 'churn' is provided by the following extension:
1126 1126
1127 1127 churn command to display statistics about repository history
1128 1128
1129 1129 (use 'hg help extensions' for information on enabling extensions)
1130 1130 [255]
1131 1131
1132 1132
1133 1133
1134 1134 Disabled extensions:
1135 1135
1136 1136 $ hg help churn
1137 1137 churn extension - command to display statistics about repository history
1138 1138
1139 1139 (use 'hg help extensions' for information on enabling extensions)
1140 1140
1141 1141 $ hg help patchbomb
1142 1142 patchbomb extension - command to send changesets as (a series of) patch emails
1143 1143
1144 1144 The series is started off with a "[PATCH 0 of N]" introduction, which
1145 1145 describes the series as a whole.
1146 1146
1147 1147 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1148 1148 line of the changeset description as the subject text. The message contains
1149 1149 two or three body parts:
1150 1150
1151 1151 - The changeset description.
1152 1152 - [Optional] The result of running diffstat on the patch.
1153 1153 - The patch itself, as generated by 'hg export'.
1154 1154
1155 1155 Each message refers to the first in the series using the In-Reply-To and
1156 1156 References headers, so they will show up as a sequence in threaded mail and
1157 1157 news readers, and in mail archives.
1158 1158
1159 1159 To configure other defaults, add a section like this to your configuration
1160 1160 file:
1161 1161
1162 1162 [email]
1163 1163 from = My Name <my@email>
1164 1164 to = recipient1, recipient2, ...
1165 1165 cc = cc1, cc2, ...
1166 1166 bcc = bcc1, bcc2, ...
1167 1167 reply-to = address1, address2, ...
1168 1168
1169 1169 Use "[patchbomb]" as configuration section name if you need to override global
1170 1170 "[email]" address settings.
1171 1171
1172 1172 Then you can use the 'hg email' command to mail a series of changesets as a
1173 1173 patchbomb.
1174 1174
1175 1175 You can also either configure the method option in the email section to be a
1176 1176 sendmail compatible mailer or fill out the [smtp] section so that the
1177 1177 patchbomb extension can automatically send patchbombs directly from the
1178 1178 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1179 1179
1180 1180 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1181 1181 supply one via configuration or the command line. You can override this to
1182 1182 never prompt by configuring an empty value:
1183 1183
1184 1184 [email]
1185 1185 cc =
1186 1186
1187 1187 You can control the default inclusion of an introduction message with the
1188 1188 "patchbomb.intro" configuration option. The configuration is always
1189 1189 overwritten by command line flags like --intro and --desc:
1190 1190
1191 1191 [patchbomb]
1192 1192 intro=auto # include introduction message if more than 1 patch (default)
1193 1193 intro=never # never include an introduction message
1194 1194 intro=always # always include an introduction message
1195 1195
1196 1196 You can specify a template for flags to be added in subject prefixes. Flags
1197 1197 specified by --flag option are exported as "{flags}" keyword:
1198 1198
1199 1199 [patchbomb]
1200 1200 flagtemplate = "{separate(' ',
1201 1201 ifeq(branch, 'default', '', branch|upper),
1202 1202 flags)}"
1203 1203
1204 1204 You can set patchbomb to always ask for confirmation by setting
1205 1205 "patchbomb.confirm" to true.
1206 1206
1207 1207 (use 'hg help extensions' for information on enabling extensions)
1208 1208
1209 1209
1210 1210 Broken disabled extension and command:
1211 1211
1212 1212 $ mkdir hgext
1213 1213 $ echo > hgext/__init__.py
1214 1214 $ cat > hgext/broken.py <<EOF
1215 1215 > "broken extension'
1216 1216 > EOF
1217 1217 $ cat > path.py <<EOF
1218 1218 > import os, sys
1219 1219 > sys.path.insert(0, os.environ['HGEXTPATH'])
1220 1220 > EOF
1221 1221 $ HGEXTPATH=`pwd`
1222 1222 $ export HGEXTPATH
1223 1223
1224 1224 $ hg --config extensions.path=./path.py help broken
1225 1225 broken extension - (no help text available)
1226 1226
1227 1227 (use 'hg help extensions' for information on enabling extensions)
1228 1228
1229 1229
1230 1230 $ cat > hgext/forest.py <<EOF
1231 1231 > cmdtable = None
1232 > @command()
1233 > def f():
1234 > pass
1235 > @command(123)
1236 > def g():
1237 > pass
1232 1238 > EOF
1233 1239 $ hg --config extensions.path=./path.py help foo > /dev/null
1234 warning: error finding commands in $TESTTMP/hgext/forest.py
1235 1240 abort: no such help topic: foo
1236 1241 (try 'hg help --keyword foo')
1237 1242 [255]
1238 1243
1239 1244 $ cat > throw.py <<EOF
1240 1245 > from mercurial import commands, registrar, util
1241 1246 > cmdtable = {}
1242 1247 > command = registrar.command(cmdtable)
1243 1248 > class Bogon(Exception): pass
1244 1249 > @command(b'throw', [], b'hg throw', norepo=True)
1245 1250 > def throw(ui, **opts):
1246 1251 > """throws an exception"""
1247 1252 > raise Bogon()
1248 1253 > EOF
1249 1254
1250 1255 No declared supported version, extension complains:
1251 1256 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1252 1257 ** Unknown exception encountered with possibly-broken third-party extension throw
1253 1258 ** which supports versions unknown of Mercurial.
1254 1259 ** Please disable throw and try your action again.
1255 1260 ** If that fixes the bug please report it to the extension author.
1256 1261 ** Python * (glob)
1257 1262 ** Mercurial Distributed SCM * (glob)
1258 1263 ** Extensions loaded: throw
1259 1264
1260 1265 empty declaration of supported version, extension complains:
1261 1266 $ echo "testedwith = ''" >> throw.py
1262 1267 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1263 1268 ** Unknown exception encountered with possibly-broken third-party extension throw
1264 1269 ** which supports versions unknown of Mercurial.
1265 1270 ** Please disable throw and try your action again.
1266 1271 ** If that fixes the bug please report it to the extension author.
1267 1272 ** Python * (glob)
1268 1273 ** Mercurial Distributed SCM (*) (glob)
1269 1274 ** Extensions loaded: throw
1270 1275
1271 1276 If the extension specifies a buglink, show that:
1272 1277 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1273 1278 $ rm -f throw.pyc throw.pyo
1274 1279 $ rm -Rf __pycache__
1275 1280 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1276 1281 ** Unknown exception encountered with possibly-broken third-party extension throw
1277 1282 ** which supports versions unknown of Mercurial.
1278 1283 ** Please disable throw and try your action again.
1279 1284 ** If that fixes the bug please report it to http://example.com/bts
1280 1285 ** Python * (glob)
1281 1286 ** Mercurial Distributed SCM (*) (glob)
1282 1287 ** Extensions loaded: throw
1283 1288
1284 1289 If the extensions declare outdated versions, accuse the older extension first:
1285 1290 $ echo "from mercurial import util" >> older.py
1286 1291 $ echo "util.version = lambda:b'2.2'" >> older.py
1287 1292 $ echo "testedwith = b'1.9.3'" >> older.py
1288 1293 $ echo "testedwith = b'2.1.1'" >> throw.py
1289 1294 $ rm -f throw.pyc throw.pyo
1290 1295 $ rm -Rf __pycache__
1291 1296 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1292 1297 > throw 2>&1 | egrep '^\*\*'
1293 1298 ** Unknown exception encountered with possibly-broken third-party extension older
1294 1299 ** which supports versions 1.9 of Mercurial.
1295 1300 ** Please disable older and try your action again.
1296 1301 ** If that fixes the bug please report it to the extension author.
1297 1302 ** Python * (glob)
1298 1303 ** Mercurial Distributed SCM (version 2.2)
1299 1304 ** Extensions loaded: throw, older
1300 1305
1301 1306 One extension only tested with older, one only with newer versions:
1302 1307 $ echo "util.version = lambda:b'2.1'" >> older.py
1303 1308 $ rm -f older.pyc older.pyo
1304 1309 $ rm -Rf __pycache__
1305 1310 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1306 1311 > throw 2>&1 | egrep '^\*\*'
1307 1312 ** Unknown exception encountered with possibly-broken third-party extension older
1308 1313 ** which supports versions 1.9 of Mercurial.
1309 1314 ** Please disable older and try your action again.
1310 1315 ** If that fixes the bug please report it to the extension author.
1311 1316 ** Python * (glob)
1312 1317 ** Mercurial Distributed SCM (version 2.1)
1313 1318 ** Extensions loaded: throw, older
1314 1319
1315 1320 Older extension is tested with current version, the other only with newer:
1316 1321 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1317 1322 $ rm -f older.pyc older.pyo
1318 1323 $ rm -Rf __pycache__
1319 1324 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1320 1325 > throw 2>&1 | egrep '^\*\*'
1321 1326 ** Unknown exception encountered with possibly-broken third-party extension throw
1322 1327 ** which supports versions 2.1 of Mercurial.
1323 1328 ** Please disable throw and try your action again.
1324 1329 ** If that fixes the bug please report it to http://example.com/bts
1325 1330 ** Python * (glob)
1326 1331 ** Mercurial Distributed SCM (version 1.9.3)
1327 1332 ** Extensions loaded: throw, older
1328 1333
1329 1334 Ability to point to a different point
1330 1335 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1331 1336 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1332 1337 ** unknown exception encountered, please report by visiting
1333 1338 ** Your Local Goat Lenders
1334 1339 ** Python * (glob)
1335 1340 ** Mercurial Distributed SCM (*) (glob)
1336 1341 ** Extensions loaded: throw, older
1337 1342
1338 1343 Declare the version as supporting this hg version, show regular bts link:
1339 1344 $ hgver=`hg debuginstall -T '{hgver}'`
1340 1345 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1341 1346 $ if [ -z "$hgver" ]; then
1342 1347 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1343 1348 > fi
1344 1349 $ rm -f throw.pyc throw.pyo
1345 1350 $ rm -Rf __pycache__
1346 1351 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1347 1352 ** unknown exception encountered, please report by visiting
1348 1353 ** https://mercurial-scm.org/wiki/BugTracker
1349 1354 ** Python * (glob)
1350 1355 ** Mercurial Distributed SCM (*) (glob)
1351 1356 ** Extensions loaded: throw
1352 1357
1353 1358 Patch version is ignored during compatibility check
1354 1359 $ echo "testedwith = b'3.2'" >> throw.py
1355 1360 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1356 1361 $ rm -f throw.pyc throw.pyo
1357 1362 $ rm -Rf __pycache__
1358 1363 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1359 1364 ** unknown exception encountered, please report by visiting
1360 1365 ** https://mercurial-scm.org/wiki/BugTracker
1361 1366 ** Python * (glob)
1362 1367 ** Mercurial Distributed SCM (*) (glob)
1363 1368 ** Extensions loaded: throw
1364 1369
1365 1370 Test version number support in 'hg version':
1366 1371 $ echo '__version__ = (1, 2, 3)' >> throw.py
1367 1372 $ rm -f throw.pyc throw.pyo
1368 1373 $ rm -Rf __pycache__
1369 1374 $ hg version -v
1370 1375 Mercurial Distributed SCM (version *) (glob)
1371 1376 (see https://mercurial-scm.org for more information)
1372 1377
1373 1378 Copyright (C) 2005-* Matt Mackall and others (glob)
1374 1379 This is free software; see the source for copying conditions. There is NO
1375 1380 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1376 1381
1377 1382 Enabled extensions:
1378 1383
1379 1384
1380 1385 $ hg version -v --config extensions.throw=throw.py
1381 1386 Mercurial Distributed SCM (version *) (glob)
1382 1387 (see https://mercurial-scm.org for more information)
1383 1388
1384 1389 Copyright (C) 2005-* Matt Mackall and others (glob)
1385 1390 This is free software; see the source for copying conditions. There is NO
1386 1391 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1387 1392
1388 1393 Enabled extensions:
1389 1394
1390 1395 throw external 1.2.3
1391 1396 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1392 1397 $ rm -f throw.pyc throw.pyo
1393 1398 $ rm -Rf __pycache__
1394 1399 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1395 1400 Mercurial Distributed SCM (version *) (glob)
1396 1401 (see https://mercurial-scm.org for more information)
1397 1402
1398 1403 Copyright (C) 2005-* Matt Mackall and others (glob)
1399 1404 This is free software; see the source for copying conditions. There is NO
1400 1405 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1401 1406
1402 1407 Enabled extensions:
1403 1408
1404 1409 throw external 1.twentythree
1405 1410 strip internal
1406 1411
1407 1412 $ hg version -q --config extensions.throw=throw.py
1408 1413 Mercurial Distributed SCM (version *) (glob)
1409 1414
1410 1415 Test template output:
1411 1416
1412 1417 $ hg version --config extensions.strip= -T'{extensions}'
1413 1418 strip
1414 1419
1415 1420 Test JSON output of version:
1416 1421
1417 1422 $ hg version -Tjson
1418 1423 [
1419 1424 {
1420 1425 "extensions": [],
1421 1426 "ver": "*" (glob)
1422 1427 }
1423 1428 ]
1424 1429
1425 1430 $ hg version --config extensions.throw=throw.py -Tjson
1426 1431 [
1427 1432 {
1428 1433 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1429 1434 "ver": "3.2.2"
1430 1435 }
1431 1436 ]
1432 1437
1433 1438 $ hg version --config extensions.strip= -Tjson
1434 1439 [
1435 1440 {
1436 1441 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1437 1442 "ver": "*" (glob)
1438 1443 }
1439 1444 ]
1440 1445
1441 1446 Test template output of version:
1442 1447
1443 1448 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1444 1449 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1445 1450 throw 1.twentythree (external)
1446 1451 strip (internal)
1447 1452
1448 1453 Refuse to load extensions with minimum version requirements
1449 1454
1450 1455 $ cat > minversion1.py << EOF
1451 1456 > from mercurial import util
1452 1457 > util.version = lambda: b'3.5.2'
1453 1458 > minimumhgversion = b'3.6'
1454 1459 > EOF
1455 1460 $ hg --config extensions.minversion=minversion1.py version
1456 1461 (third party extension minversion requires version 3.6 or newer of Mercurial; disabling)
1457 1462 Mercurial Distributed SCM (version 3.5.2)
1458 1463 (see https://mercurial-scm.org for more information)
1459 1464
1460 1465 Copyright (C) 2005-* Matt Mackall and others (glob)
1461 1466 This is free software; see the source for copying conditions. There is NO
1462 1467 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1463 1468
1464 1469 $ cat > minversion2.py << EOF
1465 1470 > from mercurial import util
1466 1471 > util.version = lambda: b'3.6'
1467 1472 > minimumhgversion = b'3.7'
1468 1473 > EOF
1469 1474 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1470 1475 (third party extension minversion requires version 3.7 or newer of Mercurial; disabling)
1471 1476
1472 1477 Can load version that is only off by point release
1473 1478
1474 1479 $ cat > minversion2.py << EOF
1475 1480 > from mercurial import util
1476 1481 > util.version = lambda: b'3.6.1'
1477 1482 > minimumhgversion = b'3.6'
1478 1483 > EOF
1479 1484 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1480 1485 [1]
1481 1486
1482 1487 Can load minimum version identical to current
1483 1488
1484 1489 $ cat > minversion3.py << EOF
1485 1490 > from mercurial import util
1486 1491 > util.version = lambda: b'3.5'
1487 1492 > minimumhgversion = b'3.5'
1488 1493 > EOF
1489 1494 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1490 1495 [1]
1491 1496
1492 1497 Restore HGRCPATH
1493 1498
1494 1499 $ HGRCPATH=$ORGHGRCPATH
1495 1500 $ export HGRCPATH
1496 1501
1497 1502 Commands handling multiple repositories at a time should invoke only
1498 1503 "reposetup()" of extensions enabling in the target repository.
1499 1504
1500 1505 $ mkdir reposetup-test
1501 1506 $ cd reposetup-test
1502 1507
1503 1508 $ cat > $TESTTMP/reposetuptest.py <<EOF
1504 1509 > from mercurial import extensions
1505 1510 > def reposetup(ui, repo):
1506 1511 > ui.write(b'reposetup() for %s\n' % (repo.root))
1507 1512 > ui.flush()
1508 1513 > EOF
1509 1514 $ hg init src
1510 1515 $ echo a > src/a
1511 1516 $ hg -R src commit -Am '#0 at src/a'
1512 1517 adding a
1513 1518 $ echo '[extensions]' >> src/.hg/hgrc
1514 1519 $ echo '# enable extension locally' >> src/.hg/hgrc
1515 1520 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1516 1521 $ hg -R src status
1517 1522 reposetup() for $TESTTMP/reposetup-test/src
1518 1523 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1519 1524
1520 1525 $ hg --cwd src debugextensions
1521 1526 reposetup() for $TESTTMP/reposetup-test/src
1522 1527 dodo (untested!)
1523 1528 dudu (untested!)
1524 1529 mq
1525 1530 reposetuptest (untested!)
1526 1531 strip
1527 1532
1528 1533 $ hg clone -U src clone-dst1
1529 1534 reposetup() for $TESTTMP/reposetup-test/src
1530 1535 $ hg init push-dst1
1531 1536 $ hg -q -R src push push-dst1
1532 1537 reposetup() for $TESTTMP/reposetup-test/src
1533 1538 $ hg init pull-src1
1534 1539 $ hg -q -R pull-src1 pull src
1535 1540 reposetup() for $TESTTMP/reposetup-test/src
1536 1541
1537 1542 $ cat <<EOF >> $HGRCPATH
1538 1543 > [extensions]
1539 1544 > # disable extension globally and explicitly
1540 1545 > reposetuptest = !
1541 1546 > EOF
1542 1547 $ hg clone -U src clone-dst2
1543 1548 reposetup() for $TESTTMP/reposetup-test/src
1544 1549 $ hg init push-dst2
1545 1550 $ hg -q -R src push push-dst2
1546 1551 reposetup() for $TESTTMP/reposetup-test/src
1547 1552 $ hg init pull-src2
1548 1553 $ hg -q -R pull-src2 pull src
1549 1554 reposetup() for $TESTTMP/reposetup-test/src
1550 1555
1551 1556 $ cat <<EOF >> $HGRCPATH
1552 1557 > [extensions]
1553 1558 > # enable extension globally
1554 1559 > reposetuptest = $TESTTMP/reposetuptest.py
1555 1560 > EOF
1556 1561 $ hg clone -U src clone-dst3
1557 1562 reposetup() for $TESTTMP/reposetup-test/src
1558 1563 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1559 1564 $ hg init push-dst3
1560 1565 reposetup() for $TESTTMP/reposetup-test/push-dst3
1561 1566 $ hg -q -R src push push-dst3
1562 1567 reposetup() for $TESTTMP/reposetup-test/src
1563 1568 reposetup() for $TESTTMP/reposetup-test/push-dst3
1564 1569 $ hg init pull-src3
1565 1570 reposetup() for $TESTTMP/reposetup-test/pull-src3
1566 1571 $ hg -q -R pull-src3 pull src
1567 1572 reposetup() for $TESTTMP/reposetup-test/pull-src3
1568 1573 reposetup() for $TESTTMP/reposetup-test/src
1569 1574
1570 1575 $ echo '[extensions]' >> src/.hg/hgrc
1571 1576 $ echo '# disable extension locally' >> src/.hg/hgrc
1572 1577 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1573 1578 $ hg clone -U src clone-dst4
1574 1579 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1575 1580 $ hg init push-dst4
1576 1581 reposetup() for $TESTTMP/reposetup-test/push-dst4
1577 1582 $ hg -q -R src push push-dst4
1578 1583 reposetup() for $TESTTMP/reposetup-test/push-dst4
1579 1584 $ hg init pull-src4
1580 1585 reposetup() for $TESTTMP/reposetup-test/pull-src4
1581 1586 $ hg -q -R pull-src4 pull src
1582 1587 reposetup() for $TESTTMP/reposetup-test/pull-src4
1583 1588
1584 1589 disabling in command line overlays with all configuration
1585 1590 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1586 1591 $ hg --config extensions.reposetuptest=! init push-dst5
1587 1592 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1588 1593 $ hg --config extensions.reposetuptest=! init pull-src5
1589 1594 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1590 1595
1591 1596 $ cat <<EOF >> $HGRCPATH
1592 1597 > [extensions]
1593 1598 > # disable extension globally and explicitly
1594 1599 > reposetuptest = !
1595 1600 > EOF
1596 1601 $ hg init parent
1597 1602 $ hg init parent/sub1
1598 1603 $ echo 1 > parent/sub1/1
1599 1604 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1600 1605 adding 1
1601 1606 $ hg init parent/sub2
1602 1607 $ hg init parent/sub2/sub21
1603 1608 $ echo 21 > parent/sub2/sub21/21
1604 1609 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1605 1610 adding 21
1606 1611 $ cat > parent/sub2/.hgsub <<EOF
1607 1612 > sub21 = sub21
1608 1613 > EOF
1609 1614 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1610 1615 adding .hgsub
1611 1616 $ hg init parent/sub3
1612 1617 $ echo 3 > parent/sub3/3
1613 1618 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1614 1619 adding 3
1615 1620 $ cat > parent/.hgsub <<EOF
1616 1621 > sub1 = sub1
1617 1622 > sub2 = sub2
1618 1623 > sub3 = sub3
1619 1624 > EOF
1620 1625 $ hg -R parent commit -Am '#0 at parent'
1621 1626 adding .hgsub
1622 1627 $ echo '[extensions]' >> parent/.hg/hgrc
1623 1628 $ echo '# enable extension locally' >> parent/.hg/hgrc
1624 1629 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1625 1630 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1626 1631 $ hg -R parent status -S -A
1627 1632 reposetup() for $TESTTMP/reposetup-test/parent
1628 1633 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1629 1634 C .hgsub
1630 1635 C .hgsubstate
1631 1636 C sub1/1
1632 1637 C sub2/.hgsub
1633 1638 C sub2/.hgsubstate
1634 1639 C sub2/sub21/21
1635 1640 C sub3/3
1636 1641
1637 1642 $ cd ..
1638 1643
1639 1644 Prohibit registration of commands that don't use @command (issue5137)
1640 1645
1641 1646 $ hg init deprecated
1642 1647 $ cd deprecated
1643 1648
1644 1649 $ cat <<EOF > deprecatedcmd.py
1645 1650 > def deprecatedcmd(repo, ui):
1646 1651 > pass
1647 1652 > cmdtable = {
1648 1653 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1649 1654 > }
1650 1655 > EOF
1651 1656 $ cat <<EOF > .hg/hgrc
1652 1657 > [extensions]
1653 1658 > deprecatedcmd = `pwd`/deprecatedcmd.py
1654 1659 > mq = !
1655 1660 > hgext.mq = !
1656 1661 > hgext/mq = !
1657 1662 > EOF
1658 1663
1659 1664 $ hg deprecatedcmd > /dev/null
1660 1665 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1661 1666 *** (use @command decorator to register 'deprecatedcmd')
1662 1667 hg: unknown command 'deprecatedcmd'
1663 1668 [255]
1664 1669
1665 1670 the extension shouldn't be loaded at all so the mq works:
1666 1671
1667 1672 $ hg qseries --config extensions.mq= > /dev/null
1668 1673 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1669 1674 *** (use @command decorator to register 'deprecatedcmd')
1670 1675
1671 1676 $ cd ..
1672 1677
1673 1678 Test synopsis and docstring extending
1674 1679
1675 1680 $ hg init exthelp
1676 1681 $ cat > exthelp.py <<EOF
1677 1682 > from mercurial import commands, extensions
1678 1683 > def exbookmarks(orig, *args, **opts):
1679 1684 > return orig(*args, **opts)
1680 1685 > def uisetup(ui):
1681 1686 > synopsis = b' GREPME [--foo] [-x]'
1682 1687 > docstring = '''
1683 1688 > GREPME make sure that this is in the help!
1684 1689 > '''
1685 1690 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1686 1691 > synopsis, docstring)
1687 1692 > EOF
1688 1693 $ abspath=`pwd`/exthelp.py
1689 1694 $ echo '[extensions]' >> $HGRCPATH
1690 1695 $ echo "exthelp = $abspath" >> $HGRCPATH
1691 1696 $ cd exthelp
1692 1697 $ hg help bookmarks | grep GREPME
1693 1698 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1694 1699 GREPME make sure that this is in the help!
1695 1700 $ cd ..
1696 1701
1697 1702 Show deprecation warning for the use of cmdutil.command
1698 1703
1699 1704 $ cat > nonregistrar.py <<EOF
1700 1705 > from mercurial import cmdutil
1701 1706 > cmdtable = {}
1702 1707 > command = cmdutil.command(cmdtable)
1703 1708 > @command(b'foo', [], norepo=True)
1704 1709 > def foo(ui):
1705 1710 > pass
1706 1711 > EOF
1707 1712
1708 1713 Prohibit the use of unicode strings as the default value of options
1709 1714
1710 1715 $ hg init $TESTTMP/opt-unicode-default
1711 1716
1712 1717 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1713 1718 > from mercurial import registrar
1714 1719 > cmdtable = {}
1715 1720 > command = registrar.command(cmdtable)
1716 1721 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1717 1722 > def ext(*args, **opts):
1718 1723 > print(opts[b'opt'])
1719 1724 > EOF
1720 1725 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1721 1726 > [extensions]
1722 1727 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1723 1728 > EOF
1724 1729 $ hg -R $TESTTMP/opt-unicode-default dummy
1725 1730 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode u'value' found in cmdtable.dummy
1726 1731 *** (use b'' to make it byte string)
1727 1732 hg: unknown command 'dummy'
1728 1733 (did you mean summary?)
1729 1734 [255]
General Comments 0
You need to be logged in to leave comments. Login now