comparison mercurial/dispatch.py @ 5185:18a9fbb5cd78

dispatch: move command dispatching into its own module - move command dispatching functions from commands and cmdutil to dispatch - change findcmd to take a table argument - remove circular import of commands in cmdutil - privatize helper functions in dispatch
author Matt Mackall <mpm@selenic.com>
date Wed, 15 Aug 2007 16:55:13 -0500
parents
children 60acf1432ee0
comparison
equal deleted inserted replaced
5184:92236732d5a1 5185:18a9fbb5cd78
1 # dispatch.py - command dispatching for mercurial
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from node import *
9 from i18n import _
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
12 import cmdutil
13 import ui as _ui
14
15 class ParseError(Exception):
16 """Exception raised on errors in parsing the command line."""
17
18 def run():
19 "run the command in sys.argv"
20 sys.exit(dispatch(sys.argv[1:]))
21
22 def dispatch(args):
23 "run the command specified in args"
24 try:
25 u = _ui.ui(traceback='--traceback' in args)
26 except util.Abort, inst:
27 sys.stderr.write(_("abort: %s\n") % inst)
28 return -1
29 return _runcatch(u, args)
30
31 def _runcatch(ui, args):
32 def catchterm(*args):
33 raise util.SignalInterrupt
34
35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
36 num = getattr(signal, name, None)
37 if num: signal.signal(num, catchterm)
38
39 try:
40 try:
41 # enter the debugger before command execution
42 if '--debugger' in args:
43 pdb.set_trace()
44 try:
45 return _dispatch(ui, args)
46 finally:
47 ui.flush()
48 except:
49 # enter the debugger when we hit an exception
50 if '--debugger' in args:
51 pdb.post_mortem(sys.exc_info()[2])
52 ui.print_exc()
53 raise
54
55 except ParseError, inst:
56 if inst.args[0]:
57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
58 commands.help_(ui, inst.args[0])
59 else:
60 ui.warn(_("hg: %s\n") % inst.args[1])
61 commands.help_(ui, 'shortlist')
62 except cmdutil.AmbiguousCommand, inst:
63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
64 (inst.args[0], " ".join(inst.args[1])))
65 except cmdutil.UnknownCommand, inst:
66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
67 commands.help_(ui, 'shortlist')
68 except hg.RepoError, inst:
69 ui.warn(_("abort: %s!\n") % inst)
70 except lock.LockHeld, inst:
71 if inst.errno == errno.ETIMEDOUT:
72 reason = _('timed out waiting for lock held by %s') % inst.locker
73 else:
74 reason = _('lock held by %s') % inst.locker
75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
76 except lock.LockUnavailable, inst:
77 ui.warn(_("abort: could not lock %s: %s\n") %
78 (inst.desc or inst.filename, inst.strerror))
79 except revlog.RevlogError, inst:
80 ui.warn(_("abort: %s!\n") % inst)
81 except util.SignalInterrupt:
82 ui.warn(_("killed!\n"))
83 except KeyboardInterrupt:
84 try:
85 ui.warn(_("interrupted!\n"))
86 except IOError, inst:
87 if inst.errno == errno.EPIPE:
88 if ui.debugflag:
89 ui.warn(_("\nbroken pipe\n"))
90 else:
91 raise
92 except socket.error, inst:
93 ui.warn(_("abort: %s\n") % inst[1])
94 except IOError, inst:
95 if hasattr(inst, "code"):
96 ui.warn(_("abort: %s\n") % inst)
97 elif hasattr(inst, "reason"):
98 try: # usually it is in the form (errno, strerror)
99 reason = inst.reason.args[1]
100 except: # it might be anything, for example a string
101 reason = inst.reason
102 ui.warn(_("abort: error: %s\n") % reason)
103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
104 if ui.debugflag:
105 ui.warn(_("broken pipe\n"))
106 elif getattr(inst, "strerror", None):
107 if getattr(inst, "filename", None):
108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
109 else:
110 ui.warn(_("abort: %s\n") % inst.strerror)
111 else:
112 raise
113 except OSError, inst:
114 if getattr(inst, "filename", None):
115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
116 else:
117 ui.warn(_("abort: %s\n") % inst.strerror)
118 except util.UnexpectedOutput, inst:
119 ui.warn(_("abort: %s") % inst[0])
120 if not isinstance(inst[1], basestring):
121 ui.warn(" %r\n" % (inst[1],))
122 elif not inst[1]:
123 ui.warn(_(" empty string\n"))
124 else:
125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
126 except ImportError, inst:
127 m = str(inst).split()[-1]
128 ui.warn(_("abort: could not import module %s!\n" % m))
129 if m in "mpatch bdiff".split():
130 ui.warn(_("(did you forget to compile extensions?)\n"))
131 elif m in "zlib".split():
132 ui.warn(_("(is your Python install correct?)\n"))
133
134 except util.Abort, inst:
135 ui.warn(_("abort: %s\n") % inst)
136 except SystemExit, inst:
137 # Commands shouldn't sys.exit directly, but give a return code.
138 # Just in case catch this and and pass exit code to caller.
139 return inst.code
140 except:
141 ui.warn(_("** unknown exception encountered, details follow\n"))
142 ui.warn(_("** report bug details to "
143 "http://www.selenic.com/mercurial/bts\n"))
144 ui.warn(_("** or mercurial@selenic.com\n"))
145 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
146 % version.get_version())
147 raise
148
149 return -1
150
151 def _findrepo():
152 p = os.getcwd()
153 while not os.path.isdir(os.path.join(p, ".hg")):
154 oldp, p = p, os.path.dirname(p)
155 if p == oldp:
156 return None
157
158 return p
159
160 def _parse(ui, args):
161 options = {}
162 cmdoptions = {}
163
164 try:
165 args = fancyopts.fancyopts(args, commands.globalopts, options)
166 except fancyopts.getopt.GetoptError, inst:
167 raise ParseError(None, inst)
168
169 if args:
170 cmd, args = args[0], args[1:]
171 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
172 cmd = aliases[0]
173 defaults = ui.config("defaults", cmd)
174 if defaults:
175 args = shlex.split(defaults) + args
176 c = list(i[1])
177 else:
178 cmd = None
179 c = []
180
181 # combine global options into local
182 for o in commands.globalopts:
183 c.append((o[0], o[1], options[o[1]], o[3]))
184
185 try:
186 args = fancyopts.fancyopts(args, c, cmdoptions)
187 except fancyopts.getopt.GetoptError, inst:
188 raise ParseError(cmd, inst)
189
190 # separate global options back out
191 for o in commands.globalopts:
192 n = o[1]
193 options[n] = cmdoptions[n]
194 del cmdoptions[n]
195
196 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
197
198 def _parseconfig(config):
199 """parse the --config options from the command line"""
200 parsed = []
201 for cfg in config:
202 try:
203 name, value = cfg.split('=', 1)
204 section, name = name.split('.', 1)
205 if not section or not name:
206 raise IndexError
207 parsed.append((section, name, value))
208 except (IndexError, ValueError):
209 raise util.Abort(_('malformed --config option: %s') % cfg)
210 return parsed
211
212 def _earlygetopt(aliases, args):
213 """Return list of values for an option (or aliases).
214
215 The values are listed in the order they appear in args.
216 The options and values are removed from args.
217 """
218 try:
219 argcount = args.index("--")
220 except ValueError:
221 argcount = len(args)
222 shortopts = [opt for opt in aliases if len(opt) == 2]
223 values = []
224 pos = 0
225 while pos < argcount:
226 if args[pos] in aliases:
227 if pos + 1 >= argcount:
228 # ignore and let getopt report an error if there is no value
229 break
230 del args[pos]
231 values.append(args.pop(pos))
232 argcount -= 2
233 elif args[pos][:2] in shortopts:
234 # short option can have no following space, e.g. hg log -Rfoo
235 values.append(args.pop(pos)[2:])
236 argcount -= 1
237 else:
238 pos += 1
239 return values
240
241 def _dispatch(ui, args):
242 # read --config before doing anything else
243 # (e.g. to change trust settings for reading .hg/hgrc)
244 config = _earlygetopt(['--config'], args)
245 if config:
246 ui.updateopts(config=_parseconfig(config))
247
248 # check for cwd
249 cwd = _earlygetopt(['--cwd'], args)
250 if cwd:
251 os.chdir(cwd[-1])
252
253 # read the local repository .hgrc into a local ui object
254 path = _findrepo() or ""
255 if not path:
256 lui = ui
257 if path:
258 try:
259 lui = _ui.ui(parentui=ui)
260 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
261 except IOError:
262 pass
263
264 # now we can expand paths, even ones in .hg/hgrc
265 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
266 if rpath:
267 path = lui.expandpath(rpath[-1])
268 lui = _ui.ui(parentui=ui)
269 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
270
271 extensions.loadall(lui)
272 # check for fallback encoding
273 fallback = lui.config('ui', 'fallbackencoding')
274 if fallback:
275 util._fallbackencoding = fallback
276
277 fullargs = args
278 cmd, func, args, options, cmdoptions = _parse(lui, args)
279
280 if options["config"]:
281 raise util.Abort(_("Option --config may not be abbreviated!"))
282 if options["cwd"]:
283 raise util.Abort(_("Option --cwd may not be abbreviated!"))
284 if options["repository"]:
285 raise util.Abort(_(
286 "Option -R has to be separated from other options (i.e. not -qR) "
287 "and --repository may only be abbreviated as --repo!"))
288
289 if options["encoding"]:
290 util._encoding = options["encoding"]
291 if options["encodingmode"]:
292 util._encodingmode = options["encodingmode"]
293 if options["time"]:
294 def get_times():
295 t = os.times()
296 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
297 t = (t[0], t[1], t[2], t[3], time.clock())
298 return t
299 s = get_times()
300 def print_time():
301 t = get_times()
302 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
303 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
304 atexit.register(print_time)
305
306 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
307 not options["noninteractive"], options["traceback"])
308
309 if options['help']:
310 return commands.help_(ui, cmd, options['version'])
311 elif options['version']:
312 return commands.version_(ui)
313 elif not cmd:
314 return commands.help_(ui, 'shortlist')
315
316 repo = None
317 if cmd not in commands.norepo.split():
318 try:
319 repo = hg.repository(ui, path=path)
320 ui = repo.ui
321 if not repo.local():
322 raise util.Abort(_("repository '%s' is not local") % path)
323 except hg.RepoError:
324 if cmd not in commands.optionalrepo.split():
325 if not path:
326 raise hg.RepoError(_("There is no Mercurial repository here"
327 " (.hg not found)"))
328 raise
329 d = lambda: func(ui, repo, *args, **cmdoptions)
330 else:
331 d = lambda: func(ui, *args, **cmdoptions)
332
333 # run pre-hook, and abort if it fails
334 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
335 if ret:
336 return ret
337 ret = _runcommand(ui, options, cmd, d)
338 # run post-hook, passing command result
339 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
340 result = ret)
341 return ret
342
343 def _runcommand(ui, options, cmd, cmdfunc):
344 def checkargs():
345 try:
346 return cmdfunc()
347 except TypeError, inst:
348 # was this an argument error?
349 tb = traceback.extract_tb(sys.exc_info()[2])
350 if len(tb) != 2: # no
351 raise
352 raise ParseError(cmd, _("invalid arguments"))
353
354 if options['profile']:
355 import hotshot, hotshot.stats
356 prof = hotshot.Profile("hg.prof")
357 try:
358 try:
359 return prof.runcall(checkargs)
360 except:
361 try:
362 ui.warn(_('exception raised - generating '
363 'profile anyway\n'))
364 except:
365 pass
366 raise
367 finally:
368 prof.close()
369 stats = hotshot.stats.load("hg.prof")
370 stats.strip_dirs()
371 stats.sort_stats('time', 'calls')
372 stats.print_stats(40)
373 elif options['lsprof']:
374 try:
375 from mercurial import lsprof
376 except ImportError:
377 raise util.Abort(_(
378 'lsprof not available - install from '
379 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
380 p = lsprof.Profiler()
381 p.enable(subcalls=True)
382 try:
383 return checkargs()
384 finally:
385 p.disable()
386 stats = lsprof.Stats(p.getstats())
387 stats.sort()
388 stats.pprint(top=10, file=sys.stderr, climit=5)
389 else:
390 return checkargs()