diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py new file mode 100644 --- /dev/null +++ b/mercurial/dispatch.py @@ -0,0 +1,390 @@ +# dispatch.py - command dispatching for mercurial +# +# Copyright 2005-2007 Matt Mackall +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from node import * +from i18n import _ +import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time +import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook +import cmdutil +import ui as _ui + +class ParseError(Exception): + """Exception raised on errors in parsing the command line.""" + +def run(): + "run the command in sys.argv" + sys.exit(dispatch(sys.argv[1:])) + +def dispatch(args): + "run the command specified in args" + try: + u = _ui.ui(traceback='--traceback' in args) + except util.Abort, inst: + sys.stderr.write(_("abort: %s\n") % inst) + return -1 + return _runcatch(u, args) + +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 cmdutil.AmbiguousCommand, inst: + ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % + (inst.args[0], " ".join(inst.args[1]))) + except cmdutil.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 _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 = cmdutil.findcmd(ui, cmd, commands.table) + 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 = _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 = _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()