dispatch.py
504 lines
| 17.2 KiB
| text/x-python
|
PythonLexer
/ mercurial / dispatch.py
|
r5178 | # dispatch.py - command dispatching for mercurial | ||
# | ||||
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r5178 | |||
from i18n import _ | ||||
|
r7388 | import os, sys, atexit, signal, pdb, socket, errno, shlex, time | ||
|
r7873 | import util, commands, hg, fancyopts, extensions, hook, error | ||
|
r7948 | import cmdutil, encoding | ||
|
r5178 | import ui as _ui | ||
def run(): | ||||
"run the command in sys.argv" | ||||
sys.exit(dispatch(sys.argv[1:])) | ||||
def dispatch(args): | ||||
"run the command specified in args" | ||||
try: | ||||
|
r8136 | u = _ui.ui() | ||
if '--traceback' in args: | ||||
u.setconfig('ui', 'traceback', 'on') | ||||
|
r5178 | except util.Abort, inst: | ||
sys.stderr.write(_("abort: %s\n") % inst) | ||||
return -1 | ||||
|
r9470 | except error.ConfigError, inst: | ||
sys.stderr.write(_("hg: %s\n") % inst) | ||||
return -1 | ||||
|
r5178 | return _runcatch(u, args) | ||
def _runcatch(ui, args): | ||||
def catchterm(*args): | ||||
|
r7644 | raise error.SignalInterrupt | ||
|
r5178 | |||
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]) | ||||
|
r8206 | ui.traceback() | ||
|
r5178 | raise | ||
|
r7645 | # Global exception handling, alphabetically | ||
# Mercurial-specific first, followed by built-in and library exceptions | ||||
|
r7643 | except error.AmbiguousCommand, inst: | ||
|
r5178 | ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") % | ||
(inst.args[0], " ".join(inst.args[1]))) | ||||
|
r8144 | except error.ConfigError, inst: | ||
ui.warn(_("hg: %s\n") % inst.args[0]) | ||||
|
r7640 | except error.LockHeld, inst: | ||
|
r5178 | 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)) | ||||
|
r7640 | except error.LockUnavailable, inst: | ||
|
r5178 | ui.warn(_("abort: could not lock %s: %s\n") % | ||
(inst.desc or inst.filename, inst.strerror)) | ||||
|
r7645 | except error.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 error.RepoError, inst: | ||||
ui.warn(_("abort: %s!\n") % inst) | ||||
except error.ResponseError, inst: | ||||
ui.warn(_("abort: %s") % inst.args[0]) | ||||
if not isinstance(inst.args[1], basestring): | ||||
ui.warn(" %r\n" % (inst.args[1],)) | ||||
elif not inst.args[1]: | ||||
ui.warn(_(" empty string\n")) | ||||
else: | ||||
ui.warn("\n%r\n" % util.ellipsis(inst.args[1])) | ||||
|
r7633 | except error.RevlogError, inst: | ||
|
r5178 | ui.warn(_("abort: %s!\n") % inst) | ||
|
r7644 | except error.SignalInterrupt: | ||
|
r5178 | ui.warn(_("killed!\n")) | ||
|
r7645 | except error.UnknownCommand, inst: | ||
ui.warn(_("hg: unknown command '%s'\n") % inst.args[0]) | ||||
commands.help_(ui, 'shortlist') | ||||
except util.Abort, inst: | ||||
ui.warn(_("abort: %s\n") % inst) | ||||
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")) | ||||
|
r5178 | 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) | ||||
|
r7474 | elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE: | ||
|
r5178 | 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) | ||||
|
r7645 | 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 | ||||
|
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 | ||||
|
r7645 | except socket.error, inst: | ||
ui.warn(_("abort: %s\n") % inst.args[-1]) | ||||
|
r5178 | except: | ||
ui.warn(_("** unknown exception encountered, details follow\n")) | ||||
ui.warn(_("** report bug details to " | ||||
|
r8936 | "http://mercurial.selenic.com/bts/\n")) | ||
|
r5178 | ui.warn(_("** or mercurial@selenic.com\n")) | ||
ui.warn(_("** Mercurial Distributed SCM (version %s)\n") | ||||
|
r7632 | % util.version()) | ||
|
r6985 | ui.warn(_("** Extensions loaded: %s\n") | ||
% ", ".join([x[0] for x in extensions.extensions()])) | ||||
|
r5178 | raise | ||
return -1 | ||||
|
r6150 | def _findrepo(p): | ||
|
r5178 | while not os.path.isdir(os.path.join(p, ".hg")): | ||
oldp, p = p, os.path.dirname(p) | ||||
if p == oldp: | ||||
return None | ||||
return p | ||||
|
r8655 | def aliasargs(fn): | ||
if hasattr(fn, 'args'): | ||||
return fn.args | ||||
return [] | ||||
class cmdalias(object): | ||||
def __init__(self, name, definition, cmdtable): | ||||
self.name = name | ||||
self.definition = definition | ||||
self.args = [] | ||||
self.opts = [] | ||||
self.help = '' | ||||
self.norepo = True | ||||
try: | ||||
cmdutil.findcmd(self.name, cmdtable, True) | ||||
self.shadows = True | ||||
except error.UnknownCommand: | ||||
self.shadows = False | ||||
if not self.definition: | ||||
def fn(ui, *args): | ||||
ui.warn(_("no definition for alias '%s'\n") % self.name) | ||||
return 1 | ||||
self.fn = fn | ||||
return | ||||
args = shlex.split(self.definition) | ||||
cmd = args.pop(0) | ||||
opts = [] | ||||
help = '' | ||||
try: | ||||
self.fn, self.opts, self.help = cmdutil.findcmd(cmd, cmdtable, False)[1] | ||||
self.args = aliasargs(self.fn) + args | ||||
if cmd not in commands.norepo.split(' '): | ||||
self.norepo = False | ||||
except error.UnknownCommand: | ||||
def fn(ui, *args): | ||||
ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \ | ||||
% (self.name, cmd)) | ||||
return 1 | ||||
self.fn = fn | ||||
except error.AmbiguousCommand: | ||||
def fn(ui, *args): | ||||
ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \ | ||||
% (self.name, cmd)) | ||||
return 1 | ||||
self.fn = fn | ||||
def __call__(self, ui, *args, **opts): | ||||
if self.shadows: | ||||
ui.debug(_("alias '%s' shadows command\n") % self.name) | ||||
return self.fn(ui, *args, **opts) | ||||
def addaliases(ui, cmdtable): | ||||
# aliases are processed after extensions have been loaded, so they | ||||
# may use extension commands. Aliases can also use other alias definitions, | ||||
# but only if they have been defined prior to the current definition. | ||||
for alias, definition in ui.configitems('alias'): | ||||
aliasdef = cmdalias(alias, definition, cmdtable) | ||||
cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help) | ||||
if aliasdef.norepo: | ||||
commands.norepo += ' %s' % alias | ||||
|
r5178 | def _parse(ui, args): | ||
options = {} | ||||
cmdoptions = {} | ||||
try: | ||||
args = fancyopts.fancyopts(args, commands.globalopts, options) | ||||
except fancyopts.getopt.GetoptError, inst: | ||||
|
r7636 | raise error.ParseError(None, inst) | ||
|
r5178 | |||
if args: | ||||
cmd, args = args[0], args[1:] | ||||
|
r7213 | aliases, i = cmdutil.findcmd(cmd, commands.table, | ||
|
r7224 | ui.config("ui", "strict")) | ||
|
r5178 | cmd = aliases[0] | ||
|
r8655 | args = aliasargs(i[0]) + args | ||
|
r5178 | 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: | ||||
|
r7772 | args = fancyopts.fancyopts(args, c, cmdoptions, True) | ||
|
r5178 | except fancyopts.getopt.GetoptError, inst: | ||
|
r7636 | raise error.ParseError(cmd, inst) | ||
|
r5178 | |||
# 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) | ||||
|
r8137 | def _parseconfig(ui, config): | ||
|
r5178 | """parse the --config options from the command line""" | ||
for cfg in config: | ||||
try: | ||||
name, value = cfg.split('=', 1) | ||||
section, name = name.split('.', 1) | ||||
if not section or not name: | ||||
raise IndexError | ||||
|
r8137 | ui.setconfig(section, name, value) | ||
|
r5178 | except (IndexError, ValueError): | ||
raise util.Abort(_('malformed --config option: %s') % cfg) | ||||
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 | ||||
|
r7819 | def runcommand(lui, repo, cmd, fullargs, ui, options, d): | ||
# run pre-hook, and abort if it fails | ||||
ret = hook.hook(lui, 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(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs), | ||||
result = ret) | ||||
return ret | ||||
|
r8304 | _loaded = set() | ||
|
r5178 | def _dispatch(ui, args): | ||
# read --config before doing anything else | ||||
# (e.g. to change trust settings for reading .hg/hgrc) | ||||
|
r8137 | _parseconfig(ui, _earlygetopt(['--config'], args)) | ||
|
r5178 | |||
# check for cwd | ||||
cwd = _earlygetopt(['--cwd'], args) | ||||
if cwd: | ||||
os.chdir(cwd[-1]) | ||||
# read the local repository .hgrc into a local ui object | ||||
|
r6150 | path = _findrepo(os.getcwd()) or "" | ||
|
r5178 | if not path: | ||
lui = ui | ||||
if path: | ||||
try: | ||||
|
r8190 | lui = ui.copy() | ||
|
r5178 | 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]) | ||||
|
r8190 | lui = ui.copy() | ||
|
r5178 | 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) | ||||
|
r8304 | _loaded.add(name) | ||
|
r8655 | |||
addaliases(lui, commands.table) | ||||
|
r5178 | # check for fallback encoding | ||
fallback = lui.config('ui', 'fallbackencoding') | ||||
if fallback: | ||||
|
r7948 | encoding.fallbackencoding = fallback | ||
|
r5178 | |||
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(_( | ||||
|
r8911 | "Option -R has to be separated from other options (e.g. not -qR) " | ||
|
r5178 | "and --repository may only be abbreviated as --repo!")) | ||
if options["encoding"]: | ||||
|
r7948 | encoding.encoding = options["encoding"] | ||
|
r5178 | if options["encodingmode"]: | ||
|
r7948 | encoding.encodingmode = options["encodingmode"] | ||
|
r5178 | 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) | ||||
|
r8136 | if options['verbose'] or options['debug'] or options['quiet']: | ||
ui.setconfig('ui', 'verbose', str(bool(options['verbose']))) | ||||
ui.setconfig('ui', 'debug', str(bool(options['debug']))) | ||||
ui.setconfig('ui', 'quiet', str(bool(options['quiet']))) | ||||
if options['traceback']: | ||||
ui.setconfig('ui', 'traceback', 'on') | ||||
if options['noninteractive']: | ||||
ui.setconfig('ui', 'interactive', 'off') | ||||
|
r5178 | |||
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) | ||||
|
r6105 | ui.setconfig("bundle", "mainreporoot", repo.root) | ||
|
r7637 | except error.RepoError: | ||
|
r5178 | if cmd not in commands.optionalrepo.split(): | ||
|
r6150 | if args and not path: # try to infer -R from command args | ||
repos = map(_findrepo, args) | ||||
guess = repos[0] | ||||
if guess and repos.count(guess) == len(repos): | ||||
return _dispatch(ui, ['--repository', guess] + fullargs) | ||||
|
r5178 | if not path: | ||
|
r7637 | raise error.RepoError(_("There is no Mercurial repository" | ||
" here (.hg not found)")) | ||||
|
r5178 | raise | ||
|
r7388 | args.insert(0, repo) | ||
|
r7733 | elif rpath: | ||
ui.warn("warning: --repository ignored\n") | ||||
|
r7388 | |||
d = lambda: util.checksignature(func)(ui, *args, **cmdoptions) | ||||
|
r7819 | return runcommand(lui, repo, cmd, fullargs, ui, options, d) | ||
|
r5178 | |||
def _runcommand(ui, options, cmd, cmdfunc): | ||||
def checkargs(): | ||||
try: | ||||
return cmdfunc() | ||||
|
r7646 | except error.SignatureError: | ||
|
r7636 | raise error.ParseError(cmd, _("invalid arguments")) | ||
|
r5178 | |||
|
r6141 | if options['profile']: | ||
|
r8023 | format = ui.config('profiling', 'format', default='text') | ||
|
r8024 | if not format in ['text', 'kcachegrind']: | ||
|
r8023 | ui.warn(_("unrecognized profiling format '%s'" | ||
" - Ignored\n") % format) | ||||
format = 'text' | ||||
|
r8022 | output = ui.config('profiling', 'output') | ||
if output: | ||||
path = os.path.expanduser(output) | ||||
path = ui.expandpath(path) | ||||
ostream = open(path, 'wb') | ||||
else: | ||||
ostream = sys.stderr | ||||
|
r5178 | 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: | ||||
|
r6141 | return checkargs() | ||
|
r5178 | finally: | ||
p.disable() | ||||
|
r8024 | |||
if format == 'kcachegrind': | ||||
import lsprofcalltree | ||||
calltree = lsprofcalltree.KCacheGrind(p) | ||||
calltree.output(ostream) | ||||
else: | ||||
# format == 'text' | ||||
stats = lsprof.Stats(p.getstats()) | ||||
stats.sort() | ||||
stats.pprint(top=10, file=ostream, climit=5) | ||||
|
r8022 | |||
if output: | ||||
ostream.close() | ||||
|
r5178 | else: | ||
|
r6141 | return checkargs() | ||