Mercurial > public > mercurial-scm > hg
diff mercurial/cmdutil.py @ 5178: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 |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Wed Aug 15 16:10:36 2007 -0500 +++ b/mercurial/cmdutil.py Wed Aug 15 16:55:13 2007 -0500 @@ -7,10 +7,8 @@ from node import * from i18n import _ -import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex -import bisect, stat -import mdiff, bdiff, util, templater, patch, commands, hg, lock, time -import fancyopts, revlog, version, extensions, hook +import os, sys, bisect, stat +import mdiff, bdiff, util, templater, patch revrangesep = ':' @@ -18,130 +16,8 @@ """Exception raised if command is not in the command table.""" class AmbiguousCommand(Exception): """Exception raised if command shortcut matches more than one command.""" -class ParseError(Exception): - """Exception raised on errors in parsing the command line.""" -def runcatch(ui, args): - def catchterm(*args): - raise util.SignalInterrupt - - for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': - num = getattr(signal, name, None) - if num: signal.signal(num, catchterm) - - try: - try: - # enter the debugger before command execution - if '--debugger' in args: - pdb.set_trace() - try: - return dispatch(ui, args) - finally: - ui.flush() - except: - # enter the debugger when we hit an exception - if '--debugger' in args: - pdb.post_mortem(sys.exc_info()[2]) - ui.print_exc() - raise - - except ParseError, inst: - if inst.args[0]: - ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) - commands.help_(ui, inst.args[0]) - else: - ui.warn(_("hg: %s\n") % inst.args[1]) - commands.help_(ui, 'shortlist') - except AmbiguousCommand, inst: - ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % - (inst.args[0], " ".join(inst.args[1]))) - except UnknownCommand, inst: - ui.warn(_("hg: unknown command '%s'\n") % inst.args[0]) - commands.help_(ui, 'shortlist') - except hg.RepoError, inst: - ui.warn(_("abort: %s!\n") % inst) - except lock.LockHeld, inst: - if inst.errno == errno.ETIMEDOUT: - reason = _('timed out waiting for lock held by %s') % inst.locker - else: - reason = _('lock held by %s') % inst.locker - ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason)) - except lock.LockUnavailable, inst: - ui.warn(_("abort: could not lock %s: %s\n") % - (inst.desc or inst.filename, inst.strerror)) - except revlog.RevlogError, inst: - ui.warn(_("abort: %s!\n") % inst) - except util.SignalInterrupt: - ui.warn(_("killed!\n")) - except KeyboardInterrupt: - try: - ui.warn(_("interrupted!\n")) - except IOError, inst: - if inst.errno == errno.EPIPE: - if ui.debugflag: - ui.warn(_("\nbroken pipe\n")) - else: - raise - except socket.error, inst: - ui.warn(_("abort: %s\n") % inst[1]) - except IOError, inst: - if hasattr(inst, "code"): - ui.warn(_("abort: %s\n") % inst) - elif hasattr(inst, "reason"): - try: # usually it is in the form (errno, strerror) - reason = inst.reason.args[1] - except: # it might be anything, for example a string - reason = inst.reason - ui.warn(_("abort: error: %s\n") % reason) - elif hasattr(inst, "args") and inst[0] == errno.EPIPE: - if ui.debugflag: - ui.warn(_("broken pipe\n")) - elif getattr(inst, "strerror", None): - if getattr(inst, "filename", None): - ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename)) - else: - ui.warn(_("abort: %s\n") % inst.strerror) - else: - raise - except OSError, inst: - if getattr(inst, "filename", None): - ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename)) - else: - ui.warn(_("abort: %s\n") % inst.strerror) - except util.UnexpectedOutput, inst: - ui.warn(_("abort: %s") % inst[0]) - if not isinstance(inst[1], basestring): - ui.warn(" %r\n" % (inst[1],)) - elif not inst[1]: - ui.warn(_(" empty string\n")) - else: - ui.warn("\n%r\n" % util.ellipsis(inst[1])) - except ImportError, inst: - m = str(inst).split()[-1] - ui.warn(_("abort: could not import module %s!\n" % m)) - if m in "mpatch bdiff".split(): - ui.warn(_("(did you forget to compile extensions?)\n")) - elif m in "zlib".split(): - ui.warn(_("(is your Python install correct?)\n")) - - except util.Abort, inst: - ui.warn(_("abort: %s\n") % inst) - except SystemExit, inst: - # Commands shouldn't sys.exit directly, but give a return code. - # Just in case catch this and and pass exit code to caller. - return inst.code - except: - ui.warn(_("** unknown exception encountered, details follow\n")) - ui.warn(_("** report bug details to " - "http://www.selenic.com/mercurial/bts\n")) - ui.warn(_("** or mercurial@selenic.com\n")) - ui.warn(_("** Mercurial Distributed SCM (version %s)\n") - % version.get_version()) - raise - - return -1 - -def findpossible(ui, cmd): +def findpossible(ui, cmd, table): """ Return cmd -> (aliases, command table entry) for each matching command. @@ -149,7 +25,7 @@ """ choice = {} debugchoice = {} - for e in commands.table.keys(): + for e in table.keys(): aliases = e.lstrip("^").split("|") found = None if cmd in aliases: @@ -161,18 +37,18 @@ break if found is not None: if aliases[0].startswith("debug") or found.startswith("debug"): - debugchoice[found] = (aliases, commands.table[e]) + debugchoice[found] = (aliases, table[e]) else: - choice[found] = (aliases, commands.table[e]) + choice[found] = (aliases, table[e]) if not choice and debugchoice: choice = debugchoice return choice -def findcmd(ui, cmd): +def findcmd(ui, cmd, table): """Return (aliases, command table entry) for command string.""" - choice = findpossible(ui, cmd) + choice = findpossible(ui, cmd, table) if choice.has_key(cmd): return choice[cmd] @@ -187,247 +63,6 @@ raise UnknownCommand(cmd) -def findrepo(): - p = os.getcwd() - while not os.path.isdir(os.path.join(p, ".hg")): - oldp, p = p, os.path.dirname(p) - if p == oldp: - return None - - return p - -def parse(ui, args): - options = {} - cmdoptions = {} - - try: - args = fancyopts.fancyopts(args, commands.globalopts, options) - except fancyopts.getopt.GetoptError, inst: - raise ParseError(None, inst) - - if args: - cmd, args = args[0], args[1:] - aliases, i = findcmd(ui, cmd) - cmd = aliases[0] - defaults = ui.config("defaults", cmd) - if defaults: - args = shlex.split(defaults) + args - c = list(i[1]) - else: - cmd = None - c = [] - - # combine global options into local - for o in commands.globalopts: - c.append((o[0], o[1], options[o[1]], o[3])) - - try: - args = fancyopts.fancyopts(args, c, cmdoptions) - except fancyopts.getopt.GetoptError, inst: - raise ParseError(cmd, inst) - - # separate global options back out - for o in commands.globalopts: - n = o[1] - options[n] = cmdoptions[n] - del cmdoptions[n] - - return (cmd, cmd and i[0] or None, args, options, cmdoptions) - -def parseconfig(config): - """parse the --config options from the command line""" - parsed = [] - for cfg in config: - try: - name, value = cfg.split('=', 1) - section, name = name.split('.', 1) - if not section or not name: - raise IndexError - parsed.append((section, name, value)) - except (IndexError, ValueError): - raise util.Abort(_('malformed --config option: %s') % cfg) - return parsed - -def earlygetopt(aliases, args): - """Return list of values for an option (or aliases). - - The values are listed in the order they appear in args. - The options and values are removed from args. - """ - try: - argcount = args.index("--") - except ValueError: - argcount = len(args) - shortopts = [opt for opt in aliases if len(opt) == 2] - values = [] - pos = 0 - while pos < argcount: - if args[pos] in aliases: - if pos + 1 >= argcount: - # ignore and let getopt report an error if there is no value - break - del args[pos] - values.append(args.pop(pos)) - argcount -= 2 - elif args[pos][:2] in shortopts: - # short option can have no following space, e.g. hg log -Rfoo - values.append(args.pop(pos)[2:]) - argcount -= 1 - else: - pos += 1 - return values - -def dispatch(ui, args): - # read --config before doing anything else - # (e.g. to change trust settings for reading .hg/hgrc) - config = earlygetopt(['--config'], args) - if config: - ui.updateopts(config=parseconfig(config)) - - # check for cwd - cwd = earlygetopt(['--cwd'], args) - if cwd: - os.chdir(cwd[-1]) - - # read the local repository .hgrc into a local ui object - path = findrepo() or "" - if not path: - lui = ui - if path: - try: - lui = commands.ui.ui(parentui=ui) - lui.readconfig(os.path.join(path, ".hg", "hgrc")) - except IOError: - pass - - # now we can expand paths, even ones in .hg/hgrc - rpath = earlygetopt(["-R", "--repository", "--repo"], args) - if rpath: - path = lui.expandpath(rpath[-1]) - lui = commands.ui.ui(parentui=ui) - lui.readconfig(os.path.join(path, ".hg", "hgrc")) - - extensions.loadall(lui) - # check for fallback encoding - fallback = lui.config('ui', 'fallbackencoding') - if fallback: - util._fallbackencoding = fallback - - fullargs = args - cmd, func, args, options, cmdoptions = parse(lui, args) - - if options["config"]: - raise util.Abort(_("Option --config may not be abbreviated!")) - if options["cwd"]: - raise util.Abort(_("Option --cwd may not be abbreviated!")) - if options["repository"]: - raise util.Abort(_( - "Option -R has to be separated from other options (i.e. not -qR) " - "and --repository may only be abbreviated as --repo!")) - - if options["encoding"]: - util._encoding = options["encoding"] - if options["encodingmode"]: - util._encodingmode = options["encodingmode"] - if options["time"]: - def get_times(): - t = os.times() - if t[4] == 0.0: # Windows leaves this as zero, so use time.clock() - t = (t[0], t[1], t[2], t[3], time.clock()) - return t - s = get_times() - def print_time(): - t = get_times() - ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") % - (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3])) - atexit.register(print_time) - - ui.updateopts(options["verbose"], options["debug"], options["quiet"], - not options["noninteractive"], options["traceback"]) - - if options['help']: - return commands.help_(ui, cmd, options['version']) - elif options['version']: - return commands.version_(ui) - elif not cmd: - return commands.help_(ui, 'shortlist') - - repo = None - if cmd not in commands.norepo.split(): - try: - repo = hg.repository(ui, path=path) - ui = repo.ui - if not repo.local(): - raise util.Abort(_("repository '%s' is not local") % path) - except hg.RepoError: - if cmd not in commands.optionalrepo.split(): - if not path: - raise hg.RepoError(_("There is no Mercurial repository here" - " (.hg not found)")) - raise - d = lambda: func(ui, repo, *args, **cmdoptions) - else: - d = lambda: func(ui, *args, **cmdoptions) - - # run pre-hook, and abort if it fails - ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs)) - if ret: - return ret - ret = runcommand(ui, options, cmd, d) - # run post-hook, passing command result - hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs), - result = ret) - return ret - -def runcommand(ui, options, cmd, cmdfunc): - def checkargs(): - try: - return cmdfunc() - except TypeError, inst: - # was this an argument error? - tb = traceback.extract_tb(sys.exc_info()[2]) - if len(tb) != 2: # no - raise - raise ParseError(cmd, _("invalid arguments")) - - if options['profile']: - import hotshot, hotshot.stats - prof = hotshot.Profile("hg.prof") - try: - try: - return prof.runcall(checkargs) - except: - try: - ui.warn(_('exception raised - generating ' - 'profile anyway\n')) - except: - pass - raise - finally: - prof.close() - stats = hotshot.stats.load("hg.prof") - stats.strip_dirs() - stats.sort_stats('time', 'calls') - stats.print_stats(40) - elif options['lsprof']: - try: - from mercurial import lsprof - except ImportError: - raise util.Abort(_( - 'lsprof not available - install from ' - 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/')) - p = lsprof.Profiler() - p.enable(subcalls=True) - try: - return checkargs() - finally: - p.disable() - stats = lsprof.Stats(p.getstats()) - stats.sort() - stats.pprint(top=10, file=sys.stderr, climit=5) - else: - return checkargs() - def bail_if_changed(repo): modified, added, removed, deleted = repo.status()[:4] if modified or added or removed or deleted: