##// END OF EJS Templates
dispatch: handle empty `testedwith` value in extension...
Pierre-Yves David -
r18224:0f901311 stable
parent child Browse files
Show More
@@ -1,831 +1,831 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 testedwith = getattr(mod, 'testedwith', 'unknown')
220 testedwith = getattr(mod, 'testedwith', '')
221 221 report = getattr(mod, 'buglink', _('the extension author.'))
222 if testedwith == 'unknown':
222 if not testedwith.strip():
223 223 # We found an untested extension. It's likely the culprit.
224 worst = name, testedwith, report
224 worst = name, 'unknown', 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 228 lower = [t for t in tested if t < ct]
229 229 nearest = max(lower or tested)
230 230 if worst[0] is None or nearest < worst[1]:
231 231 worst = name, nearest, report
232 232 if worst[0] is not None:
233 233 name, testedwith, report = worst
234 234 if not isinstance(testedwith, str):
235 235 testedwith = '.'.join([str(c) for c in testedwith])
236 236 warning = (_('** Unknown exception encountered with '
237 237 'possibly-broken third-party extension %s\n'
238 238 '** which supports versions %s of Mercurial.\n'
239 239 '** Please disable %s and try your action again.\n'
240 240 '** If that fixes the bug please report it to %s\n')
241 241 % (name, testedwith, name, report))
242 242 else:
243 243 warning = (_("** unknown exception encountered, "
244 244 "please report by visiting\n") +
245 245 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
246 246 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
247 247 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
248 248 (_("** Extensions loaded: %s\n") %
249 249 ", ".join([x[0] for x in extensions.extensions()])))
250 250 ui.warn(warning)
251 251 raise
252 252
253 253 return -1
254 254
255 255 def tuplever(v):
256 256 try:
257 257 return tuple([int(i) for i in v.split('.')])
258 258 except ValueError:
259 259 return tuple()
260 260
261 261 def aliasargs(fn, givenargs):
262 262 args = getattr(fn, 'args', [])
263 263 if args:
264 264 cmd = ' '.join(map(util.shellquote, args))
265 265
266 266 nums = []
267 267 def replacer(m):
268 268 num = int(m.group(1)) - 1
269 269 nums.append(num)
270 270 if num < len(givenargs):
271 271 return givenargs[num]
272 272 raise util.Abort(_('too few arguments for command alias'))
273 273 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
274 274 givenargs = [x for i, x in enumerate(givenargs)
275 275 if i not in nums]
276 276 args = shlex.split(cmd)
277 277 return args + givenargs
278 278
279 279 class cmdalias(object):
280 280 def __init__(self, name, definition, cmdtable):
281 281 self.name = self.cmd = name
282 282 self.cmdname = ''
283 283 self.definition = definition
284 284 self.args = []
285 285 self.opts = []
286 286 self.help = ''
287 287 self.norepo = True
288 288 self.optionalrepo = False
289 289 self.badalias = False
290 290
291 291 try:
292 292 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
293 293 for alias, e in cmdtable.iteritems():
294 294 if e is entry:
295 295 self.cmd = alias
296 296 break
297 297 self.shadows = True
298 298 except error.UnknownCommand:
299 299 self.shadows = False
300 300
301 301 if not self.definition:
302 302 def fn(ui, *args):
303 303 ui.warn(_("no definition for alias '%s'\n") % self.name)
304 304 return 1
305 305 self.fn = fn
306 306 self.badalias = True
307 307 return
308 308
309 309 if self.definition.startswith('!'):
310 310 self.shell = True
311 311 def fn(ui, *args):
312 312 env = {'HG_ARGS': ' '.join((self.name,) + args)}
313 313 def _checkvar(m):
314 314 if m.groups()[0] == '$':
315 315 return m.group()
316 316 elif int(m.groups()[0]) <= len(args):
317 317 return m.group()
318 318 else:
319 319 ui.debug("No argument found for substitution "
320 320 "of %i variable in alias '%s' definition."
321 321 % (int(m.groups()[0]), self.name))
322 322 return ''
323 323 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
324 324 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
325 325 replace['0'] = self.name
326 326 replace['@'] = ' '.join(args)
327 327 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
328 328 return util.system(cmd, environ=env, out=ui.fout)
329 329 self.fn = fn
330 330 return
331 331
332 332 args = shlex.split(self.definition)
333 333 self.cmdname = cmd = args.pop(0)
334 334 args = map(util.expandpath, args)
335 335
336 336 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
337 337 if _earlygetopt([invalidarg], args):
338 338 def fn(ui, *args):
339 339 ui.warn(_("error in definition for alias '%s': %s may only "
340 340 "be given on the command line\n")
341 341 % (self.name, invalidarg))
342 342 return 1
343 343
344 344 self.fn = fn
345 345 self.badalias = True
346 346 return
347 347
348 348 try:
349 349 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
350 350 if len(tableentry) > 2:
351 351 self.fn, self.opts, self.help = tableentry
352 352 else:
353 353 self.fn, self.opts = tableentry
354 354
355 355 self.args = aliasargs(self.fn, args)
356 356 if cmd not in commands.norepo.split(' '):
357 357 self.norepo = False
358 358 if cmd in commands.optionalrepo.split(' '):
359 359 self.optionalrepo = True
360 360 if self.help.startswith("hg " + cmd):
361 361 # drop prefix in old-style help lines so hg shows the alias
362 362 self.help = self.help[4 + len(cmd):]
363 363 self.__doc__ = self.fn.__doc__
364 364
365 365 except error.UnknownCommand:
366 366 def fn(ui, *args):
367 367 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
368 368 % (self.name, cmd))
369 369 try:
370 370 # check if the command is in a disabled extension
371 371 commands.help_(ui, cmd, unknowncmd=True)
372 372 except error.UnknownCommand:
373 373 pass
374 374 return 1
375 375 self.fn = fn
376 376 self.badalias = True
377 377 except error.AmbiguousCommand:
378 378 def fn(ui, *args):
379 379 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
380 380 % (self.name, cmd))
381 381 return 1
382 382 self.fn = fn
383 383 self.badalias = True
384 384
385 385 def __call__(self, ui, *args, **opts):
386 386 if self.shadows:
387 387 ui.debug("alias '%s' shadows command '%s'\n" %
388 388 (self.name, self.cmdname))
389 389
390 390 if util.safehasattr(self, 'shell'):
391 391 return self.fn(ui, *args, **opts)
392 392 else:
393 393 try:
394 394 util.checksignature(self.fn)(ui, *args, **opts)
395 395 except error.SignatureError:
396 396 args = ' '.join([self.cmdname] + self.args)
397 397 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
398 398 raise
399 399
400 400 def addaliases(ui, cmdtable):
401 401 # aliases are processed after extensions have been loaded, so they
402 402 # may use extension commands. Aliases can also use other alias definitions,
403 403 # but only if they have been defined prior to the current definition.
404 404 for alias, definition in ui.configitems('alias'):
405 405 aliasdef = cmdalias(alias, definition, cmdtable)
406 406
407 407 try:
408 408 olddef = cmdtable[aliasdef.cmd][0]
409 409 if olddef.definition == aliasdef.definition:
410 410 continue
411 411 except (KeyError, AttributeError):
412 412 # definition might not exist or it might not be a cmdalias
413 413 pass
414 414
415 415 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
416 416 if aliasdef.norepo:
417 417 commands.norepo += ' %s' % alias
418 418 if aliasdef.optionalrepo:
419 419 commands.optionalrepo += ' %s' % alias
420 420
421 421 def _parse(ui, args):
422 422 options = {}
423 423 cmdoptions = {}
424 424
425 425 try:
426 426 args = fancyopts.fancyopts(args, commands.globalopts, options)
427 427 except fancyopts.getopt.GetoptError, inst:
428 428 raise error.CommandError(None, inst)
429 429
430 430 if args:
431 431 cmd, args = args[0], args[1:]
432 432 aliases, entry = cmdutil.findcmd(cmd, commands.table,
433 433 ui.configbool("ui", "strict"))
434 434 cmd = aliases[0]
435 435 args = aliasargs(entry[0], args)
436 436 defaults = ui.config("defaults", cmd)
437 437 if defaults:
438 438 args = map(util.expandpath, shlex.split(defaults)) + args
439 439 c = list(entry[1])
440 440 else:
441 441 cmd = None
442 442 c = []
443 443
444 444 # combine global options into local
445 445 for o in commands.globalopts:
446 446 c.append((o[0], o[1], options[o[1]], o[3]))
447 447
448 448 try:
449 449 args = fancyopts.fancyopts(args, c, cmdoptions, True)
450 450 except fancyopts.getopt.GetoptError, inst:
451 451 raise error.CommandError(cmd, inst)
452 452
453 453 # separate global options back out
454 454 for o in commands.globalopts:
455 455 n = o[1]
456 456 options[n] = cmdoptions[n]
457 457 del cmdoptions[n]
458 458
459 459 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
460 460
461 461 def _parseconfig(ui, config):
462 462 """parse the --config options from the command line"""
463 463 configs = []
464 464
465 465 for cfg in config:
466 466 try:
467 467 name, value = cfg.split('=', 1)
468 468 section, name = name.split('.', 1)
469 469 if not section or not name:
470 470 raise IndexError
471 471 ui.setconfig(section, name, value)
472 472 configs.append((section, name, value))
473 473 except (IndexError, ValueError):
474 474 raise util.Abort(_('malformed --config option: %r '
475 475 '(use --config section.name=value)') % cfg)
476 476
477 477 return configs
478 478
479 479 def _earlygetopt(aliases, args):
480 480 """Return list of values for an option (or aliases).
481 481
482 482 The values are listed in the order they appear in args.
483 483 The options and values are removed from args.
484 484 """
485 485 try:
486 486 argcount = args.index("--")
487 487 except ValueError:
488 488 argcount = len(args)
489 489 shortopts = [opt for opt in aliases if len(opt) == 2]
490 490 values = []
491 491 pos = 0
492 492 while pos < argcount:
493 493 if args[pos] in aliases:
494 494 if pos + 1 >= argcount:
495 495 # ignore and let getopt report an error if there is no value
496 496 break
497 497 del args[pos]
498 498 values.append(args.pop(pos))
499 499 argcount -= 2
500 500 elif args[pos][:2] in shortopts:
501 501 # short option can have no following space, e.g. hg log -Rfoo
502 502 values.append(args.pop(pos)[2:])
503 503 argcount -= 1
504 504 else:
505 505 pos += 1
506 506 return values
507 507
508 508 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
509 509 # run pre-hook, and abort if it fails
510 510 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
511 511 pats=cmdpats, opts=cmdoptions)
512 512 if ret:
513 513 return ret
514 514 ret = _runcommand(ui, options, cmd, d)
515 515 # run post-hook, passing command result
516 516 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
517 517 result=ret, pats=cmdpats, opts=cmdoptions)
518 518 return ret
519 519
520 520 def _getlocal(ui, rpath):
521 521 """Return (path, local ui object) for the given target path.
522 522
523 523 Takes paths in [cwd]/.hg/hgrc into account."
524 524 """
525 525 try:
526 526 wd = os.getcwd()
527 527 except OSError, e:
528 528 raise util.Abort(_("error getting current working directory: %s") %
529 529 e.strerror)
530 530 path = cmdutil.findrepo(wd) or ""
531 531 if not path:
532 532 lui = ui
533 533 else:
534 534 lui = ui.copy()
535 535 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
536 536
537 537 if rpath and rpath[-1]:
538 538 path = lui.expandpath(rpath[-1])
539 539 lui = ui.copy()
540 540 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
541 541
542 542 return path, lui
543 543
544 544 def _checkshellalias(lui, ui, args):
545 545 options = {}
546 546
547 547 try:
548 548 args = fancyopts.fancyopts(args, commands.globalopts, options)
549 549 except fancyopts.getopt.GetoptError:
550 550 return
551 551
552 552 if not args:
553 553 return
554 554
555 555 norepo = commands.norepo
556 556 optionalrepo = commands.optionalrepo
557 557 def restorecommands():
558 558 commands.norepo = norepo
559 559 commands.optionalrepo = optionalrepo
560 560
561 561 cmdtable = commands.table.copy()
562 562 addaliases(lui, cmdtable)
563 563
564 564 cmd = args[0]
565 565 try:
566 566 aliases, entry = cmdutil.findcmd(cmd, cmdtable,
567 567 lui.configbool("ui", "strict"))
568 568 except (error.AmbiguousCommand, error.UnknownCommand):
569 569 restorecommands()
570 570 return
571 571
572 572 cmd = aliases[0]
573 573 fn = entry[0]
574 574
575 575 if cmd and util.safehasattr(fn, 'shell'):
576 576 d = lambda: fn(ui, *args[1:])
577 577 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
578 578 [], {})
579 579
580 580 restorecommands()
581 581
582 582 _loaded = set()
583 583 def _dispatch(req):
584 584 args = req.args
585 585 ui = req.ui
586 586
587 587 # read --config before doing anything else
588 588 # (e.g. to change trust settings for reading .hg/hgrc)
589 589 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
590 590
591 591 # check for cwd
592 592 cwd = _earlygetopt(['--cwd'], args)
593 593 if cwd:
594 594 os.chdir(cwd[-1])
595 595
596 596 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
597 597 path, lui = _getlocal(ui, rpath)
598 598
599 599 # Now that we're operating in the right directory/repository with
600 600 # the right config settings, check for shell aliases
601 601 shellaliasfn = _checkshellalias(lui, ui, args)
602 602 if shellaliasfn:
603 603 return shellaliasfn()
604 604
605 605 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
606 606 # reposetup. Programs like TortoiseHg will call _dispatch several
607 607 # times so we keep track of configured extensions in _loaded.
608 608 extensions.loadall(lui)
609 609 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
610 610 # Propagate any changes to lui.__class__ by extensions
611 611 ui.__class__ = lui.__class__
612 612
613 613 # (uisetup and extsetup are handled in extensions.loadall)
614 614
615 615 for name, module in exts:
616 616 cmdtable = getattr(module, 'cmdtable', {})
617 617 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
618 618 if overrides:
619 619 ui.warn(_("extension '%s' overrides commands: %s\n")
620 620 % (name, " ".join(overrides)))
621 621 commands.table.update(cmdtable)
622 622 _loaded.add(name)
623 623
624 624 # (reposetup is handled in hg.repository)
625 625
626 626 addaliases(lui, commands.table)
627 627
628 628 # check for fallback encoding
629 629 fallback = lui.config('ui', 'fallbackencoding')
630 630 if fallback:
631 631 encoding.fallbackencoding = fallback
632 632
633 633 fullargs = args
634 634 cmd, func, args, options, cmdoptions = _parse(lui, args)
635 635
636 636 if options["config"]:
637 637 raise util.Abort(_("option --config may not be abbreviated!"))
638 638 if options["cwd"]:
639 639 raise util.Abort(_("option --cwd may not be abbreviated!"))
640 640 if options["repository"]:
641 641 raise util.Abort(_(
642 642 "option -R has to be separated from other options (e.g. not -qR) "
643 643 "and --repository may only be abbreviated as --repo!"))
644 644
645 645 if options["encoding"]:
646 646 encoding.encoding = options["encoding"]
647 647 if options["encodingmode"]:
648 648 encoding.encodingmode = options["encodingmode"]
649 649 if options["time"]:
650 650 def get_times():
651 651 t = os.times()
652 652 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
653 653 t = (t[0], t[1], t[2], t[3], time.clock())
654 654 return t
655 655 s = get_times()
656 656 def print_time():
657 657 t = get_times()
658 658 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
659 659 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
660 660 atexit.register(print_time)
661 661
662 662 uis = set([ui, lui])
663 663
664 664 if req.repo:
665 665 uis.add(req.repo.ui)
666 666
667 667 # copy configs that were passed on the cmdline (--config) to the repo ui
668 668 for cfg in cfgs:
669 669 req.repo.ui.setconfig(*cfg)
670 670
671 671 if options['verbose'] or options['debug'] or options['quiet']:
672 672 for opt in ('verbose', 'debug', 'quiet'):
673 673 val = str(bool(options[opt]))
674 674 for ui_ in uis:
675 675 ui_.setconfig('ui', opt, val)
676 676
677 677 if options['traceback']:
678 678 for ui_ in uis:
679 679 ui_.setconfig('ui', 'traceback', 'on')
680 680
681 681 if options['noninteractive']:
682 682 for ui_ in uis:
683 683 ui_.setconfig('ui', 'interactive', 'off')
684 684
685 685 if cmdoptions.get('insecure', False):
686 686 for ui_ in uis:
687 687 ui_.setconfig('web', 'cacerts', '')
688 688
689 689 if options['version']:
690 690 return commands.version_(ui)
691 691 if options['help']:
692 692 return commands.help_(ui, cmd)
693 693 elif not cmd:
694 694 return commands.help_(ui, 'shortlist')
695 695
696 696 repo = None
697 697 cmdpats = args[:]
698 698 if cmd not in commands.norepo.split():
699 699 # use the repo from the request only if we don't have -R
700 700 if not rpath and not cwd:
701 701 repo = req.repo
702 702
703 703 if repo:
704 704 # set the descriptors of the repo ui to those of ui
705 705 repo.ui.fin = ui.fin
706 706 repo.ui.fout = ui.fout
707 707 repo.ui.ferr = ui.ferr
708 708 else:
709 709 try:
710 710 repo = hg.repository(ui, path=path)
711 711 if not repo.local():
712 712 raise util.Abort(_("repository '%s' is not local") % path)
713 713 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
714 714 except error.RequirementError:
715 715 raise
716 716 except error.RepoError:
717 717 if cmd not in commands.optionalrepo.split():
718 718 if (cmd in commands.inferrepo.split() and
719 719 args and not path): # try to infer -R from command args
720 720 repos = map(cmdutil.findrepo, args)
721 721 guess = repos[0]
722 722 if guess and repos.count(guess) == len(repos):
723 723 req.args = ['--repository', guess] + fullargs
724 724 return _dispatch(req)
725 725 if not path:
726 726 raise error.RepoError(_("no repository found in '%s'"
727 727 " (.hg not found)")
728 728 % os.getcwd())
729 729 raise
730 730 if repo:
731 731 ui = repo.ui
732 732 args.insert(0, repo)
733 733 elif rpath:
734 734 ui.warn(_("warning: --repository ignored\n"))
735 735
736 736 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
737 737 ui.log("command", msg + "\n")
738 738 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
739 739 try:
740 740 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
741 741 cmdpats, cmdoptions)
742 742 finally:
743 743 if repo and repo != req.repo:
744 744 repo.close()
745 745
746 746 def lsprofile(ui, func, fp):
747 747 format = ui.config('profiling', 'format', default='text')
748 748 field = ui.config('profiling', 'sort', default='inlinetime')
749 749 climit = ui.configint('profiling', 'nested', default=5)
750 750
751 751 if format not in ['text', 'kcachegrind']:
752 752 ui.warn(_("unrecognized profiling format '%s'"
753 753 " - Ignored\n") % format)
754 754 format = 'text'
755 755
756 756 try:
757 757 from mercurial import lsprof
758 758 except ImportError:
759 759 raise util.Abort(_(
760 760 'lsprof not available - install from '
761 761 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
762 762 p = lsprof.Profiler()
763 763 p.enable(subcalls=True)
764 764 try:
765 765 return func()
766 766 finally:
767 767 p.disable()
768 768
769 769 if format == 'kcachegrind':
770 770 import lsprofcalltree
771 771 calltree = lsprofcalltree.KCacheGrind(p)
772 772 calltree.output(fp)
773 773 else:
774 774 # format == 'text'
775 775 stats = lsprof.Stats(p.getstats())
776 776 stats.sort(field)
777 777 stats.pprint(limit=30, file=fp, climit=climit)
778 778
779 779 def statprofile(ui, func, fp):
780 780 try:
781 781 import statprof
782 782 except ImportError:
783 783 raise util.Abort(_(
784 784 'statprof not available - install using "easy_install statprof"'))
785 785
786 786 freq = ui.configint('profiling', 'freq', default=1000)
787 787 if freq > 0:
788 788 statprof.reset(freq)
789 789 else:
790 790 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
791 791
792 792 statprof.start()
793 793 try:
794 794 return func()
795 795 finally:
796 796 statprof.stop()
797 797 statprof.display(fp)
798 798
799 799 def _runcommand(ui, options, cmd, cmdfunc):
800 800 def checkargs():
801 801 try:
802 802 return cmdfunc()
803 803 except error.SignatureError:
804 804 raise error.CommandError(cmd, _("invalid arguments"))
805 805
806 806 if options['profile']:
807 807 profiler = os.getenv('HGPROF')
808 808 if profiler is None:
809 809 profiler = ui.config('profiling', 'type', default='ls')
810 810 if profiler not in ('ls', 'stat'):
811 811 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
812 812 profiler = 'ls'
813 813
814 814 output = ui.config('profiling', 'output')
815 815
816 816 if output:
817 817 path = ui.expandpath(output)
818 818 fp = open(path, 'wb')
819 819 else:
820 820 fp = sys.stderr
821 821
822 822 try:
823 823 if profiler == 'ls':
824 824 return lsprofile(ui, checkargs, fp)
825 825 else:
826 826 return statprofile(ui, checkargs, fp)
827 827 finally:
828 828 if output:
829 829 fp.close()
830 830 else:
831 831 return checkargs()
@@ -1,561 +1,571 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 the global options
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 the global options
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 empty declaration of supported version, extension complains:
502 $ echo "testedwith = ''" >> throw.py
503 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
504 ** Unknown exception encountered with possibly-broken third-party extension throw
505 ** which supports versions unknown of Mercurial.
506 ** Please disable throw and try your action again.
507 ** If that fixes the bug please report it to the extension author.
508 ** Python * (glob)
509 ** Mercurial Distributed SCM (*) (glob)
510 ** Extensions loaded: throw
501 511 If the extension specifies a buglink, show that:
502 512 $ echo 'buglink = "http://example.com/bts"' >> throw.py
503 513 $ rm -f throw.pyc throw.pyo
504 514 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
505 515 ** Unknown exception encountered with possibly-broken third-party extension throw
506 516 ** which supports versions unknown of Mercurial.
507 517 ** Please disable throw and try your action again.
508 518 ** If that fixes the bug please report it to http://example.com/bts
509 519 ** Python * (glob)
510 520 ** Mercurial Distributed SCM (*) (glob)
511 521 ** Extensions loaded: throw
512 522 If the extensions declare outdated versions, accuse the older extension first:
513 523 $ echo "from mercurial import util" >> older.py
514 524 $ echo "util.version = lambda:'2.2'" >> older.py
515 525 $ echo "testedwith = '1.9.3'" >> older.py
516 526 $ echo "testedwith = '2.1.1'" >> throw.py
517 527 $ rm -f throw.pyc throw.pyo
518 528 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
519 529 > throw 2>&1 | egrep '^\*\*'
520 530 ** Unknown exception encountered with possibly-broken third-party extension older
521 531 ** which supports versions 1.9.3 of Mercurial.
522 532 ** Please disable older and try your action again.
523 533 ** If that fixes the bug please report it to the extension author.
524 534 ** Python * (glob)
525 535 ** Mercurial Distributed SCM (version 2.2)
526 536 ** Extensions loaded: throw, older
527 537 One extension only tested with older, one only with newer versions:
528 538 $ echo "util.version = lambda:'2.1.0'" >> older.py
529 539 $ rm -f older.pyc older.pyo
530 540 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
531 541 > throw 2>&1 | egrep '^\*\*'
532 542 ** Unknown exception encountered with possibly-broken third-party extension older
533 543 ** which supports versions 1.9.3 of Mercurial.
534 544 ** Please disable older and try your action again.
535 545 ** If that fixes the bug please report it to the extension author.
536 546 ** Python * (glob)
537 547 ** Mercurial Distributed SCM (version 2.1.0)
538 548 ** Extensions loaded: throw, older
539 549 Older extension is tested with current version, the other only with newer:
540 550 $ echo "util.version = lambda:'1.9.3'" >> older.py
541 551 $ rm -f older.pyc older.pyo
542 552 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
543 553 > throw 2>&1 | egrep '^\*\*'
544 554 ** Unknown exception encountered with possibly-broken third-party extension throw
545 555 ** which supports versions 2.1.1 of Mercurial.
546 556 ** Please disable throw and try your action again.
547 557 ** If that fixes the bug please report it to http://example.com/bts
548 558 ** Python * (glob)
549 559 ** Mercurial Distributed SCM (version 1.9.3)
550 560 ** Extensions loaded: throw, older
551 561
552 562 Declare the version as supporting this hg version, show regular bts link:
553 563 $ hgver=`python -c 'from mercurial import util; print util.version().split("+")[0]'`
554 564 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
555 565 $ rm -f throw.pyc throw.pyo
556 566 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
557 567 ** unknown exception encountered, please report by visiting
558 568 ** http://mercurial.selenic.com/wiki/BugTracker
559 569 ** Python * (glob)
560 570 ** Mercurial Distributed SCM (*) (glob)
561 571 ** Extensions loaded: throw
General Comments 0
You need to be logged in to leave comments. Login now