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