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