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