dispatch.py
413 lines
| 13.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / dispatch.py
|
r5178 | # 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] | ||||
|
r5542 | ui.warn(_("abort: could not import module %s!\n") % m) | ||
|
r5178 | 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) | ||||
|
r5633 | except MemoryError: | ||
ui.warn(_("abort: out of memory\n")) | ||||
|
r5178 | 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 | ||||
|
r5192 | _loaded = {} | ||
|
r5178 | 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) | ||||
|
r5192 | for name, module in extensions.extensions(): | ||
if name in _loaded: | ||||
continue | ||||
|
r5828 | |||
# setup extensions | ||||
# TODO this should be generalized to scheme, where extensions can | ||||
# redepend on other extensions. then we should toposort them, and | ||||
# do initialization in correct order | ||||
extsetup = getattr(module, 'extsetup', None) | ||||
if extsetup: | ||||
extsetup() | ||||
|
r5192 | cmdtable = getattr(module, 'cmdtable', {}) | ||
overrides = [cmd for cmd in cmdtable if cmd in commands.table] | ||||
if overrides: | ||||
ui.warn(_("extension '%s' overrides commands: %s\n") | ||||
% (name, " ".join(overrides))) | ||||
commands.table.update(cmdtable) | ||||
_loaded[name] = 1 | ||||
|
r5178 | # 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 | ||||
|
r5664 | ui.setconfig("bundle", "mainreporoot", repo.root) | ||
|
r5178 | 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 | ||||
|
r5869 | ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs)) | ||
|
r5178 | if ret: | ||
return ret | ||||
ret = _runcommand(ui, options, cmd, d) | ||||
# run post-hook, passing command result | ||||
|
r5869 | hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs), | ||
|
r5178 | 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() | ||||