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