mercurial/dispatch.py
changeset 5178 18a9fbb5cd78
child 5192 60acf1432ee0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/dispatch.py	Wed Aug 15 16:55:13 2007 -0500
@@ -0,0 +1,390 @@
+# dispatch.py - command dispatching for mercurial
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# 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()