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