501 lines
17 KiB
Python
501 lines
17 KiB
Python
# 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 version 2, incorporated herein by reference.
|
|
|
|
from i18n import _
|
|
import os, sys, atexit, signal, pdb, socket, errno, shlex, time
|
|
import util, commands, hg, fancyopts, extensions, hook, error
|
|
import cmdutil, encoding
|
|
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:
|
|
u = _ui.ui()
|
|
if '--traceback' in args:
|
|
u.setconfig('ui', 'traceback', 'on')
|
|
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 error.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.traceback()
|
|
raise
|
|
|
|
# Global exception handling, alphabetically
|
|
# Mercurial-specific first, followed by built-in and library exceptions
|
|
except error.AmbiguousCommand, inst:
|
|
ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
|
|
(inst.args[0], " ".join(inst.args[1])))
|
|
except error.ConfigError, inst:
|
|
ui.warn(_("hg: %s\n") % inst.args[0])
|
|
except error.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 error.LockUnavailable, inst:
|
|
ui.warn(_("abort: could not lock %s: %s\n") %
|
|
(inst.desc or inst.filename, inst.strerror))
|
|
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]))
|
|
except error.RevlogError, inst:
|
|
ui.warn(_("abort: %s!\n") % inst)
|
|
except error.SignalInterrupt:
|
|
ui.warn(_("killed!\n"))
|
|
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"))
|
|
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.args[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 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 MemoryError:
|
|
ui.warn(_("abort: out of memory\n"))
|
|
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 socket.error, inst:
|
|
ui.warn(_("abort: %s\n") % inst.args[-1])
|
|
except:
|
|
ui.warn(_("** unknown exception encountered, details follow\n"))
|
|
ui.warn(_("** report bug details to "
|
|
"http://mercurial.selenic.com/bts/\n"))
|
|
ui.warn(_("** or mercurial@selenic.com\n"))
|
|
ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
|
|
% util.version())
|
|
ui.warn(_("** Extensions loaded: %s\n")
|
|
% ", ".join([x[0] for x in extensions.extensions()]))
|
|
raise
|
|
|
|
return -1
|
|
|
|
def _findrepo(p):
|
|
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 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
|
|
|
|
def _parse(ui, args):
|
|
options = {}
|
|
cmdoptions = {}
|
|
|
|
try:
|
|
args = fancyopts.fancyopts(args, commands.globalopts, options)
|
|
except fancyopts.getopt.GetoptError, inst:
|
|
raise error.ParseError(None, inst)
|
|
|
|
if args:
|
|
cmd, args = args[0], args[1:]
|
|
aliases, i = cmdutil.findcmd(cmd, commands.table,
|
|
ui.config("ui", "strict"))
|
|
cmd = aliases[0]
|
|
args = aliasargs(i[0]) + args
|
|
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, True)
|
|
except fancyopts.getopt.GetoptError, inst:
|
|
raise error.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(ui, config):
|
|
"""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
|
|
ui.setconfig(section, name, value)
|
|
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
|
|
|
|
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
|
|
|
|
_loaded = set()
|
|
def _dispatch(ui, args):
|
|
# read --config before doing anything else
|
|
# (e.g. to change trust settings for reading .hg/hgrc)
|
|
_parseconfig(ui, _earlygetopt(['--config'], args))
|
|
|
|
# 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(os.getcwd()) or ""
|
|
if not path:
|
|
lui = ui
|
|
if path:
|
|
try:
|
|
lui = ui.copy()
|
|
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.copy()
|
|
lui.readconfig(os.path.join(path, ".hg", "hgrc"))
|
|
|
|
extensions.loadall(lui)
|
|
for name, module in extensions.extensions():
|
|
if name in _loaded:
|
|
continue
|
|
|
|
# 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()
|
|
|
|
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.add(name)
|
|
|
|
addaliases(lui, commands.table)
|
|
|
|
# check for fallback encoding
|
|
fallback = lui.config('ui', 'fallbackencoding')
|
|
if fallback:
|
|
encoding.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 (e.g. not -qR) "
|
|
"and --repository may only be abbreviated as --repo!"))
|
|
|
|
if options["encoding"]:
|
|
encoding.encoding = options["encoding"]
|
|
if options["encodingmode"]:
|
|
encoding.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)
|
|
|
|
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')
|
|
|
|
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)
|
|
ui.setconfig("bundle", "mainreporoot", repo.root)
|
|
except error.RepoError:
|
|
if cmd not in commands.optionalrepo.split():
|
|
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)
|
|
if not path:
|
|
raise error.RepoError(_("There is no Mercurial repository"
|
|
" here (.hg not found)"))
|
|
raise
|
|
args.insert(0, repo)
|
|
elif rpath:
|
|
ui.warn("warning: --repository ignored\n")
|
|
|
|
d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
|
|
return runcommand(lui, repo, cmd, fullargs, ui, options, d)
|
|
|
|
def _runcommand(ui, options, cmd, cmdfunc):
|
|
def checkargs():
|
|
try:
|
|
return cmdfunc()
|
|
except error.SignatureError:
|
|
raise error.ParseError(cmd, _("invalid arguments"))
|
|
|
|
if options['profile']:
|
|
format = ui.config('profiling', 'format', default='text')
|
|
|
|
if not format in ['text', 'kcachegrind']:
|
|
ui.warn(_("unrecognized profiling format '%s'"
|
|
" - Ignored\n") % format)
|
|
format = 'text'
|
|
|
|
output = ui.config('profiling', 'output')
|
|
|
|
if output:
|
|
path = os.path.expanduser(output)
|
|
path = ui.expandpath(path)
|
|
ostream = open(path, 'wb')
|
|
else:
|
|
ostream = sys.stderr
|
|
|
|
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()
|
|
|
|
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)
|
|
|
|
if output:
|
|
ostream.close()
|
|
else:
|
|
return checkargs()
|