##// END OF EJS Templates
alias: do not crash when aliased command has no usage help text
Nicolas Dumazet -
r9993:8bce1e0d stable
parent child Browse files
Show More
@@ -1,500 +1,505 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, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as _ui
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = _ui.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 return -1
27 27 except error.ConfigError, inst:
28 28 sys.stderr.write(_("hg: %s\n") % inst)
29 29 return -1
30 30 return _runcatch(u, args)
31 31
32 32 def _runcatch(ui, args):
33 33 def catchterm(*args):
34 34 raise error.SignalInterrupt
35 35
36 36 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
37 37 num = getattr(signal, name, None)
38 38 if num: signal.signal(num, catchterm)
39 39
40 40 try:
41 41 try:
42 42 # enter the debugger before command execution
43 43 if '--debugger' in args:
44 44 pdb.set_trace()
45 45 try:
46 46 return _dispatch(ui, args)
47 47 finally:
48 48 ui.flush()
49 49 except:
50 50 # enter the debugger when we hit an exception
51 51 if '--debugger' in args:
52 52 pdb.post_mortem(sys.exc_info()[2])
53 53 ui.traceback()
54 54 raise
55 55
56 56 # Global exception handling, alphabetically
57 57 # Mercurial-specific first, followed by built-in and library exceptions
58 58 except error.AmbiguousCommand, inst:
59 59 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
60 60 (inst.args[0], " ".join(inst.args[1])))
61 61 except error.ConfigError, inst:
62 62 ui.warn(_("hg: %s\n") % inst.args[0])
63 63 except error.LockHeld, inst:
64 64 if inst.errno == errno.ETIMEDOUT:
65 65 reason = _('timed out waiting for lock held by %s') % inst.locker
66 66 else:
67 67 reason = _('lock held by %s') % inst.locker
68 68 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
69 69 except error.LockUnavailable, inst:
70 70 ui.warn(_("abort: could not lock %s: %s\n") %
71 71 (inst.desc or inst.filename, inst.strerror))
72 72 except error.ParseError, inst:
73 73 if inst.args[0]:
74 74 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
75 75 commands.help_(ui, inst.args[0])
76 76 else:
77 77 ui.warn(_("hg: %s\n") % inst.args[1])
78 78 commands.help_(ui, 'shortlist')
79 79 except error.RepoError, inst:
80 80 ui.warn(_("abort: %s!\n") % inst)
81 81 except error.ResponseError, inst:
82 82 ui.warn(_("abort: %s") % inst.args[0])
83 83 if not isinstance(inst.args[1], basestring):
84 84 ui.warn(" %r\n" % (inst.args[1],))
85 85 elif not inst.args[1]:
86 86 ui.warn(_(" empty string\n"))
87 87 else:
88 88 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
89 89 except error.RevlogError, inst:
90 90 ui.warn(_("abort: %s!\n") % inst)
91 91 except error.SignalInterrupt:
92 92 ui.warn(_("killed!\n"))
93 93 except error.UnknownCommand, inst:
94 94 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
95 95 commands.help_(ui, 'shortlist')
96 96 except util.Abort, inst:
97 97 ui.warn(_("abort: %s\n") % inst)
98 98 except ImportError, inst:
99 99 m = str(inst).split()[-1]
100 100 ui.warn(_("abort: could not import module %s!\n") % m)
101 101 if m in "mpatch bdiff".split():
102 102 ui.warn(_("(did you forget to compile extensions?)\n"))
103 103 elif m in "zlib".split():
104 104 ui.warn(_("(is your Python install correct?)\n"))
105 105 except IOError, inst:
106 106 if hasattr(inst, "code"):
107 107 ui.warn(_("abort: %s\n") % inst)
108 108 elif hasattr(inst, "reason"):
109 109 try: # usually it is in the form (errno, strerror)
110 110 reason = inst.reason.args[1]
111 111 except: # it might be anything, for example a string
112 112 reason = inst.reason
113 113 ui.warn(_("abort: error: %s\n") % reason)
114 114 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
115 115 if ui.debugflag:
116 116 ui.warn(_("broken pipe\n"))
117 117 elif getattr(inst, "strerror", None):
118 118 if getattr(inst, "filename", None):
119 119 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
120 120 else:
121 121 ui.warn(_("abort: %s\n") % inst.strerror)
122 122 else:
123 123 raise
124 124 except OSError, inst:
125 125 if getattr(inst, "filename", None):
126 126 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
127 127 else:
128 128 ui.warn(_("abort: %s\n") % inst.strerror)
129 129 except KeyboardInterrupt:
130 130 try:
131 131 ui.warn(_("interrupted!\n"))
132 132 except IOError, inst:
133 133 if inst.errno == errno.EPIPE:
134 134 if ui.debugflag:
135 135 ui.warn(_("\nbroken pipe\n"))
136 136 else:
137 137 raise
138 138 except MemoryError:
139 139 ui.warn(_("abort: out of memory\n"))
140 140 except SystemExit, inst:
141 141 # Commands shouldn't sys.exit directly, but give a return code.
142 142 # Just in case catch this and and pass exit code to caller.
143 143 return inst.code
144 144 except socket.error, inst:
145 145 ui.warn(_("abort: %s\n") % inst.args[-1])
146 146 except:
147 147 ui.warn(_("** unknown exception encountered, details follow\n"))
148 148 ui.warn(_("** report bug details to "
149 149 "http://mercurial.selenic.com/bts/\n"))
150 150 ui.warn(_("** or mercurial@selenic.com\n"))
151 151 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
152 152 % util.version())
153 153 ui.warn(_("** Extensions loaded: %s\n")
154 154 % ", ".join([x[0] for x in extensions.extensions()]))
155 155 raise
156 156
157 157 return -1
158 158
159 159 def _findrepo(p):
160 160 while not os.path.isdir(os.path.join(p, ".hg")):
161 161 oldp, p = p, os.path.dirname(p)
162 162 if p == oldp:
163 163 return None
164 164
165 165 return p
166 166
167 167 def aliasargs(fn):
168 168 if hasattr(fn, 'args'):
169 169 return fn.args
170 170 return []
171 171
172 172 class cmdalias(object):
173 173 def __init__(self, name, definition, cmdtable):
174 174 self.name = name
175 175 self.definition = definition
176 176 self.args = []
177 177 self.opts = []
178 178 self.help = ''
179 179 self.norepo = True
180 180
181 181 try:
182 182 cmdutil.findcmd(self.name, cmdtable, True)
183 183 self.shadows = True
184 184 except error.UnknownCommand:
185 185 self.shadows = False
186 186
187 187 if not self.definition:
188 188 def fn(ui, *args):
189 189 ui.warn(_("no definition for alias '%s'\n") % self.name)
190 190 return 1
191 191 self.fn = fn
192 192
193 193 return
194 194
195 195 args = shlex.split(self.definition)
196 196 cmd = args.pop(0)
197 197
198 198 try:
199 self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1]
199 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
200 if len(tableentry) > 2:
201 self.fn, self.opts, self.help = tableentry
202 else:
203 self.fn, self.opts = tableentry
204
200 205 self.args = aliasargs(self.fn) + args
201 206 if cmd not in commands.norepo.split(' '):
202 207 self.norepo = False
203 208 except error.UnknownCommand:
204 209 def fn(ui, *args):
205 210 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
206 211 % (self.name, cmd))
207 212 return 1
208 213 self.fn = fn
209 214 except error.AmbiguousCommand:
210 215 def fn(ui, *args):
211 216 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
212 217 % (self.name, cmd))
213 218 return 1
214 219 self.fn = fn
215 220
216 221 def __call__(self, ui, *args, **opts):
217 222 if self.shadows:
218 223 ui.debug("alias '%s' shadows command\n" % self.name)
219 224
220 225 return self.fn(ui, *args, **opts)
221 226
222 227 def addaliases(ui, cmdtable):
223 228 # aliases are processed after extensions have been loaded, so they
224 229 # may use extension commands. Aliases can also use other alias definitions,
225 230 # but only if they have been defined prior to the current definition.
226 231 for alias, definition in ui.configitems('alias'):
227 232 aliasdef = cmdalias(alias, definition, cmdtable)
228 233 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
229 234 if aliasdef.norepo:
230 235 commands.norepo += ' %s' % alias
231 236
232 237 def _parse(ui, args):
233 238 options = {}
234 239 cmdoptions = {}
235 240
236 241 try:
237 242 args = fancyopts.fancyopts(args, commands.globalopts, options)
238 243 except fancyopts.getopt.GetoptError, inst:
239 244 raise error.ParseError(None, inst)
240 245
241 246 if args:
242 247 cmd, args = args[0], args[1:]
243 248 aliases, i = cmdutil.findcmd(cmd, commands.table,
244 249 ui.config("ui", "strict"))
245 250 cmd = aliases[0]
246 251 args = aliasargs(i[0]) + args
247 252 defaults = ui.config("defaults", cmd)
248 253 if defaults:
249 254 args = map(util.expandpath, shlex.split(defaults)) + args
250 255 c = list(i[1])
251 256 else:
252 257 cmd = None
253 258 c = []
254 259
255 260 # combine global options into local
256 261 for o in commands.globalopts:
257 262 c.append((o[0], o[1], options[o[1]], o[3]))
258 263
259 264 try:
260 265 args = fancyopts.fancyopts(args, c, cmdoptions, True)
261 266 except fancyopts.getopt.GetoptError, inst:
262 267 raise error.ParseError(cmd, inst)
263 268
264 269 # separate global options back out
265 270 for o in commands.globalopts:
266 271 n = o[1]
267 272 options[n] = cmdoptions[n]
268 273 del cmdoptions[n]
269 274
270 275 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
271 276
272 277 def _parseconfig(ui, config):
273 278 """parse the --config options from the command line"""
274 279 for cfg in config:
275 280 try:
276 281 name, value = cfg.split('=', 1)
277 282 section, name = name.split('.', 1)
278 283 if not section or not name:
279 284 raise IndexError
280 285 ui.setconfig(section, name, value)
281 286 except (IndexError, ValueError):
282 287 raise util.Abort(_('malformed --config option: %r '
283 288 '(use --config section.name=value)') % cfg)
284 289
285 290 def _earlygetopt(aliases, args):
286 291 """Return list of values for an option (or aliases).
287 292
288 293 The values are listed in the order they appear in args.
289 294 The options and values are removed from args.
290 295 """
291 296 try:
292 297 argcount = args.index("--")
293 298 except ValueError:
294 299 argcount = len(args)
295 300 shortopts = [opt for opt in aliases if len(opt) == 2]
296 301 values = []
297 302 pos = 0
298 303 while pos < argcount:
299 304 if args[pos] in aliases:
300 305 if pos + 1 >= argcount:
301 306 # ignore and let getopt report an error if there is no value
302 307 break
303 308 del args[pos]
304 309 values.append(args.pop(pos))
305 310 argcount -= 2
306 311 elif args[pos][:2] in shortopts:
307 312 # short option can have no following space, e.g. hg log -Rfoo
308 313 values.append(args.pop(pos)[2:])
309 314 argcount -= 1
310 315 else:
311 316 pos += 1
312 317 return values
313 318
314 319 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
315 320 # run pre-hook, and abort if it fails
316 321 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
317 322 if ret:
318 323 return ret
319 324 ret = _runcommand(ui, options, cmd, d)
320 325 # run post-hook, passing command result
321 326 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
322 327 result = ret)
323 328 return ret
324 329
325 330 _loaded = set()
326 331 def _dispatch(ui, args):
327 332 # read --config before doing anything else
328 333 # (e.g. to change trust settings for reading .hg/hgrc)
329 334 _parseconfig(ui, _earlygetopt(['--config'], args))
330 335
331 336 # check for cwd
332 337 cwd = _earlygetopt(['--cwd'], args)
333 338 if cwd:
334 339 os.chdir(cwd[-1])
335 340
336 341 # read the local repository .hgrc into a local ui object
337 342 path = _findrepo(os.getcwd()) or ""
338 343 if not path:
339 344 lui = ui
340 345 else:
341 346 try:
342 347 lui = ui.copy()
343 348 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
344 349 except IOError:
345 350 pass
346 351
347 352 # now we can expand paths, even ones in .hg/hgrc
348 353 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
349 354 if rpath:
350 355 path = lui.expandpath(rpath[-1])
351 356 lui = ui.copy()
352 357 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
353 358
354 359 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
355 360 # reposetup. Programs like TortoiseHg will call _dispatch several
356 361 # times so we keep track of configured extensions in _loaded.
357 362 extensions.loadall(lui)
358 363 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
359 364
360 365 # (uisetup and extsetup are handled in extensions.loadall)
361 366
362 367 for name, module in exts:
363 368 cmdtable = getattr(module, 'cmdtable', {})
364 369 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
365 370 if overrides:
366 371 ui.warn(_("extension '%s' overrides commands: %s\n")
367 372 % (name, " ".join(overrides)))
368 373 commands.table.update(cmdtable)
369 374 _loaded.add(name)
370 375
371 376 # (reposetup is handled in hg.repository)
372 377
373 378 addaliases(lui, commands.table)
374 379
375 380 # check for fallback encoding
376 381 fallback = lui.config('ui', 'fallbackencoding')
377 382 if fallback:
378 383 encoding.fallbackencoding = fallback
379 384
380 385 fullargs = args
381 386 cmd, func, args, options, cmdoptions = _parse(lui, args)
382 387
383 388 if options["config"]:
384 389 raise util.Abort(_("Option --config may not be abbreviated!"))
385 390 if options["cwd"]:
386 391 raise util.Abort(_("Option --cwd may not be abbreviated!"))
387 392 if options["repository"]:
388 393 raise util.Abort(_(
389 394 "Option -R has to be separated from other options (e.g. not -qR) "
390 395 "and --repository may only be abbreviated as --repo!"))
391 396
392 397 if options["encoding"]:
393 398 encoding.encoding = options["encoding"]
394 399 if options["encodingmode"]:
395 400 encoding.encodingmode = options["encodingmode"]
396 401 if options["time"]:
397 402 def get_times():
398 403 t = os.times()
399 404 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
400 405 t = (t[0], t[1], t[2], t[3], time.clock())
401 406 return t
402 407 s = get_times()
403 408 def print_time():
404 409 t = get_times()
405 410 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
406 411 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
407 412 atexit.register(print_time)
408 413
409 414 if options['verbose'] or options['debug'] or options['quiet']:
410 415 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
411 416 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
412 417 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
413 418 if options['traceback']:
414 419 ui.setconfig('ui', 'traceback', 'on')
415 420 if options['noninteractive']:
416 421 ui.setconfig('ui', 'interactive', 'off')
417 422
418 423 if options['help']:
419 424 return commands.help_(ui, cmd, options['version'])
420 425 elif options['version']:
421 426 return commands.version_(ui)
422 427 elif not cmd:
423 428 return commands.help_(ui, 'shortlist')
424 429
425 430 repo = None
426 431 if cmd not in commands.norepo.split():
427 432 try:
428 433 repo = hg.repository(ui, path=path)
429 434 ui = repo.ui
430 435 if not repo.local():
431 436 raise util.Abort(_("repository '%s' is not local") % path)
432 437 ui.setconfig("bundle", "mainreporoot", repo.root)
433 438 except error.RepoError:
434 439 if cmd not in commands.optionalrepo.split():
435 440 if args and not path: # try to infer -R from command args
436 441 repos = map(_findrepo, args)
437 442 guess = repos[0]
438 443 if guess and repos.count(guess) == len(repos):
439 444 return _dispatch(ui, ['--repository', guess] + fullargs)
440 445 if not path:
441 446 raise error.RepoError(_("There is no Mercurial repository"
442 447 " here (.hg not found)"))
443 448 raise
444 449 args.insert(0, repo)
445 450 elif rpath:
446 451 ui.warn("warning: --repository ignored\n")
447 452
448 453 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
449 454 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
450 455
451 456 def _runcommand(ui, options, cmd, cmdfunc):
452 457 def checkargs():
453 458 try:
454 459 return cmdfunc()
455 460 except error.SignatureError:
456 461 raise error.ParseError(cmd, _("invalid arguments"))
457 462
458 463 if options['profile']:
459 464 format = ui.config('profiling', 'format', default='text')
460 465
461 466 if not format in ['text', 'kcachegrind']:
462 467 ui.warn(_("unrecognized profiling format '%s'"
463 468 " - Ignored\n") % format)
464 469 format = 'text'
465 470
466 471 output = ui.config('profiling', 'output')
467 472
468 473 if output:
469 474 path = ui.expandpath(output)
470 475 ostream = open(path, 'wb')
471 476 else:
472 477 ostream = sys.stderr
473 478
474 479 try:
475 480 from mercurial import lsprof
476 481 except ImportError:
477 482 raise util.Abort(_(
478 483 'lsprof not available - install from '
479 484 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
480 485 p = lsprof.Profiler()
481 486 p.enable(subcalls=True)
482 487 try:
483 488 return checkargs()
484 489 finally:
485 490 p.disable()
486 491
487 492 if format == 'kcachegrind':
488 493 import lsprofcalltree
489 494 calltree = lsprofcalltree.KCacheGrind(p)
490 495 calltree.output(ostream)
491 496 else:
492 497 # format == 'text'
493 498 stats = lsprof.Stats(p.getstats())
494 499 stats.sort()
495 500 stats.pprint(top=10, file=ostream, climit=5)
496 501
497 502 if output:
498 503 ostream.close()
499 504 else:
500 505 return checkargs()
@@ -1,52 +1,57 b''
1 1 #!/bin/sh
2 2
3 3 cat >> $HGRCPATH <<EOF
4 4 [alias]
5 5 myinit = init
6 6 cleanstatus = status -c
7 7 unknown = bargle
8 8 ambiguous = s
9 9 recursive = recursive
10 10 nodefinition =
11 11 mylog = log
12 12 lognull = log -r null
13 13 shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
14 14 dln = lognull --debug
15 nousage = rollback
15 16
16 17 [defaults]
17 18 mylog = -q
18 19 lognull = -q
19 20 log = -v
20 21 EOF
21 22
22 23 echo '% basic'
23 24 hg myinit alias
24 25
25 26 echo '% unknown'
26 27 hg unknown
27 28
28 29 echo '% ambiguous'
29 30 hg ambiguous
30 31
31 32 echo '% recursive'
32 33 hg recursive
33 34
34 35 echo '% no definition'
35 36 hg nodef
36 37
37 38 cd alias
39
40 echo '% no usage'
41 hg nousage
42
38 43 echo foo > foo
39 44 hg ci -Amfoo
40 45
41 46 echo '% with opts'
42 47 hg cleanst
43 48
44 49 echo '% with opts and whitespace'
45 50 hg shortlog
46 51
47 52 echo '% interaction with defaults'
48 53 hg mylog
49 54 hg lognull
50 55
51 56 echo '% properly recursive'
52 57 hg dln
@@ -1,26 +1,28 b''
1 1 % basic
2 2 % unknown
3 3 alias 'unknown' resolves to unknown command 'bargle'
4 4 % ambiguous
5 5 alias 'ambiguous' resolves to ambiguous command 's'
6 6 % recursive
7 7 alias 'recursive' resolves to unknown command 'recursive'
8 8 % no definition
9 9 no definition for alias 'nodefinition'
10 % no usage
11 no rollback information available
10 12 adding foo
11 13 % with opts
12 14 C foo
13 15 % with opts and whitespace
14 16 0 e63c23eaa88a | 1970-01-01 00:00 +0000
15 17 % interaction with defaults
16 18 0:e63c23eaa88a
17 19 -1:000000000000
18 20 % properly recursive
19 21 changeset: -1:0000000000000000000000000000000000000000
20 22 parent: -1:0000000000000000000000000000000000000000
21 23 parent: -1:0000000000000000000000000000000000000000
22 24 manifest: -1:0000000000000000000000000000000000000000
23 25 user:
24 26 date: Thu Jan 01 00:00:00 1970 +0000
25 27 extra: branch=default
26 28
General Comments 0
You need to be logged in to leave comments. Login now