changeset 4549:0c61124ad877

dispatch: move dispatching code to cmdutil
author Matt Mackall <mpm@selenic.com>
date Mon, 11 Jun 2007 21:09:24 -0500
parents c9fcebbfc422
children 6ed91894261e
files hgext/fetch.py hgext/mq.py mercurial/cmdutil.py mercurial/commands.py tests/test-ui-config
diffstat 5 files changed, 334 insertions(+), 337 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/fetch.py
+++ b/hgext/fetch.py
@@ -7,7 +7,7 @@
 
 from mercurial.i18n import _
 from mercurial.node import *
-from mercurial import commands, hg, node, util
+from mercurial import commands, cmdutil, hg, node, util
 
 def fetch(ui, repo, source='default', **opts):
     '''Pull changes from a remote repository, merge new changes if needed.
@@ -42,7 +42,7 @@ def fetch(ui, repo, source='default', **
                       (len(newheads) - 1))
         if not err:
             mod, add, rem = repo.status(wlock=wlock)[:3]
-            message = (commands.logmessage(opts) or
+            message = (cmdutil.logmessage(opts) or
                        (_('Automated merge with %s') % other.url()))
             n = repo.commit(mod + add + rem, message,
                             opts['user'], opts['date'], lock=lock, wlock=wlock,
@@ -51,7 +51,7 @@ def fetch(ui, repo, source='default', **
                         'with local\n') % (repo.changelog.rev(n),
                                            short(n)))
     def pull():
-        commands.setremoteconfig(ui, opts)
+        cmdutil.setremoteconfig(ui, opts)
 
         other = hg.repository(ui, ui.expandpath(source))
         ui.status(_('pulling from %s\n') % ui.expandpath(source))
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1579,7 +1579,7 @@ def clone(ui, source, dest=None, **opts)
     Source patch repository is looked for in <src>/.hg/patches by
     default.  Use -p <url> to change.
     '''
-    commands.setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
     if dest is None:
         dest = hg.defaultdest(source)
     sr = hg.repository(ui, ui.expandpath(source))
@@ -1670,7 +1670,7 @@ def new(ui, repo, patch, **opts):
     If none is specified, the patch header is empty and the
     commit message is 'New patch: PATCH'"""
     q = repo.mq
-    message = commands.logmessage(opts)
+    message = cmdutil.logmessage(opts)
     if opts['edit']:
         message = ui.edit(message, ui.username())
     q.new(repo, patch, msg=message, force=opts['force'])
@@ -1688,7 +1688,7 @@ def refresh(ui, repo, *pats, **opts):
     git-style patches (--git or [diff] git=1) to track copies and renames.
     """
     q = repo.mq
-    message = commands.logmessage(opts)
+    message = cmdutil.logmessage(opts)
     if opts['edit']:
         if message:
             raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
@@ -1724,7 +1724,7 @@ def fold(ui, repo, *files, **opts):
     if not q.check_toppatch(repo):
         raise util.Abort(_('No patches applied'))
 
-    message = commands.logmessage(opts)
+    message = cmdutil.logmessage(opts)
     if opts['edit']:
         if message:
             raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
@@ -1964,7 +1964,7 @@ def restore(ui, repo, rev, **opts):
 def save(ui, repo, **opts):
     """save current queue state"""
     q = repo.mq
-    message = commands.logmessage(opts)
+    message = cmdutil.logmessage(opts)
     ret = q.save(repo, msg=message)
     if ret:
         return ret
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -7,10 +7,279 @@
 
 from node import *
 from i18n import _
-import os, sys, mdiff, bdiff, util, templater, patch
+import os, sys, mdiff, bdiff, util, templater, patch, commands
+import atexit, signal, pdb, hg, lock, fancyopts, traceback
+import socket, revlog, version, extensions, errno
 
 revrangesep = ':'
 
+class UnknownCommand(Exception):
+    """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(u, 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:
+        return dispatch(u, args)
+    except ParseError, inst:
+        if inst.args[0]:
+            u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
+            commands.help_(u, inst.args[0])
+        else:
+            u.warn(_("hg: %s\n") % inst.args[1])
+            commands.help_(u, 'shortlist')
+    except AmbiguousCommand, inst:
+        u.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
+                (inst.args[0], " ".join(inst.args[1])))
+    except UnknownCommand, inst:
+        u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
+        commands.help_(u, 'shortlist')
+    except hg.RepoError, inst:
+        u.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
+        u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
+    except lock.LockUnavailable, inst:
+        u.warn(_("abort: could not lock %s: %s\n") %
+               (inst.desc or inst.filename, inst.strerror))
+    except revlog.RevlogError, inst:
+        u.warn(_("abort: %s!\n") % inst)
+    except util.SignalInterrupt:
+        u.warn(_("killed!\n"))
+    except KeyboardInterrupt:
+        try:
+            u.warn(_("interrupted!\n"))
+        except IOError, inst:
+            if inst.errno == errno.EPIPE:
+                if u.debugflag:
+                    u.warn(_("\nbroken pipe\n"))
+            else:
+                raise
+    except socket.error, inst:
+        u.warn(_("abort: %s\n") % inst[1])
+    except IOError, inst:
+        if hasattr(inst, "code"):
+            u.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
+            u.warn(_("abort: error: %s\n") % reason)
+        elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
+            if u.debugflag:
+                u.warn(_("broken pipe\n"))
+        elif getattr(inst, "strerror", None):
+            if getattr(inst, "filename", None):
+                u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
+            else:
+                u.warn(_("abort: %s\n") % inst.strerror)
+        else:
+            raise
+    except OSError, inst:
+        if getattr(inst, "filename", None):
+            u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
+        else:
+            u.warn(_("abort: %s\n") % inst.strerror)
+    except util.UnexpectedOutput, inst:
+        u.warn(_("abort: %s") % inst[0])
+        if not isinstance(inst[1], basestring):
+            u.warn(" %r\n" % (inst[1],))
+        elif not inst[1]:
+            u.warn(_(" empty string\n"))
+        else:
+            u.warn("\n%r\n" % util.ellipsis(inst[1]))
+    except util.Abort, inst:
+        u.warn(_("abort: %s\n") % inst)
+    except TypeError, inst:
+        # was this an argument error?
+        tb = traceback.extract_tb(sys.exc_info()[2])
+        if len(tb) > 2: # no
+            raise
+        u.debug(inst, "\n")
+        u.warn(_("%s: invalid arguments\n") % cmd)
+        commands.help_(u, cmd)
+    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:
+        u.warn(_("** unknown exception encountered, details follow\n"))
+        u.warn(_("** report bug details to "
+                 "http://www.selenic.com/mercurial/bts\n"))
+        u.warn(_("** or mercurial@selenic.com\n"))
+        u.warn(_("** Mercurial Distributed SCM (version %s)\n")
+               % version.get_version())
+        raise
+
+    return -1
+
+def findpossible(ui, cmd):
+    """
+    Return cmd -> (aliases, command table entry)
+    for each matching command.
+    Return debug commands (or their aliases) only if no normal command matches.
+    """
+    choice = {}
+    debugchoice = {}
+    for e in commands.table.keys():
+        aliases = e.lstrip("^").split("|")
+        found = None
+        if cmd in aliases:
+            found = cmd
+        elif not ui.config("ui", "strict"):
+            for a in aliases:
+                if a.startswith(cmd):
+                    found = a
+                    break
+        if found is not None:
+            if aliases[0].startswith("debug") or found.startswith("debug"):
+                debugchoice[found] = (aliases, commands.table[e])
+            else:
+                choice[found] = (aliases, commands.table[e])
+
+    if not choice and debugchoice:
+        choice = debugchoice
+
+    return choice
+
+def findcmd(ui, cmd):
+    """Return (aliases, command table entry) for command string."""
+    choice = findpossible(ui, cmd)
+
+    if choice.has_key(cmd):
+        return choice[cmd]
+
+    if len(choice) > 1:
+        clist = choice.keys()
+        clist.sort()
+        raise AmbiguousCommand(cmd, clist)
+
+    if choice:
+        return choice.values()[0]
+
+    raise UnknownCommand(cmd)
+
+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 dispatch(u, args):
+    extensions.loadall(u)
+    u.addreadhook(extensions.loadall)
+
+    cmd, func, args, options, cmdoptions = parse(u, args)
+
+    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()
+            u.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)
+
+    if options['cwd']:
+        os.chdir(options['cwd'])
+
+    u.updateopts(options["verbose"], options["debug"], options["quiet"],
+                 not options["noninteractive"], options["traceback"],
+                 parseconfig(options["config"]))
+
+    path = u.expandpath(options["repository"]) or ""
+    repo = path and hg.repository(u, path=path) or None
+    if repo and not repo.local():
+        raise util.Abort(_("repository '%s' is not local") % path)
+
+    if options['help']:
+        return commands.help_(u, cmd, options['version'])
+    elif options['version']:
+        return commands.version_(u)
+    elif not cmd:
+        return commands.help_(u, 'shortlist')
+
+    if cmd not in commands.norepo.split():
+        try:
+            if not repo:
+                repo = hg.repository(u, path=path)
+            u = repo.ui
+        except hg.RepoError:
+            if cmd not in commands.optionalrepo.split():
+                raise
+        d = lambda: func(u, repo, *args, **cmdoptions)
+    else:
+        d = lambda: func(u, *args, **cmdoptions)
+
+    return runcommand(u, options, d)
+
 def runcommand(u, options, d):
     # enter the debugger before command execution
     if options['debugger']:
@@ -64,6 +333,37 @@ def runcommand(u, options, d):
         u.print_exc()
         raise
 
+def bail_if_changed(repo):
+    modified, added, removed, deleted = repo.status()[:4]
+    if modified or added or removed or deleted:
+        raise util.Abort(_("outstanding uncommitted changes"))
+
+def logmessage(opts):
+    """ get the log message according to -m and -l option """
+    message = opts['message']
+    logfile = opts['logfile']
+
+    if message and logfile:
+        raise util.Abort(_('options --message and --logfile are mutually '
+                           'exclusive'))
+    if not message and logfile:
+        try:
+            if logfile == '-':
+                message = sys.stdin.read()
+            else:
+                message = open(logfile).read()
+        except IOError, inst:
+            raise util.Abort(_("can't read commit message '%s': %s") %
+                             (logfile, inst.strerror))
+    return message
+
+def setremoteconfig(ui, opts):
+    "copy remote options to ui tree"
+    if opts.get('ssh'):
+        ui.setconfig("ui", "ssh", opts['ssh'])
+    if opts.get('remotecmd'):
+        ui.setconfig("ui", "remotecmd", opts['remotecmd'])
+
 def parseurl(url, revs):
     '''parse url#branch, returning url, branch + revs'''
 
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -8,48 +8,12 @@
 import demandimport; demandimport.enable()
 from node import *
 from i18n import _
-import bisect, os, re, sys, signal, urllib, pdb, shlex, stat
-import fancyopts, ui, hg, util, lock, revlog, bundlerepo, extensions
+import bisect, os, re, sys, urllib, shlex, stat
+import ui, hg, util, revlog, bundlerepo, extensions
 import difflib, patch, time, help, mdiff, tempfile
-import traceback, errno, version, atexit, socket
+import errno, version, socket
 import archival, changegroup, cmdutil, hgweb.server, sshserver
 
-class UnknownCommand(Exception):
-    """Exception raised if command is not in the command table."""
-class AmbiguousCommand(Exception):
-    """Exception raised if command shortcut matches more than one command."""
-
-def bail_if_changed(repo):
-    modified, added, removed, deleted = repo.status()[:4]
-    if modified or added or removed or deleted:
-        raise util.Abort(_("outstanding uncommitted changes"))
-
-def logmessage(opts):
-    """ get the log message according to -m and -l option """
-    message = opts['message']
-    logfile = opts['logfile']
-
-    if message and logfile:
-        raise util.Abort(_('options --message and --logfile are mutually '
-                           'exclusive'))
-    if not message and logfile:
-        try:
-            if logfile == '-':
-                message = sys.stdin.read()
-            else:
-                message = open(logfile).read()
-        except IOError, inst:
-            raise util.Abort(_("can't read commit message '%s': %s") %
-                             (logfile, inst.strerror))
-    return message
-
-def setremoteconfig(ui, opts):
-    "copy remote options to ui tree"
-    if opts.get('ssh'):
-        ui.setconfig("ui", "ssh", opts['ssh'])
-    if opts.get('remotecmd'):
-        ui.setconfig("ui", "remotecmd", opts['remotecmd'])
-
 # Commands start here, listed alphabetically
 
 def add(ui, repo, *pats, **opts):
@@ -205,7 +169,7 @@ def backout(ui, repo, node=None, rev=Non
     if not rev:
         rev = node
 
-    bail_if_changed(repo)
+    cmdutil.bail_if_changed(repo)
     op1, op2 = repo.dirstate.parents()
     if op2 != nullid:
         raise util.Abort(_('outstanding uncommitted merge'))
@@ -335,7 +299,7 @@ def bundle(ui, repo, fname, dest=None, *
                         seen[p] = 1
                         visit.append(p)
     else:
-        setremoteconfig(ui, opts)
+        cmdutil.setremoteconfig(ui, opts)
         dest, revs = cmdutil.parseurl(
             ui.expandpath(dest or 'default-push', dest or 'default'), revs)
         other = hg.repository(ui, dest)
@@ -407,7 +371,7 @@ def clone(ui, source, dest=None, **opts)
     Look at the help text for the pull command for important details
     about ssh:// URLs.
     """
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
     hg.clone(ui, source, dest,
              pull=opts['pull'],
              stream=opts['uncompressed'],
@@ -425,7 +389,7 @@ def commit(ui, repo, *pats, **opts):
     If no commit message is specified, the editor configured in your hgrc
     or in the EDITOR environment variable is started to enter a message.
     """
-    message = logmessage(opts)
+    message = cmdutil.logmessage(opts)
 
     if opts['addremove']:
         cmdutil.addremove(repo, pats, opts)
@@ -685,7 +649,7 @@ def debugcomplete(ui, cmd='', **opts):
         options = []
         otables = [globalopts]
         if cmd:
-            aliases, entry = findcmd(ui, cmd)
+            aliases, entry = cmdutil.findcmd(ui, cmd)
             otables.append(entry[1])
         for t in otables:
             for o in t:
@@ -695,7 +659,7 @@ def debugcomplete(ui, cmd='', **opts):
         ui.write("%s\n" % "\n".join(options))
         return
 
-    clist = findpossible(ui, cmd).keys()
+    clist = cmdutil.findpossible(ui, cmd).keys()
     clist.sort()
     ui.write("%s\n" % "\n".join(clist))
 
@@ -1295,7 +1259,7 @@ def help_(ui, name=None, with_version=Fa
         if with_version:
             version_(ui)
             ui.write('\n')
-        aliases, i = findcmd(ui, name)
+        aliases, i = cmdutil.findcmd(ui, name)
         # synopsis
         ui.write("%s\n\n" % i[2])
 
@@ -1357,7 +1321,7 @@ def help_(ui, name=None, with_version=Fa
                 v = i
                 header = l[-1]
         if not v:
-            raise UnknownCommand(name)
+            raise cmdutil.UnknownCommand(name)
 
         # description
         doc = help.helptable[v]
@@ -1373,7 +1337,7 @@ def help_(ui, name=None, with_version=Fa
         try:
             mod = extensions.find(name)
         except KeyError:
-            raise UnknownCommand(name)
+            raise cmdutil.UnknownCommand(name)
 
         doc = (mod.__doc__ or _('No help text available')).splitlines(0)
         ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
@@ -1399,7 +1363,7 @@ def help_(ui, name=None, with_version=Fa
                 f(name)
                 i = None
                 break
-            except UnknownCommand, inst:
+            except cmdutil.UnknownCommand, inst:
                 i = inst
         if i:
             raise i
@@ -1506,7 +1470,7 @@ def import_(ui, repo, patch1, *patches, 
     patches = (patch1,) + patches
 
     if opts.get('exact') or not opts['force']:
-        bail_if_changed(repo)
+        cmdutil.bail_if_changed(repo)
 
     d = opts["base"]
     strip = opts["strip"]
@@ -1528,7 +1492,7 @@ def import_(ui, repo, patch1, *patches, 
             raise util.Abort(_('no diffs found'))
 
         try:
-            cmdline_message = logmessage(opts)
+            cmdline_message = cmdutil.logmessage(opts)
             if cmdline_message:
                 # pickup the cmdline msg
                 message = cmdline_message
@@ -1587,7 +1551,7 @@ def incoming(ui, repo, source="default",
     See pull for valid source format details.
     """
     source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, source)
     ui.status(_('comparing with %s\n') % source)
@@ -1653,7 +1617,7 @@ def init(ui, dest=".", **opts):
     Look at the help text for the pull command for important details
     about ssh:// URLs.
     """
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
     hg.repository(ui, dest, create=1)
 
 def locate(ui, repo, *pats, **opts):
@@ -1890,7 +1854,7 @@ def outgoing(ui, repo, dest=None, **opts
     """
     dest, revs = cmdutil.parseurl(
         ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
     if revs:
         revs = [repo.lookup(rev) for rev in revs]
 
@@ -2005,7 +1969,7 @@ def pull(ui, repo, source="default", **o
       with the --ssh command line option.
     """
     source, revs = cmdutil.parseurl(ui.expandpath(source), opts['rev'])
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, source)
     ui.status(_('pulling from %s\n') % (source))
@@ -2051,7 +2015,7 @@ def push(ui, repo, dest=None, **opts):
     """
     dest, revs = cmdutil.parseurl(
         ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
-    setremoteconfig(ui, opts)
+    cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, dest)
     ui.status('pushing to %s\n' % (dest))
@@ -2075,7 +2039,7 @@ def rawcommit(ui, repo, *pats, **opts):
 
     ui.warn(_("(the rawcommit command is deprecated)\n"))
 
-    message = logmessage(opts)
+    message = cmdutil.logmessage(opts)
 
     files, match, anypats = cmdutil.matchpats(repo, pats, opts)
     if opts['files']:
@@ -3039,272 +3003,5 @@ def run():
     except util.Abort, inst:
         sys.stderr.write(_("abort: %s\n") % inst)
         return -1
-    sys.exit(runcatch(u, sys.argv[1:]))
-
-def runcatch(u, 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:
-        return dispatch(u, args)
-    except hg.RepoError, inst:
-        u.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
-        u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
-    except lock.LockUnavailable, inst:
-        u.warn(_("abort: could not lock %s: %s\n") %
-               (inst.desc or inst.filename, inst.strerror))
-    except revlog.RevlogError, inst:
-        u.warn(_("abort: %s!\n") % inst)
-    except util.SignalInterrupt:
-        u.warn(_("killed!\n"))
-    except KeyboardInterrupt:
-        try:
-            u.warn(_("interrupted!\n"))
-        except IOError, inst:
-            if inst.errno == errno.EPIPE:
-                if u.debugflag:
-                    u.warn(_("\nbroken pipe\n"))
-            else:
-                raise
-    except socket.error, inst:
-        u.warn(_("abort: %s\n") % inst[1])
-    except IOError, inst:
-        if hasattr(inst, "code"):
-            u.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
-            u.warn(_("abort: error: %s\n") % reason)
-        elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
-            if u.debugflag:
-                u.warn(_("broken pipe\n"))
-        elif getattr(inst, "strerror", None):
-            if getattr(inst, "filename", None):
-                u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
-            else:
-                u.warn(_("abort: %s\n") % inst.strerror)
-        else:
-            raise
-    except OSError, inst:
-        if getattr(inst, "filename", None):
-            u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
-        else:
-            u.warn(_("abort: %s\n") % inst.strerror)
-    except util.UnexpectedOutput, inst:
-        u.warn(_("abort: %s") % inst[0])
-        if not isinstance(inst[1], basestring):
-            u.warn(" %r\n" % (inst[1],))
-        elif not inst[1]:
-            u.warn(_(" empty string\n"))
-        else:
-            u.warn("\n%r\n" % util.ellipsis(inst[1]))
-    except util.Abort, inst:
-        u.warn(_("abort: %s\n") % inst)
-    except TypeError, inst:
-        # was this an argument error?
-        tb = traceback.extract_tb(sys.exc_info()[2])
-        if len(tb) > 2: # no
-            raise
-        u.debug(inst, "\n")
-        u.warn(_("%s: invalid arguments\n") % cmd)
-        help_(u, cmd)
-    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:
-        u.warn(_("** unknown exception encountered, details follow\n"))
-        u.warn(_("** report bug details to "
-                 "http://www.selenic.com/mercurial/bts\n"))
-        u.warn(_("** or mercurial@selenic.com\n"))
-        u.warn(_("** Mercurial Distributed SCM (version %s)\n")
-               % version.get_version())
-        raise
-
-    return -1
-
-def findpossible(ui, cmd):
-    """
-    Return cmd -> (aliases, command table entry)
-    for each matching command.
-    Return debug commands (or their aliases) only if no normal command matches.
-    """
-    choice = {}
-    debugchoice = {}
-    for e in table.keys():
-        aliases = e.lstrip("^").split("|")
-        found = None
-        if cmd in aliases:
-            found = cmd
-        elif not ui.config("ui", "strict"):
-            for a in aliases:
-                if a.startswith(cmd):
-                    found = a
-                    break
-        if found is not None:
-            if aliases[0].startswith("debug") or found.startswith("debug"):
-                debugchoice[found] = (aliases, table[e])
-            else:
-                choice[found] = (aliases, table[e])
-
-    if not choice and debugchoice:
-        choice = debugchoice
-
-    return choice
-
-def findcmd(ui, cmd):
-    """Return (aliases, command table entry) for command string."""
-    choice = findpossible(ui, cmd)
-
-    if choice.has_key(cmd):
-        return choice[cmd]
-
-    if len(choice) > 1:
-        clist = choice.keys()
-        clist.sort()
-        raise AmbiguousCommand(cmd, clist)
-
-    if choice:
-        return choice.values()[0]
-
-    raise UnknownCommand(cmd)
-
-class ParseError(Exception):
-    """Exception raised on errors in parsing the command line."""
-
-def parse(ui, args):
-    options = {}
-    cmdoptions = {}
-
-    try:
-        args = fancyopts.fancyopts(args, 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 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 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 dispatch(u, args):
-    extensions.loadall(u)
-    u.addreadhook(extensions.loadall)
-
-    try:
-        cmd, func, args, options, cmdoptions = parse(u, args)
-    except ParseError, inst:
-        if inst.args[0]:
-            u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
-            help_(u, inst.args[0])
-        else:
-            u.warn(_("hg: %s\n") % inst.args[1])
-            help_(u, 'shortlist')
-        return -1
-    except AmbiguousCommand, inst:
-        u.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
-                (inst.args[0], " ".join(inst.args[1])))
-        return -1
-    except UnknownCommand, inst:
-        u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
-        help_(u, 'shortlist')
-        return -1
-
-    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()
-            u.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)
-
-    if options['cwd']:
-        os.chdir(options['cwd'])
-
-    u.updateopts(options["verbose"], options["debug"], options["quiet"],
-                 not options["noninteractive"], options["traceback"],
-                 parseconfig(options["config"]))
-
-    path = u.expandpath(options["repository"]) or ""
-    repo = path and hg.repository(u, path=path) or None
-    if repo and not repo.local():
-        raise util.Abort(_("repository '%s' is not local") % path)
-
-    if options['help']:
-        return help_(u, cmd, options['version'])
-    elif options['version']:
-        return version_(u)
-    elif not cmd:
-        return help_(u, 'shortlist')
-
-    if cmd not in norepo.split():
-        try:
-            if not repo:
-                repo = hg.repository(u, path=path)
-            u = repo.ui
-        except hg.RepoError:
-            if cmd not in optionalrepo.split():
-                raise
-        d = lambda: func(u, repo, *args, **cmdoptions)
-    else:
-        d = lambda: func(u, *args, **cmdoptions)
-
-    return cmdutil.runcommand(u, options, d)
-
+    sys.exit(cmdutil.runcatch(u, sys.argv[1:]))
+
--- a/tests/test-ui-config
+++ b/tests/test-ui-config
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
 
 import ConfigParser
-from mercurial import ui, util, commands
+from mercurial import ui, util, cmdutil
 
 testui = ui.ui()
-parsed = commands.parseconfig([
+parsed = cmdutil.parseconfig([
     'values.string=string value',
     'values.bool1=true',
     'values.bool2=false',