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