|
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() |