##// END OF EJS Templates
dispatch: try and identify third-party extensions as sources of tracebacks...
Augie Fackler -
r16744:1c9f58a6 default
parent child Browse files
Show More
@@ -1,789 +1,827 b''
1 1 # dispatch.py - command dispatching 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 i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 class request(object):
15 15 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
16 16 ferr=None):
17 17 self.args = args
18 18 self.ui = ui
19 19 self.repo = repo
20 20
21 21 # input/output/error streams
22 22 self.fin = fin
23 23 self.fout = fout
24 24 self.ferr = ferr
25 25
26 26 def run():
27 27 "run the command in sys.argv"
28 28 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
29 29
30 30 def dispatch(req):
31 31 "run the command specified in req.args"
32 32 if req.ferr:
33 33 ferr = req.ferr
34 34 elif req.ui:
35 35 ferr = req.ui.ferr
36 36 else:
37 37 ferr = sys.stderr
38 38
39 39 try:
40 40 if not req.ui:
41 41 req.ui = uimod.ui()
42 42 if '--traceback' in req.args:
43 43 req.ui.setconfig('ui', 'traceback', 'on')
44 44
45 45 # set ui streams from the request
46 46 if req.fin:
47 47 req.ui.fin = req.fin
48 48 if req.fout:
49 49 req.ui.fout = req.fout
50 50 if req.ferr:
51 51 req.ui.ferr = req.ferr
52 52 except util.Abort, inst:
53 53 ferr.write(_("abort: %s\n") % inst)
54 54 if inst.hint:
55 55 ferr.write(_("(%s)\n") % inst.hint)
56 56 return -1
57 57 except error.ParseError, inst:
58 58 if len(inst.args) > 1:
59 59 ferr.write(_("hg: parse error at %s: %s\n") %
60 60 (inst.args[1], inst.args[0]))
61 61 else:
62 62 ferr.write(_("hg: parse error: %s\n") % inst.args[0])
63 63 return -1
64 64
65 65 return _runcatch(req)
66 66
67 67 def _runcatch(req):
68 68 def catchterm(*args):
69 69 raise error.SignalInterrupt
70 70
71 71 ui = req.ui
72 72 try:
73 73 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
74 74 num = getattr(signal, name, None)
75 75 if num:
76 76 signal.signal(num, catchterm)
77 77 except ValueError:
78 78 pass # happens if called in a thread
79 79
80 80 try:
81 81 try:
82 82 # enter the debugger before command execution
83 83 if '--debugger' in req.args:
84 84 ui.warn(_("entering debugger - "
85 85 "type c to continue starting hg or h for help\n"))
86 86 pdb.set_trace()
87 87 try:
88 88 return _dispatch(req)
89 89 finally:
90 90 ui.flush()
91 91 except: # re-raises
92 92 # enter the debugger when we hit an exception
93 93 if '--debugger' in req.args:
94 94 traceback.print_exc()
95 95 pdb.post_mortem(sys.exc_info()[2])
96 96 ui.traceback()
97 97 raise
98 98
99 99 # Global exception handling, alphabetically
100 100 # Mercurial-specific first, followed by built-in and library exceptions
101 101 except error.AmbiguousCommand, inst:
102 102 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
103 103 (inst.args[0], " ".join(inst.args[1])))
104 104 except error.ParseError, inst:
105 105 if len(inst.args) > 1:
106 106 ui.warn(_("hg: parse error at %s: %s\n") %
107 107 (inst.args[1], inst.args[0]))
108 108 else:
109 109 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
110 110 return -1
111 111 except error.LockHeld, inst:
112 112 if inst.errno == errno.ETIMEDOUT:
113 113 reason = _('timed out waiting for lock held by %s') % inst.locker
114 114 else:
115 115 reason = _('lock held by %s') % inst.locker
116 116 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
117 117 except error.LockUnavailable, inst:
118 118 ui.warn(_("abort: could not lock %s: %s\n") %
119 119 (inst.desc or inst.filename, inst.strerror))
120 120 except error.CommandError, inst:
121 121 if inst.args[0]:
122 122 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
123 123 commands.help_(ui, inst.args[0], full=False, command=True)
124 124 else:
125 125 ui.warn(_("hg: %s\n") % inst.args[1])
126 126 commands.help_(ui, 'shortlist')
127 127 except error.OutOfBandError, inst:
128 128 ui.warn(_("abort: remote error:\n"))
129 129 ui.warn(''.join(inst.args))
130 130 except error.RepoError, inst:
131 131 ui.warn(_("abort: %s!\n") % inst)
132 132 if inst.hint:
133 133 ui.warn(_("(%s)\n") % inst.hint)
134 134 except error.ResponseError, inst:
135 135 ui.warn(_("abort: %s") % inst.args[0])
136 136 if not isinstance(inst.args[1], basestring):
137 137 ui.warn(" %r\n" % (inst.args[1],))
138 138 elif not inst.args[1]:
139 139 ui.warn(_(" empty string\n"))
140 140 else:
141 141 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
142 142 except error.RevlogError, inst:
143 143 ui.warn(_("abort: %s!\n") % inst)
144 144 except error.SignalInterrupt:
145 145 ui.warn(_("killed!\n"))
146 146 except error.UnknownCommand, inst:
147 147 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
148 148 try:
149 149 # check if the command is in a disabled extension
150 150 # (but don't check for extensions themselves)
151 151 commands.help_(ui, inst.args[0], unknowncmd=True)
152 152 except error.UnknownCommand:
153 153 commands.help_(ui, 'shortlist')
154 154 except util.Abort, inst:
155 155 ui.warn(_("abort: %s\n") % inst)
156 156 if inst.hint:
157 157 ui.warn(_("(%s)\n") % inst.hint)
158 158 except ImportError, inst:
159 159 ui.warn(_("abort: %s!\n") % inst)
160 160 m = str(inst).split()[-1]
161 161 if m in "mpatch bdiff".split():
162 162 ui.warn(_("(did you forget to compile extensions?)\n"))
163 163 elif m in "zlib".split():
164 164 ui.warn(_("(is your Python install correct?)\n"))
165 165 except IOError, inst:
166 166 if util.safehasattr(inst, "code"):
167 167 ui.warn(_("abort: %s\n") % inst)
168 168 elif util.safehasattr(inst, "reason"):
169 169 try: # usually it is in the form (errno, strerror)
170 170 reason = inst.reason.args[1]
171 171 except (AttributeError, IndexError):
172 172 # it might be anything, for example a string
173 173 reason = inst.reason
174 174 ui.warn(_("abort: error: %s\n") % reason)
175 175 elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE:
176 176 if ui.debugflag:
177 177 ui.warn(_("broken pipe\n"))
178 178 elif getattr(inst, "strerror", None):
179 179 if getattr(inst, "filename", None):
180 180 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
181 181 else:
182 182 ui.warn(_("abort: %s\n") % inst.strerror)
183 183 else:
184 184 raise
185 185 except OSError, inst:
186 186 if getattr(inst, "filename", None):
187 187 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
188 188 else:
189 189 ui.warn(_("abort: %s\n") % inst.strerror)
190 190 except KeyboardInterrupt:
191 191 try:
192 192 ui.warn(_("interrupted!\n"))
193 193 except IOError, inst:
194 194 if inst.errno == errno.EPIPE:
195 195 if ui.debugflag:
196 196 ui.warn(_("\nbroken pipe\n"))
197 197 else:
198 198 raise
199 199 except MemoryError:
200 200 ui.warn(_("abort: out of memory\n"))
201 201 except SystemExit, inst:
202 202 # Commands shouldn't sys.exit directly, but give a return code.
203 203 # Just in case catch this and and pass exit code to caller.
204 204 return inst.code
205 205 except socket.error, inst:
206 206 ui.warn(_("abort: %s\n") % inst.args[-1])
207 207 except: # re-raises
208 ui.warn(_("** unknown exception encountered,"
209 " please report by visiting\n"))
210 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
211 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
212 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
213 % util.version())
214 ui.warn(_("** Extensions loaded: %s\n")
215 % ", ".join([x[0] for x in extensions.extensions()]))
208 myver = util.version()
209 # For compatibility checking, we discard the portion of the hg
210 # version after the + on the assumption that if a "normal
211 # user" is running a build with a + in it the packager
212 # probably built from fairly close to a tag and anyone with a
213 # 'make local' copy of hg (where the version number can be out
214 # of date) will be clueful enough to notice the implausible
215 # version number and try updating.
216 compare = myver.split('+')[0]
217 ct = tuplever(compare)
218 worst = None, ct, ''
219 for name, mod in extensions.extensions():
220 testedwith = getattr(mod, 'testedwith', 'unknown')
221 report = getattr(mod, 'buglink', _('the extension author.'))
222 if testedwith == 'unknown':
223 # We found an untested extension. It's likely the culprit.
224 worst = name, testedwith, report
225 break
226 if compare not in testedwith.split() and testedwith != 'internal':
227 tested = [tuplever(v) for v in testedwith.split()]
228 nearest = max([t for t in tested if t < ct])
229 if nearest < worst[1]:
230 worst = name, nearest, report
231 if worst[0] is not None:
232 name, testedwith, report = worst
233 if not isinstance(testedwith, str):
234 testedwith = '.'.join([str(c) for c in testedwith])
235 warning = (_('** Unknown exception encountered with '
236 'possibly-broken third-party extension %s\n'
237 '** which supports versions %s of Mercurial.\n'
238 '** Please disable %s and try your action again.\n'
239 '** If that fixes the bug please report it to %s\n')
240 % (name, testedwith, name, report))
241 else:
242 warning = (_("** unknown exception encountered, "
243 "please report by visiting\n") +
244 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
245 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
246 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
247 (_("** Extensions loaded: %s\n") %
248 ", ".join([x[0] for x in extensions.extensions()])))
249 ui.warn(warning)
216 250 raise
217 251
218 252 return -1
219 253
254 def tuplever(v):
255 return tuple([int(i) for i in v.split('.')])
256
257
220 258 def aliasargs(fn, givenargs):
221 259 args = getattr(fn, 'args', [])
222 260 if args:
223 261 cmd = ' '.join(map(util.shellquote, args))
224 262
225 263 nums = []
226 264 def replacer(m):
227 265 num = int(m.group(1)) - 1
228 266 nums.append(num)
229 267 if num < len(givenargs):
230 268 return givenargs[num]
231 269 raise util.Abort(_('too few arguments for command alias'))
232 270 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
233 271 givenargs = [x for i, x in enumerate(givenargs)
234 272 if i not in nums]
235 273 args = shlex.split(cmd)
236 274 return args + givenargs
237 275
238 276 class cmdalias(object):
239 277 def __init__(self, name, definition, cmdtable):
240 278 self.name = self.cmd = name
241 279 self.cmdname = ''
242 280 self.definition = definition
243 281 self.args = []
244 282 self.opts = []
245 283 self.help = ''
246 284 self.norepo = True
247 285 self.optionalrepo = False
248 286 self.badalias = False
249 287
250 288 try:
251 289 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
252 290 for alias, e in cmdtable.iteritems():
253 291 if e is entry:
254 292 self.cmd = alias
255 293 break
256 294 self.shadows = True
257 295 except error.UnknownCommand:
258 296 self.shadows = False
259 297
260 298 if not self.definition:
261 299 def fn(ui, *args):
262 300 ui.warn(_("no definition for alias '%s'\n") % self.name)
263 301 return 1
264 302 self.fn = fn
265 303 self.badalias = True
266 304 return
267 305
268 306 if self.definition.startswith('!'):
269 307 self.shell = True
270 308 def fn(ui, *args):
271 309 env = {'HG_ARGS': ' '.join((self.name,) + args)}
272 310 def _checkvar(m):
273 311 if m.groups()[0] == '$':
274 312 return m.group()
275 313 elif int(m.groups()[0]) <= len(args):
276 314 return m.group()
277 315 else:
278 316 ui.debug("No argument found for substitution "
279 317 "of %i variable in alias '%s' definition."
280 318 % (int(m.groups()[0]), self.name))
281 319 return ''
282 320 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
283 321 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
284 322 replace['0'] = self.name
285 323 replace['@'] = ' '.join(args)
286 324 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
287 325 return util.system(cmd, environ=env, out=ui.fout)
288 326 self.fn = fn
289 327 return
290 328
291 329 args = shlex.split(self.definition)
292 330 self.cmdname = cmd = args.pop(0)
293 331 args = map(util.expandpath, args)
294 332
295 333 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
296 334 if _earlygetopt([invalidarg], args):
297 335 def fn(ui, *args):
298 336 ui.warn(_("error in definition for alias '%s': %s may only "
299 337 "be given on the command line\n")
300 338 % (self.name, invalidarg))
301 339 return 1
302 340
303 341 self.fn = fn
304 342 self.badalias = True
305 343 return
306 344
307 345 try:
308 346 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
309 347 if len(tableentry) > 2:
310 348 self.fn, self.opts, self.help = tableentry
311 349 else:
312 350 self.fn, self.opts = tableentry
313 351
314 352 self.args = aliasargs(self.fn, args)
315 353 if cmd not in commands.norepo.split(' '):
316 354 self.norepo = False
317 355 if cmd in commands.optionalrepo.split(' '):
318 356 self.optionalrepo = True
319 357 if self.help.startswith("hg " + cmd):
320 358 # drop prefix in old-style help lines so hg shows the alias
321 359 self.help = self.help[4 + len(cmd):]
322 360 self.__doc__ = self.fn.__doc__
323 361
324 362 except error.UnknownCommand:
325 363 def fn(ui, *args):
326 364 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
327 365 % (self.name, cmd))
328 366 try:
329 367 # check if the command is in a disabled extension
330 368 commands.help_(ui, cmd, unknowncmd=True)
331 369 except error.UnknownCommand:
332 370 pass
333 371 return 1
334 372 self.fn = fn
335 373 self.badalias = True
336 374 except error.AmbiguousCommand:
337 375 def fn(ui, *args):
338 376 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
339 377 % (self.name, cmd))
340 378 return 1
341 379 self.fn = fn
342 380 self.badalias = True
343 381
344 382 def __call__(self, ui, *args, **opts):
345 383 if self.shadows:
346 384 ui.debug("alias '%s' shadows command '%s'\n" %
347 385 (self.name, self.cmdname))
348 386
349 387 if util.safehasattr(self, 'shell'):
350 388 return self.fn(ui, *args, **opts)
351 389 else:
352 390 try:
353 391 util.checksignature(self.fn)(ui, *args, **opts)
354 392 except error.SignatureError:
355 393 args = ' '.join([self.cmdname] + self.args)
356 394 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
357 395 raise
358 396
359 397 def addaliases(ui, cmdtable):
360 398 # aliases are processed after extensions have been loaded, so they
361 399 # may use extension commands. Aliases can also use other alias definitions,
362 400 # but only if they have been defined prior to the current definition.
363 401 for alias, definition in ui.configitems('alias'):
364 402 aliasdef = cmdalias(alias, definition, cmdtable)
365 403
366 404 try:
367 405 olddef = cmdtable[aliasdef.cmd][0]
368 406 if olddef.definition == aliasdef.definition:
369 407 continue
370 408 except (KeyError, AttributeError):
371 409 # definition might not exist or it might not be a cmdalias
372 410 pass
373 411
374 412 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
375 413 if aliasdef.norepo:
376 414 commands.norepo += ' %s' % alias
377 415 if aliasdef.optionalrepo:
378 416 commands.optionalrepo += ' %s' % alias
379 417
380 418 def _parse(ui, args):
381 419 options = {}
382 420 cmdoptions = {}
383 421
384 422 try:
385 423 args = fancyopts.fancyopts(args, commands.globalopts, options)
386 424 except fancyopts.getopt.GetoptError, inst:
387 425 raise error.CommandError(None, inst)
388 426
389 427 if args:
390 428 cmd, args = args[0], args[1:]
391 429 aliases, entry = cmdutil.findcmd(cmd, commands.table,
392 430 ui.configbool("ui", "strict"))
393 431 cmd = aliases[0]
394 432 args = aliasargs(entry[0], args)
395 433 defaults = ui.config("defaults", cmd)
396 434 if defaults:
397 435 args = map(util.expandpath, shlex.split(defaults)) + args
398 436 c = list(entry[1])
399 437 else:
400 438 cmd = None
401 439 c = []
402 440
403 441 # combine global options into local
404 442 for o in commands.globalopts:
405 443 c.append((o[0], o[1], options[o[1]], o[3]))
406 444
407 445 try:
408 446 args = fancyopts.fancyopts(args, c, cmdoptions, True)
409 447 except fancyopts.getopt.GetoptError, inst:
410 448 raise error.CommandError(cmd, inst)
411 449
412 450 # separate global options back out
413 451 for o in commands.globalopts:
414 452 n = o[1]
415 453 options[n] = cmdoptions[n]
416 454 del cmdoptions[n]
417 455
418 456 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
419 457
420 458 def _parseconfig(ui, config):
421 459 """parse the --config options from the command line"""
422 460 configs = []
423 461
424 462 for cfg in config:
425 463 try:
426 464 name, value = cfg.split('=', 1)
427 465 section, name = name.split('.', 1)
428 466 if not section or not name:
429 467 raise IndexError
430 468 ui.setconfig(section, name, value)
431 469 configs.append((section, name, value))
432 470 except (IndexError, ValueError):
433 471 raise util.Abort(_('malformed --config option: %r '
434 472 '(use --config section.name=value)') % cfg)
435 473
436 474 return configs
437 475
438 476 def _earlygetopt(aliases, args):
439 477 """Return list of values for an option (or aliases).
440 478
441 479 The values are listed in the order they appear in args.
442 480 The options and values are removed from args.
443 481 """
444 482 try:
445 483 argcount = args.index("--")
446 484 except ValueError:
447 485 argcount = len(args)
448 486 shortopts = [opt for opt in aliases if len(opt) == 2]
449 487 values = []
450 488 pos = 0
451 489 while pos < argcount:
452 490 if args[pos] in aliases:
453 491 if pos + 1 >= argcount:
454 492 # ignore and let getopt report an error if there is no value
455 493 break
456 494 del args[pos]
457 495 values.append(args.pop(pos))
458 496 argcount -= 2
459 497 elif args[pos][:2] in shortopts:
460 498 # short option can have no following space, e.g. hg log -Rfoo
461 499 values.append(args.pop(pos)[2:])
462 500 argcount -= 1
463 501 else:
464 502 pos += 1
465 503 return values
466 504
467 505 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
468 506 # run pre-hook, and abort if it fails
469 507 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
470 508 pats=cmdpats, opts=cmdoptions)
471 509 if ret:
472 510 return ret
473 511 ret = _runcommand(ui, options, cmd, d)
474 512 # run post-hook, passing command result
475 513 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
476 514 result=ret, pats=cmdpats, opts=cmdoptions)
477 515 return ret
478 516
479 517 def _getlocal(ui, rpath):
480 518 """Return (path, local ui object) for the given target path.
481 519
482 520 Takes paths in [cwd]/.hg/hgrc into account."
483 521 """
484 522 try:
485 523 wd = os.getcwd()
486 524 except OSError, e:
487 525 raise util.Abort(_("error getting current working directory: %s") %
488 526 e.strerror)
489 527 path = cmdutil.findrepo(wd) or ""
490 528 if not path:
491 529 lui = ui
492 530 else:
493 531 lui = ui.copy()
494 532 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
495 533
496 534 if rpath and rpath[-1]:
497 535 path = lui.expandpath(rpath[-1])
498 536 lui = ui.copy()
499 537 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
500 538
501 539 return path, lui
502 540
503 541 def _checkshellalias(lui, ui, args):
504 542 options = {}
505 543
506 544 try:
507 545 args = fancyopts.fancyopts(args, commands.globalopts, options)
508 546 except fancyopts.getopt.GetoptError:
509 547 return
510 548
511 549 if not args:
512 550 return
513 551
514 552 norepo = commands.norepo
515 553 optionalrepo = commands.optionalrepo
516 554 def restorecommands():
517 555 commands.norepo = norepo
518 556 commands.optionalrepo = optionalrepo
519 557
520 558 cmdtable = commands.table.copy()
521 559 addaliases(lui, cmdtable)
522 560
523 561 cmd = args[0]
524 562 try:
525 563 aliases, entry = cmdutil.findcmd(cmd, cmdtable,
526 564 lui.configbool("ui", "strict"))
527 565 except (error.AmbiguousCommand, error.UnknownCommand):
528 566 restorecommands()
529 567 return
530 568
531 569 cmd = aliases[0]
532 570 fn = entry[0]
533 571
534 572 if cmd and util.safehasattr(fn, 'shell'):
535 573 d = lambda: fn(ui, *args[1:])
536 574 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
537 575 [], {})
538 576
539 577 restorecommands()
540 578
541 579 _loaded = set()
542 580 def _dispatch(req):
543 581 args = req.args
544 582 ui = req.ui
545 583
546 584 # read --config before doing anything else
547 585 # (e.g. to change trust settings for reading .hg/hgrc)
548 586 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
549 587
550 588 # check for cwd
551 589 cwd = _earlygetopt(['--cwd'], args)
552 590 if cwd:
553 591 os.chdir(cwd[-1])
554 592
555 593 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
556 594 path, lui = _getlocal(ui, rpath)
557 595
558 596 # Now that we're operating in the right directory/repository with
559 597 # the right config settings, check for shell aliases
560 598 shellaliasfn = _checkshellalias(lui, ui, args)
561 599 if shellaliasfn:
562 600 return shellaliasfn()
563 601
564 602 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
565 603 # reposetup. Programs like TortoiseHg will call _dispatch several
566 604 # times so we keep track of configured extensions in _loaded.
567 605 extensions.loadall(lui)
568 606 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
569 607 # Propagate any changes to lui.__class__ by extensions
570 608 ui.__class__ = lui.__class__
571 609
572 610 # (uisetup and extsetup are handled in extensions.loadall)
573 611
574 612 for name, module in exts:
575 613 cmdtable = getattr(module, 'cmdtable', {})
576 614 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
577 615 if overrides:
578 616 ui.warn(_("extension '%s' overrides commands: %s\n")
579 617 % (name, " ".join(overrides)))
580 618 commands.table.update(cmdtable)
581 619 _loaded.add(name)
582 620
583 621 # (reposetup is handled in hg.repository)
584 622
585 623 addaliases(lui, commands.table)
586 624
587 625 # check for fallback encoding
588 626 fallback = lui.config('ui', 'fallbackencoding')
589 627 if fallback:
590 628 encoding.fallbackencoding = fallback
591 629
592 630 fullargs = args
593 631 cmd, func, args, options, cmdoptions = _parse(lui, args)
594 632
595 633 if options["config"]:
596 634 raise util.Abort(_("option --config may not be abbreviated!"))
597 635 if options["cwd"]:
598 636 raise util.Abort(_("option --cwd may not be abbreviated!"))
599 637 if options["repository"]:
600 638 raise util.Abort(_(
601 639 "option -R has to be separated from other options (e.g. not -qR) "
602 640 "and --repository may only be abbreviated as --repo!"))
603 641
604 642 if options["encoding"]:
605 643 encoding.encoding = options["encoding"]
606 644 if options["encodingmode"]:
607 645 encoding.encodingmode = options["encodingmode"]
608 646 if options["time"]:
609 647 def get_times():
610 648 t = os.times()
611 649 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
612 650 t = (t[0], t[1], t[2], t[3], time.clock())
613 651 return t
614 652 s = get_times()
615 653 def print_time():
616 654 t = get_times()
617 655 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
618 656 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
619 657 atexit.register(print_time)
620 658
621 659 uis = set([ui, lui])
622 660
623 661 if req.repo:
624 662 uis.add(req.repo.ui)
625 663
626 664 # copy configs that were passed on the cmdline (--config) to the repo ui
627 665 for cfg in cfgs:
628 666 req.repo.ui.setconfig(*cfg)
629 667
630 668 if options['verbose'] or options['debug'] or options['quiet']:
631 669 for opt in ('verbose', 'debug', 'quiet'):
632 670 val = str(bool(options[opt]))
633 671 for ui_ in uis:
634 672 ui_.setconfig('ui', opt, val)
635 673
636 674 if options['traceback']:
637 675 for ui_ in uis:
638 676 ui_.setconfig('ui', 'traceback', 'on')
639 677
640 678 if options['noninteractive']:
641 679 for ui_ in uis:
642 680 ui_.setconfig('ui', 'interactive', 'off')
643 681
644 682 if cmdoptions.get('insecure', False):
645 683 for ui_ in uis:
646 684 ui_.setconfig('web', 'cacerts', '')
647 685
648 686 if options['version']:
649 687 return commands.version_(ui)
650 688 if options['help']:
651 689 return commands.help_(ui, cmd)
652 690 elif not cmd:
653 691 return commands.help_(ui, 'shortlist')
654 692
655 693 repo = None
656 694 cmdpats = args[:]
657 695 if cmd not in commands.norepo.split():
658 696 # use the repo from the request only if we don't have -R
659 697 if not rpath and not cwd:
660 698 repo = req.repo
661 699
662 700 if repo:
663 701 # set the descriptors of the repo ui to those of ui
664 702 repo.ui.fin = ui.fin
665 703 repo.ui.fout = ui.fout
666 704 repo.ui.ferr = ui.ferr
667 705 else:
668 706 try:
669 707 repo = hg.repository(ui, path=path)
670 708 if not repo.local():
671 709 raise util.Abort(_("repository '%s' is not local") % path)
672 710 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
673 711 except error.RequirementError:
674 712 raise
675 713 except error.RepoError:
676 714 if cmd not in commands.optionalrepo.split():
677 715 if args and not path: # try to infer -R from command args
678 716 repos = map(cmdutil.findrepo, args)
679 717 guess = repos[0]
680 718 if guess and repos.count(guess) == len(repos):
681 719 req.args = ['--repository', guess] + fullargs
682 720 return _dispatch(req)
683 721 if not path:
684 722 raise error.RepoError(_("no repository found in '%s'"
685 723 " (.hg not found)")
686 724 % os.getcwd())
687 725 raise
688 726 if repo:
689 727 ui = repo.ui
690 728 args.insert(0, repo)
691 729 elif rpath:
692 730 ui.warn(_("warning: --repository ignored\n"))
693 731
694 732 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
695 733 ui.log("command", msg + "\n")
696 734 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
697 735 try:
698 736 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
699 737 cmdpats, cmdoptions)
700 738 finally:
701 739 if repo and repo != req.repo:
702 740 repo.close()
703 741
704 742 def lsprofile(ui, func, fp):
705 743 format = ui.config('profiling', 'format', default='text')
706 744 field = ui.config('profiling', 'sort', default='inlinetime')
707 745 climit = ui.configint('profiling', 'nested', default=5)
708 746
709 747 if format not in ['text', 'kcachegrind']:
710 748 ui.warn(_("unrecognized profiling format '%s'"
711 749 " - Ignored\n") % format)
712 750 format = 'text'
713 751
714 752 try:
715 753 from mercurial import lsprof
716 754 except ImportError:
717 755 raise util.Abort(_(
718 756 'lsprof not available - install from '
719 757 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
720 758 p = lsprof.Profiler()
721 759 p.enable(subcalls=True)
722 760 try:
723 761 return func()
724 762 finally:
725 763 p.disable()
726 764
727 765 if format == 'kcachegrind':
728 766 import lsprofcalltree
729 767 calltree = lsprofcalltree.KCacheGrind(p)
730 768 calltree.output(fp)
731 769 else:
732 770 # format == 'text'
733 771 stats = lsprof.Stats(p.getstats())
734 772 stats.sort(field)
735 773 stats.pprint(limit=30, file=fp, climit=climit)
736 774
737 775 def statprofile(ui, func, fp):
738 776 try:
739 777 import statprof
740 778 except ImportError:
741 779 raise util.Abort(_(
742 780 'statprof not available - install using "easy_install statprof"'))
743 781
744 782 freq = ui.configint('profiling', 'freq', default=1000)
745 783 if freq > 0:
746 784 statprof.reset(freq)
747 785 else:
748 786 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
749 787
750 788 statprof.start()
751 789 try:
752 790 return func()
753 791 finally:
754 792 statprof.stop()
755 793 statprof.display(fp)
756 794
757 795 def _runcommand(ui, options, cmd, cmdfunc):
758 796 def checkargs():
759 797 try:
760 798 return cmdfunc()
761 799 except error.SignatureError:
762 800 raise error.CommandError(cmd, _("invalid arguments"))
763 801
764 802 if options['profile']:
765 803 profiler = os.getenv('HGPROF')
766 804 if profiler is None:
767 805 profiler = ui.config('profiling', 'type', default='ls')
768 806 if profiler not in ('ls', 'stat'):
769 807 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
770 808 profiler = 'ls'
771 809
772 810 output = ui.config('profiling', 'output')
773 811
774 812 if output:
775 813 path = ui.expandpath(output)
776 814 fp = open(path, 'wb')
777 815 else:
778 816 fp = sys.stderr
779 817
780 818 try:
781 819 if profiler == 'ls':
782 820 return lsprofile(ui, checkargs, fp)
783 821 else:
784 822 return statprofile(ui, checkargs, fp)
785 823 finally:
786 824 if output:
787 825 fp.close()
788 826 else:
789 827 return checkargs()
@@ -1,480 +1,537 b''
1 1 Test basic extension support
2 2
3 3 $ "$TESTDIR/hghave" no-outer-repo || exit 80
4 4
5 5 $ cat > foobar.py <<EOF
6 6 > import os
7 7 > from mercurial import commands
8 8 >
9 9 > def uisetup(ui):
10 10 > ui.write("uisetup called\\n")
11 11 >
12 12 > def reposetup(ui, repo):
13 13 > ui.write("reposetup called for %s\\n" % os.path.basename(repo.root))
14 14 > ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!"))
15 15 >
16 16 > def foo(ui, *args, **kwargs):
17 17 > ui.write("Foo\\n")
18 18 >
19 19 > def bar(ui, *args, **kwargs):
20 20 > ui.write("Bar\\n")
21 21 >
22 22 > cmdtable = {
23 23 > "foo": (foo, [], "hg foo"),
24 24 > "bar": (bar, [], "hg bar"),
25 25 > }
26 26 >
27 27 > commands.norepo += ' bar'
28 28 > EOF
29 29 $ abspath=`pwd`/foobar.py
30 30
31 31 $ mkdir barfoo
32 32 $ cp foobar.py barfoo/__init__.py
33 33 $ barfoopath=`pwd`/barfoo
34 34
35 35 $ hg init a
36 36 $ cd a
37 37 $ echo foo > file
38 38 $ hg add file
39 39 $ hg commit -m 'add file'
40 40
41 41 $ echo '[extensions]' >> $HGRCPATH
42 42 $ echo "foobar = $abspath" >> $HGRCPATH
43 43 $ hg foo
44 44 uisetup called
45 45 reposetup called for a
46 46 ui == repo.ui
47 47 Foo
48 48
49 49 $ cd ..
50 50 $ hg clone a b
51 51 uisetup called
52 52 reposetup called for a
53 53 ui == repo.ui
54 54 reposetup called for b
55 55 ui == repo.ui
56 56 updating to branch default
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58
59 59 $ hg bar
60 60 uisetup called
61 61 Bar
62 62 $ echo 'foobar = !' >> $HGRCPATH
63 63
64 64 module/__init__.py-style
65 65
66 66 $ echo "barfoo = $barfoopath" >> $HGRCPATH
67 67 $ cd a
68 68 $ hg foo
69 69 uisetup called
70 70 reposetup called for a
71 71 ui == repo.ui
72 72 Foo
73 73 $ echo 'barfoo = !' >> $HGRCPATH
74 74
75 75 Check that extensions are loaded in phases:
76 76
77 77 $ cat > foo.py <<EOF
78 78 > import os
79 79 > name = os.path.basename(__file__).rsplit('.', 1)[0]
80 80 > print "1) %s imported" % name
81 81 > def uisetup(ui):
82 82 > print "2) %s uisetup" % name
83 83 > def extsetup():
84 84 > print "3) %s extsetup" % name
85 85 > def reposetup(ui, repo):
86 86 > print "4) %s reposetup" % name
87 87 > EOF
88 88
89 89 $ cp foo.py bar.py
90 90 $ echo 'foo = foo.py' >> $HGRCPATH
91 91 $ echo 'bar = bar.py' >> $HGRCPATH
92 92
93 93 Command with no output, we just want to see the extensions loaded:
94 94
95 95 $ hg paths
96 96 1) foo imported
97 97 1) bar imported
98 98 2) foo uisetup
99 99 2) bar uisetup
100 100 3) foo extsetup
101 101 3) bar extsetup
102 102 4) foo reposetup
103 103 4) bar reposetup
104 104
105 105 Check hgweb's load order:
106 106
107 107 $ cat > hgweb.cgi <<EOF
108 108 > #!/usr/bin/env python
109 109 > from mercurial import demandimport; demandimport.enable()
110 110 > from mercurial.hgweb import hgweb
111 111 > from mercurial.hgweb import wsgicgi
112 112 >
113 113 > application = hgweb('.', 'test repo')
114 114 > wsgicgi.launch(application)
115 115 > EOF
116 116
117 117 $ SCRIPT_NAME='/' SERVER_PORT='80' SERVER_NAME='localhost' python hgweb.cgi \
118 118 > | grep '^[0-9]) ' # ignores HTML output
119 119 1) foo imported
120 120 1) bar imported
121 121 2) foo uisetup
122 122 2) bar uisetup
123 123 3) foo extsetup
124 124 3) bar extsetup
125 125 4) foo reposetup
126 126 4) bar reposetup
127 127 4) foo reposetup
128 128 4) bar reposetup
129 129
130 130 $ echo 'foo = !' >> $HGRCPATH
131 131 $ echo 'bar = !' >> $HGRCPATH
132 132
133 133 $ cd ..
134 134
135 135 $ cat > empty.py <<EOF
136 136 > '''empty cmdtable
137 137 > '''
138 138 > cmdtable = {}
139 139 > EOF
140 140 $ emptypath=`pwd`/empty.py
141 141 $ echo "empty = $emptypath" >> $HGRCPATH
142 142 $ hg help empty
143 143 empty extension - empty cmdtable
144 144
145 145 no commands defined
146 146
147 147 $ echo 'empty = !' >> $HGRCPATH
148 148
149 149 $ cat > debugextension.py <<EOF
150 150 > '''only debugcommands
151 151 > '''
152 152 > def debugfoobar(ui, repo, *args, **opts):
153 153 > "yet another debug command"
154 154 > pass
155 155 >
156 156 > def foo(ui, repo, *args, **opts):
157 157 > """yet another foo command
158 158 >
159 159 > This command has been DEPRECATED since forever.
160 160 > """
161 161 > pass
162 162 >
163 163 > cmdtable = {
164 164 > "debugfoobar": (debugfoobar, (), "hg debugfoobar"),
165 165 > "foo": (foo, (), "hg foo")
166 166 > }
167 167 > EOF
168 168 $ debugpath=`pwd`/debugextension.py
169 169 $ echo "debugextension = $debugpath" >> $HGRCPATH
170 170
171 171 $ hg help debugextension
172 172 debugextension extension - only debugcommands
173 173
174 174 no commands defined
175 175
176 176 $ hg --verbose help debugextension
177 177 debugextension extension - only debugcommands
178 178
179 179 list of commands:
180 180
181 181 foo:
182 182 yet another foo command
183 183
184 184 global options:
185 185
186 186 -R --repository REPO repository root directory or name of overlay bundle
187 187 file
188 188 --cwd DIR change working directory
189 189 -y --noninteractive do not prompt, automatically pick the first choice for
190 190 all prompts
191 191 -q --quiet suppress output
192 192 -v --verbose enable additional output
193 193 --config CONFIG [+] set/override config option (use 'section.name=value')
194 194 --debug enable debugging output
195 195 --debugger start debugger
196 196 --encoding ENCODE set the charset encoding (default: ascii)
197 197 --encodingmode MODE set the charset encoding mode (default: strict)
198 198 --traceback always print a traceback on exception
199 199 --time time how long the command takes
200 200 --profile print command execution profile
201 201 --version output version information and exit
202 202 -h --help display help and exit
203 203
204 204 [+] marked option can be specified multiple times
205 205
206 206 $ hg --debug help debugextension
207 207 debugextension extension - only debugcommands
208 208
209 209 list of commands:
210 210
211 211 debugfoobar:
212 212 yet another debug command
213 213 foo:
214 214 yet another foo command
215 215
216 216 global options:
217 217
218 218 -R --repository REPO repository root directory or name of overlay bundle
219 219 file
220 220 --cwd DIR change working directory
221 221 -y --noninteractive do not prompt, automatically pick the first choice for
222 222 all prompts
223 223 -q --quiet suppress output
224 224 -v --verbose enable additional output
225 225 --config CONFIG [+] set/override config option (use 'section.name=value')
226 226 --debug enable debugging output
227 227 --debugger start debugger
228 228 --encoding ENCODE set the charset encoding (default: ascii)
229 229 --encodingmode MODE set the charset encoding mode (default: strict)
230 230 --traceback always print a traceback on exception
231 231 --time time how long the command takes
232 232 --profile print command execution profile
233 233 --version output version information and exit
234 234 -h --help display help and exit
235 235
236 236 [+] marked option can be specified multiple times
237 237 $ echo 'debugextension = !' >> $HGRCPATH
238 238
239 239 Extension module help vs command help:
240 240
241 241 $ echo 'extdiff =' >> $HGRCPATH
242 242 $ hg help extdiff
243 243 hg extdiff [OPT]... [FILE]...
244 244
245 245 use external program to diff repository (or selected files)
246 246
247 247 Show differences between revisions for the specified files, using an
248 248 external program. The default program used is diff, with default options
249 249 "-Npru".
250 250
251 251 To select a different program, use the -p/--program option. The program
252 252 will be passed the names of two directories to compare. To pass additional
253 253 options to the program, use -o/--option. These will be passed before the
254 254 names of the directories to compare.
255 255
256 256 When two revision arguments are given, then changes are shown between
257 257 those revisions. If only one revision is specified then that revision is
258 258 compared to the working directory, and, when no revisions are specified,
259 259 the working directory files are compared to its parent.
260 260
261 261 use "hg help -e extdiff" to show help for the extdiff extension
262 262
263 263 options:
264 264
265 265 -p --program CMD comparison program to run
266 266 -o --option OPT [+] pass option to comparison program
267 267 -r --rev REV [+] revision
268 268 -c --change REV change made by revision
269 269 -I --include PATTERN [+] include names matching the given patterns
270 270 -X --exclude PATTERN [+] exclude names matching the given patterns
271 271
272 272 [+] marked option can be specified multiple times
273 273
274 274 use "hg -v help extdiff" to show more info
275 275
276 276 $ hg help --extension extdiff
277 277 extdiff extension - command to allow external programs to compare revisions
278 278
279 279 The extdiff Mercurial extension allows you to use external programs to compare
280 280 revisions, or revision with working directory. The external diff programs are
281 281 called with a configurable set of options and two non-option arguments: paths
282 282 to directories containing snapshots of files to compare.
283 283
284 284 The extdiff extension also allows you to configure new diff commands, so you
285 285 do not need to type "hg extdiff -p kdiff3" always.
286 286
287 287 [extdiff]
288 288 # add new command that runs GNU diff(1) in 'context diff' mode
289 289 cdiff = gdiff -Nprc5
290 290 ## or the old way:
291 291 #cmd.cdiff = gdiff
292 292 #opts.cdiff = -Nprc5
293 293
294 294 # add new command called vdiff, runs kdiff3
295 295 vdiff = kdiff3
296 296
297 297 # add new command called meld, runs meld (no need to name twice)
298 298 meld =
299 299
300 300 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
301 301 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
302 302 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
303 303 # your .vimrc
304 304 vimdiff = gvim -f "+next" \
305 305 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
306 306
307 307 Tool arguments can include variables that are expanded at runtime:
308 308
309 309 $parent1, $plabel1 - filename, descriptive label of first parent
310 310 $child, $clabel - filename, descriptive label of child revision
311 311 $parent2, $plabel2 - filename, descriptive label of second parent
312 312 $root - repository root
313 313 $parent is an alias for $parent1.
314 314
315 315 The extdiff extension will look in your [diff-tools] and [merge-tools]
316 316 sections for diff tool arguments, when none are specified in [extdiff].
317 317
318 318 [extdiff]
319 319 kdiff3 =
320 320
321 321 [diff-tools]
322 322 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
323 323
324 324 You can use -I/-X and list of file or directory names like normal "hg diff"
325 325 command. The extdiff extension makes snapshots of only needed files, so
326 326 running the external diff program will actually be pretty fast (at least
327 327 faster than having to compare the entire tree).
328 328
329 329 list of commands:
330 330
331 331 extdiff use external program to diff repository (or selected files)
332 332
333 333 use "hg -v help extdiff" to show builtin aliases and global options
334 334
335 335 $ echo 'extdiff = !' >> $HGRCPATH
336 336
337 337 Test help topic with same name as extension
338 338
339 339 $ cat > multirevs.py <<EOF
340 340 > from mercurial import commands
341 341 > """multirevs extension
342 342 > Big multi-line module docstring."""
343 343 > def multirevs(ui, repo, arg, *args, **opts):
344 344 > """multirevs command"""
345 345 > pass
346 346 > cmdtable = {
347 347 > "multirevs": (multirevs, [], 'ARG')
348 348 > }
349 349 > commands.norepo += ' multirevs'
350 350 > EOF
351 351 $ echo "multirevs = multirevs.py" >> $HGRCPATH
352 352
353 353 $ hg help multirevs
354 354 Specifying Multiple Revisions
355 355
356 356 When Mercurial accepts more than one revision, they may be specified
357 357 individually, or provided as a topologically continuous range, separated
358 358 by the ":" character.
359 359
360 360 The syntax of range notation is [BEGIN]:[END], where BEGIN and END are
361 361 revision identifiers. Both BEGIN and END are optional. If BEGIN is not
362 362 specified, it defaults to revision number 0. If END is not specified, it
363 363 defaults to the tip. The range ":" thus means "all revisions".
364 364
365 365 If BEGIN is greater than END, revisions are treated in reverse order.
366 366
367 367 A range acts as a closed interval. This means that a range of 3:5 gives 3,
368 368 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
369 369
370 370 use "hg help -c multirevs" to see help for the multirevs command
371 371
372 372 $ hg help -c multirevs
373 373 hg multirevs ARG
374 374
375 375 multirevs command
376 376
377 377 use "hg -v help multirevs" to show more info
378 378
379 379 $ hg multirevs
380 380 hg multirevs: invalid arguments
381 381 hg multirevs ARG
382 382
383 383 multirevs command
384 384
385 385 use "hg help multirevs" to show the full help text
386 386 [255]
387 387
388 388 $ echo "multirevs = !" >> $HGRCPATH
389 389
390 390 Issue811: Problem loading extensions twice (by site and by user)
391 391
392 392 $ debugpath=`pwd`/debugissue811.py
393 393 $ cat > debugissue811.py <<EOF
394 394 > '''show all loaded extensions
395 395 > '''
396 396 > from mercurial import extensions, commands
397 397 >
398 398 > def debugextensions(ui):
399 399 > "yet another debug command"
400 400 > ui.write("%s\n" % '\n'.join([x for x, y in extensions.extensions()]))
401 401 >
402 402 > cmdtable = {"debugextensions": (debugextensions, (), "hg debugextensions")}
403 403 > commands.norepo += " debugextensions"
404 404 > EOF
405 405 $ echo "debugissue811 = $debugpath" >> $HGRCPATH
406 406 $ echo "mq=" >> $HGRCPATH
407 407 $ echo "hgext.mq=" >> $HGRCPATH
408 408 $ echo "hgext/mq=" >> $HGRCPATH
409 409
410 410 Show extensions:
411 411
412 412 $ hg debugextensions
413 413 debugissue811
414 414 mq
415 415
416 416 Disabled extension commands:
417 417
418 418 $ HGRCPATH=
419 419 $ export HGRCPATH
420 420 $ hg help email
421 421 'email' is provided by the following extension:
422 422
423 423 patchbomb command to send changesets as (a series of) patch emails
424 424
425 425 use "hg help extensions" for information on enabling extensions
426 426 $ hg qdel
427 427 hg: unknown command 'qdel'
428 428 'qdelete' is provided by the following extension:
429 429
430 430 mq manage a stack of patches
431 431
432 432 use "hg help extensions" for information on enabling extensions
433 433 [255]
434 434 $ hg churn
435 435 hg: unknown command 'churn'
436 436 'churn' is provided by the following extension:
437 437
438 438 churn command to display statistics about repository history
439 439
440 440 use "hg help extensions" for information on enabling extensions
441 441 [255]
442 442
443 443 Disabled extensions:
444 444
445 445 $ hg help churn
446 446 churn extension - command to display statistics about repository history
447 447
448 448 use "hg help extensions" for information on enabling extensions
449 449 $ hg help patchbomb
450 450 patchbomb extension - command to send changesets as (a series of) patch emails
451 451
452 452 use "hg help extensions" for information on enabling extensions
453 453
454 454 Broken disabled extension and command:
455 455
456 456 $ mkdir hgext
457 457 $ echo > hgext/__init__.py
458 458 $ cat > hgext/broken.py <<EOF
459 459 > "broken extension'
460 460 > EOF
461 461 $ cat > path.py <<EOF
462 462 > import os, sys
463 463 > sys.path.insert(0, os.environ['HGEXTPATH'])
464 464 > EOF
465 465 $ HGEXTPATH=`pwd`
466 466 $ export HGEXTPATH
467 467
468 468 $ hg --config extensions.path=./path.py help broken
469 469 broken extension - (no help text available)
470 470
471 471 use "hg help extensions" for information on enabling extensions
472 472
473 473 $ cat > hgext/forest.py <<EOF
474 474 > cmdtable = None
475 475 > EOF
476 476 $ hg --config extensions.path=./path.py help foo > /dev/null
477 477 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
478 478 hg: unknown command 'foo'
479 479 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
480 480 [255]
481
482 $ cat > throw.py <<EOF
483 > from mercurial import cmdutil, commands
484 > cmdtable = {}
485 > command = cmdutil.command(cmdtable)
486 > class Bogon(Exception): pass
487 >
488 > @command('throw', [], 'hg throw')
489 > def throw(ui, **opts):
490 > """throws an exception"""
491 > raise Bogon()
492 > commands.norepo += " throw"
493 > EOF
494 No declared supported version, extension complains:
495 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
496 ** Unknown exception encountered with possibly-broken third-party extension throw
497 ** which supports versions unknown of Mercurial.
498 ** Please disable throw and try your action again.
499 ** If that fixes the bug please report it to the extension author.
500 ** Python * (glob)
501 ** Mercurial Distributed SCM * (glob)
502 ** Extensions loaded: throw
503 If the extension specifies a buglink, show that:
504 $ echo 'buglink = "http://example.com/bts"' >> throw.py
505 $ rm -f throw.pyc throw.pyo
506 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
507 ** Unknown exception encountered with possibly-broken third-party extension throw
508 ** which supports versions unknown of Mercurial.
509 ** Please disable throw and try your action again.
510 ** If that fixes the bug please report it to http://example.com/bts
511 ** Python * (glob)
512 ** Mercurial Distributed SCM (*) (glob)
513 ** Extensions loaded: throw
514 If the extensions declare outdated versions, accuse the older extension first:
515 $ echo "testedwith = '1.9.3'" >> older.py
516 $ echo "testedwith = '2.1.1'" >> throw.py
517 $ rm -f throw.pyc throw.pyo
518 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
519 > throw 2>&1 | egrep '^\*\*'
520 ** Unknown exception encountered with possibly-broken third-party extension older
521 ** which supports versions 1.9.3 of Mercurial.
522 ** Please disable older and try your action again.
523 ** If that fixes the bug please report it to the extension author.
524 ** Python * (glob)
525 ** Mercurial Distributed SCM (*) (glob)
526 ** Extensions loaded: throw, older
527
528 Declare the version as supporting this hg version, show regular bts link:
529 $ hgver=`python -c 'from mercurial import util; print util.version().split("+")[0]'`
530 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
531 $ rm -f throw.pyc throw.pyo
532 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
533 ** unknown exception encountered, please report by visiting
534 ** http://mercurial.selenic.com/wiki/BugTracker
535 ** Python * (glob)
536 ** Mercurial Distributed SCM (*) (glob)
537 ** Extensions loaded: throw
General Comments 0
You need to be logged in to leave comments. Login now