mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
31c8ecbdf4
Summary: Change the crash header to just a single line without blaming extensions or Python. This makes the crash log shorter, and easier to read when there are crashes in run-tests.py diffs. Remove `ui.supportcontact` since it's no longer used. Reviewed By: sfilipco Differential Revision: D14393982 fbshipit-source-id: 3ede8d3d3d8fd5d125944a9a750350d3ce356b14
1450 lines
45 KiB
Python
1450 lines
45 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 or any later version.
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import difflib
|
|
import errno
|
|
import getopt
|
|
import os
|
|
import pdb
|
|
import re
|
|
import signal
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
from . import (
|
|
cmdutil,
|
|
color,
|
|
commands,
|
|
demandimport,
|
|
encoding,
|
|
error,
|
|
extensions,
|
|
fancyopts,
|
|
help,
|
|
hg,
|
|
hintutil,
|
|
hook,
|
|
i18n,
|
|
perftrace,
|
|
profiling,
|
|
pycompat,
|
|
registrar,
|
|
scmutil,
|
|
ui as uimod,
|
|
util,
|
|
)
|
|
from .i18n import _
|
|
|
|
|
|
unrecoverablewrite = registrar.command.unrecoverablewrite
|
|
|
|
|
|
class request(object):
|
|
def __init__(
|
|
self,
|
|
args,
|
|
ui=None,
|
|
repo=None,
|
|
fin=None,
|
|
fout=None,
|
|
ferr=None,
|
|
prereposetups=None,
|
|
):
|
|
self.args = args
|
|
self.ui = ui
|
|
self.repo = repo
|
|
|
|
# input/output/error streams
|
|
self.fin = fin
|
|
self.fout = fout
|
|
self.ferr = ferr
|
|
|
|
# remember options pre-parsed by _earlyparseopts()
|
|
self.earlyoptions = {}
|
|
|
|
# reposetups which run before extensions, useful for chg to pre-fill
|
|
# low-level repo state (for example, changelog) before extensions.
|
|
self.prereposetups = prereposetups or []
|
|
|
|
def _runexithandlers(self):
|
|
# Silence potential EPIPE or SIGPIPE errors when writing to stdout or
|
|
# stderr.
|
|
if util.safehasattr(signal, "SIGPIPE"):
|
|
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
|
|
|
class ignoreerrorui(self.ui.__class__):
|
|
def _write(self, *args, **kwargs):
|
|
try:
|
|
return super(ignoreerrorui, self)._write(*args, **kwargs)
|
|
except (OSError, IOError):
|
|
pass
|
|
|
|
def _write_err(self, *args, **kwargs):
|
|
try:
|
|
return super(ignoreerrorui, self)._write_err(*args, **kwargs)
|
|
except (OSError, IOError):
|
|
pass
|
|
|
|
exc = None
|
|
self.ui.__class__ = ignoreerrorui
|
|
|
|
handlers = self.ui._exithandlers
|
|
try:
|
|
while handlers:
|
|
func, args, kwargs = handlers.pop()
|
|
try:
|
|
func(*args, **kwargs)
|
|
except: # re-raises below
|
|
if exc is None:
|
|
exc = sys.exc_info()[1]
|
|
self.ui.warn(("error in exit handlers:\n"))
|
|
self.ui.traceback(force=True)
|
|
finally:
|
|
if exc is not None:
|
|
raise exc
|
|
|
|
|
|
def run():
|
|
"run the command in sys.argv"
|
|
_initstdio()
|
|
req = request(pycompat.sysargv[1:])
|
|
err = None
|
|
try:
|
|
status = (dispatch(req) or 0) & 255
|
|
except error.StdioError as e:
|
|
err = e
|
|
status = -1
|
|
if util.safehasattr(req.ui, "fout"):
|
|
try:
|
|
req.ui.fout.flush()
|
|
except IOError as e:
|
|
err = e
|
|
status = -1
|
|
if util.safehasattr(req.ui, "ferr"):
|
|
if err is not None and err.errno != errno.EPIPE:
|
|
req.ui.ferr.write("abort: %s\n" % encoding.strtolocal(err.strerror))
|
|
req.ui.ferr.flush()
|
|
sys.exit(status & 255)
|
|
|
|
|
|
def _preimportmodules():
|
|
"""pre-import modules that are side-effect free (used by chg server)"""
|
|
coremods = [
|
|
"ancestor",
|
|
"archival",
|
|
"bookmarks",
|
|
"branchmap",
|
|
"bundle2",
|
|
"bundlerepo",
|
|
"byterange",
|
|
"changegroup",
|
|
"changelog",
|
|
"color",
|
|
"config",
|
|
"configitems",
|
|
"connectionpool",
|
|
"context",
|
|
"copies",
|
|
"crecord",
|
|
"dagop",
|
|
"dagparser",
|
|
"dagutil",
|
|
"destutil",
|
|
"dirstate",
|
|
"dirstateguard",
|
|
"discovery",
|
|
"exchange",
|
|
"filelog",
|
|
"filemerge",
|
|
"fileset",
|
|
"formatter",
|
|
"graphmod",
|
|
"hbisect",
|
|
"httpclient",
|
|
"httpconnection",
|
|
"httppeer",
|
|
"localrepo",
|
|
"lock",
|
|
"mail",
|
|
"manifest",
|
|
"match",
|
|
"mdiff",
|
|
"merge",
|
|
"mergeutil",
|
|
"minirst",
|
|
"namespaces",
|
|
"node",
|
|
"obsolete",
|
|
"obsutil",
|
|
"parser",
|
|
"patch",
|
|
"pathutil",
|
|
"peer",
|
|
"phases",
|
|
"policy",
|
|
"progress",
|
|
"pushkey",
|
|
"rcutil",
|
|
"repository",
|
|
"repoview",
|
|
"revlog",
|
|
"revset",
|
|
"revsetlang",
|
|
"rewriteutil",
|
|
"rust",
|
|
"scmposix",
|
|
"scmutil",
|
|
"server",
|
|
"setdiscovery",
|
|
"similar",
|
|
"simplemerge",
|
|
"smartset",
|
|
"sshpeer",
|
|
"sshserver",
|
|
"sslutil",
|
|
"store",
|
|
"streamclone",
|
|
"tags",
|
|
"templatefilters",
|
|
"templatekw",
|
|
"templater",
|
|
"transaction",
|
|
"treediscovery",
|
|
"txnutil",
|
|
"url",
|
|
"urllibcompat",
|
|
"vfs",
|
|
"wireproto",
|
|
"worker",
|
|
"__version__",
|
|
]
|
|
extmods = [
|
|
"absorb",
|
|
"amend",
|
|
"arcdiff",
|
|
"automv",
|
|
"blackbox",
|
|
"checkmessagehook",
|
|
"chistedit",
|
|
"clienttelemetry",
|
|
"clindex",
|
|
"conflictinfo",
|
|
"convert",
|
|
"copytrace",
|
|
"crdump",
|
|
"debugcommitmessage",
|
|
"debugshell",
|
|
"dialect",
|
|
"directaccess",
|
|
"dirsync",
|
|
"extlib",
|
|
"extorder",
|
|
"extutil",
|
|
"fastannotate",
|
|
"fastlog",
|
|
"fastmanifest",
|
|
"fbconduit",
|
|
"fbhistedit",
|
|
"fixcorrupt",
|
|
"fsmonitor",
|
|
"githelp",
|
|
"gitlookup",
|
|
"grpcheck",
|
|
"hgevents",
|
|
"hgsubversion",
|
|
"hiddenerror",
|
|
"histedit",
|
|
"infinitepush",
|
|
"journal",
|
|
"lfs",
|
|
"logginghelper",
|
|
"lz4revlog",
|
|
"mergedriver",
|
|
"morecolors",
|
|
"morestatus",
|
|
"patchrmdir",
|
|
"phabdiff",
|
|
"phabstatus",
|
|
"phrevset",
|
|
"progressfile",
|
|
"pullcreatemarkers",
|
|
"purge",
|
|
"pushrebase",
|
|
"rage",
|
|
"rebase",
|
|
"remotefilelog",
|
|
"remotenames",
|
|
"reset",
|
|
"sampling",
|
|
"schemes",
|
|
"share",
|
|
"shelve",
|
|
"sigtrace",
|
|
"simplecache",
|
|
"smartlog",
|
|
"sparse",
|
|
"sshaskpass",
|
|
"stat",
|
|
"traceprof",
|
|
"treemanifest",
|
|
"tweakdefaults",
|
|
"undo",
|
|
]
|
|
for name in coremods:
|
|
__import__("edenscm.mercurial.%s" % name)
|
|
for extname in extmods:
|
|
extensions.preimport(extname)
|
|
|
|
|
|
def runchgserver():
|
|
"""start the chg server, pre-import bundled extensions"""
|
|
# Clean server - do not load any config files or repos.
|
|
_initstdio()
|
|
ui = uimod.ui()
|
|
repo = None
|
|
args = sys.argv[1:]
|
|
cmd, func, args, globalopts, cmdopts = _parse(ui, args)
|
|
if not (cmd == "serve" and cmdopts["cmdserver"] == "chgunix2"):
|
|
raise error.ProgrammingError("runchgserver called without chg command")
|
|
from . import chgserver, server
|
|
|
|
_preimportmodules()
|
|
service = chgserver.chgunixservice(ui, repo, cmdopts)
|
|
server.runservice(cmdopts, initfn=service.init, runfn=service.run)
|
|
|
|
|
|
def _initstdio():
|
|
for fp in (sys.stdin, sys.stdout, sys.stderr):
|
|
util.setbinary(fp)
|
|
|
|
|
|
def _getsimilar(symbols, value):
|
|
sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
|
|
# The cutoff for similarity here is pretty arbitrary. It should
|
|
# probably be investigated and tweaked.
|
|
return [s for s in symbols if sim(s) > 0.6]
|
|
|
|
|
|
def _reportsimilar(write, similar):
|
|
if len(similar) == 1:
|
|
write(_("(did you mean %s?)\n") % similar[0])
|
|
elif similar:
|
|
ss = ", ".join(sorted(similar))
|
|
write(_("(did you mean one of %s?)\n") % ss)
|
|
|
|
|
|
def _formatparse(write, inst):
|
|
similar = []
|
|
if isinstance(inst, error.UnknownIdentifier):
|
|
# make sure to check fileset first, as revset can invoke fileset
|
|
similar = _getsimilar(inst.symbols, inst.function)
|
|
if len(inst.args) > 1:
|
|
write(_("hg: parse error at %s: %s\n") % (inst.args[1], inst.args[0]))
|
|
if inst.args[0][0] == " ":
|
|
write(_("unexpected leading whitespace\n"))
|
|
else:
|
|
write(_("hg: parse error: %s\n") % inst.args[0])
|
|
_reportsimilar(write, similar)
|
|
if inst.hint:
|
|
write(_("(%s)\n") % inst.hint)
|
|
|
|
|
|
def _formatargs(args):
|
|
return " ".join(util.shellquote(a) for a in args)
|
|
|
|
|
|
def dispatch(req):
|
|
"run the command specified in req.args"
|
|
if req.ferr:
|
|
ferr = req.ferr
|
|
elif req.ui:
|
|
ferr = req.ui.ferr
|
|
else:
|
|
ferr = util.stderr
|
|
|
|
try:
|
|
if not req.ui:
|
|
req.ui = uimod.ui.load()
|
|
req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
|
|
if req.earlyoptions["traceback"]:
|
|
req.ui.setconfig("ui", "traceback", "on", "--traceback")
|
|
|
|
# set ui streams from the request
|
|
if req.fin:
|
|
req.ui.fin = req.fin
|
|
if req.fout:
|
|
req.ui.fout = req.fout
|
|
if req.ferr:
|
|
req.ui.ferr = req.ferr
|
|
except error.Abort as inst:
|
|
ferr.write(_("abort: %s\n") % inst)
|
|
if inst.hint:
|
|
ferr.write(_("(%s)\n") % inst.hint)
|
|
return -1
|
|
except error.ParseError as inst:
|
|
_formatparse(ferr.write, inst)
|
|
return -1
|
|
|
|
msg = _formatargs(req.args)
|
|
starttime = util.timer()
|
|
ret = None
|
|
retmask = 255
|
|
|
|
def logatexit():
|
|
ui = req.ui
|
|
if ui.logmeasuredtimes:
|
|
ui.log(
|
|
"measuredtimes",
|
|
"measured times",
|
|
**pycompat.strkwargs(ui._measuredtimes)
|
|
)
|
|
if ui.metrics.stats:
|
|
# Re-arrange metrics so "a_b_c", "a_b_d", "a_c" becomes
|
|
# {'a': {'b': {'c': ..., 'd': ...}, 'c': ...}
|
|
metrics = {}
|
|
for key, value in ui.metrics.stats.items():
|
|
cur = metrics
|
|
names = key.split("_")
|
|
for name in names[:-1]:
|
|
cur = cur.setdefault(name, {})
|
|
cur[names[-1]] = value
|
|
# pprint.pformat stablizes the output
|
|
from pprint import pformat
|
|
|
|
# developer config: devel.print-metrics
|
|
if ui.configbool("devel", "print-metrics"):
|
|
# Print it out.
|
|
msg = "%s\n" % pformat({"metrics": metrics}).replace("'", " ")
|
|
ui.flush()
|
|
ui.write_err(msg, label="ui.metrics")
|
|
# Write to blackbox, and sampling
|
|
ui.log(
|
|
"metrics", pformat({"metrics": metrics}, width=1024), **ui.metrics.stats
|
|
)
|
|
|
|
# by registering this exit handler here, we guarantee that it runs
|
|
# after other exithandlers, like the killpager one
|
|
req.ui.atexit(logatexit)
|
|
|
|
try:
|
|
ret = _runcatch(req)
|
|
except error.ProgrammingError as inst:
|
|
req.ui.warn(_("** ProgrammingError: %s\n") % inst)
|
|
if inst.hint:
|
|
req.ui.warn(_("** (%s)\n") % inst.hint)
|
|
raise
|
|
except KeyboardInterrupt as inst:
|
|
try:
|
|
if isinstance(inst, error.SignalInterrupt):
|
|
msg = _("killed!\n")
|
|
else:
|
|
msg = _("interrupted!\n")
|
|
req.ui.warn(msg)
|
|
except error.SignalInterrupt:
|
|
# maybe pager would quit without consuming all the output, and
|
|
# SIGPIPE was raised. we cannot print anything in this case.
|
|
pass
|
|
except IOError as inst:
|
|
if inst.errno != errno.EPIPE:
|
|
raise
|
|
ret = -1
|
|
except IOError as inst:
|
|
# Windows does not have SIGPIPE, so pager exit does not
|
|
# get raised as a SignalInterrupt. Let's handle the error
|
|
# explicitly here
|
|
if not pycompat.iswindows or inst.errno != errno.EINVAL:
|
|
raise
|
|
ret = -1
|
|
finally:
|
|
duration = util.timer() - starttime
|
|
req.ui.flush()
|
|
req.ui.log(
|
|
"commandfinish",
|
|
"%s exited %d after %0.2f seconds\n",
|
|
msg,
|
|
ret or 0,
|
|
duration,
|
|
)
|
|
|
|
traces = perftrace.traces()
|
|
if traces:
|
|
threshold = req.ui.configint("tracing", "threshold")
|
|
for trace in traces:
|
|
if trace.duration() > threshold:
|
|
output = perftrace.asciirender(trace)
|
|
if req.ui.configbool("tracing", "stderr"):
|
|
req.ui.warn("%s\n" % output)
|
|
req.ui.log("perftrace", "Trace:\n%s\n" % output)
|
|
|
|
req.ui._measuredtimes["command_duration"] = duration * 1000
|
|
retmask = req.ui.configint("ui", "exitcodemask")
|
|
|
|
try:
|
|
req._runexithandlers()
|
|
except: # exiting, so no re-raises
|
|
ret = ret or -1
|
|
if ret is None:
|
|
ret = 0
|
|
return ret & retmask
|
|
|
|
|
|
def _runcatch(req):
|
|
def catchterm(*args):
|
|
raise error.SignalInterrupt
|
|
|
|
ui = req.ui
|
|
try:
|
|
for name in "SIGBREAK", "SIGHUP", "SIGTERM":
|
|
num = getattr(signal, name, None)
|
|
if num:
|
|
signal.signal(num, catchterm)
|
|
except ValueError:
|
|
pass # happens if called in a thread
|
|
|
|
def _runcatchfunc():
|
|
realcmd = None
|
|
try:
|
|
cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
|
|
cmd = cmdargs[0]
|
|
aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
|
|
realcmd = aliases[0]
|
|
except (
|
|
error.UnknownCommand,
|
|
error.AmbiguousCommand,
|
|
IndexError,
|
|
getopt.GetoptError,
|
|
):
|
|
# Don't handle this here. We know the command is
|
|
# invalid, but all we're worried about for now is that
|
|
# it's not a command that server operators expect to
|
|
# be safe to offer to users in a sandbox.
|
|
pass
|
|
|
|
if realcmd == "serve" and "--read-only" in req.args:
|
|
req.args.remove("--read-only")
|
|
|
|
if not req.ui:
|
|
req.ui = uimod.ui.load()
|
|
req.ui.setconfig(
|
|
"hooks", "pretxnopen.readonlyrejectpush", rejectpush, "dispatch"
|
|
)
|
|
req.ui.setconfig(
|
|
"hooks", "prepushkey.readonlyrejectpush", rejectpush, "dispatch"
|
|
)
|
|
|
|
if realcmd == "serve" and "--stdio" in cmdargs:
|
|
# We want to constrain 'hg serve --stdio' instances pretty
|
|
# closely, as many shared-ssh access tools want to grant
|
|
# access to run *only* 'hg -R $repo serve --stdio'. We
|
|
# restrict to exactly that set of arguments, and prohibit
|
|
# any repo name that starts with '--' to prevent
|
|
# shenanigans wherein a user does something like pass
|
|
# --debugger or --config=ui.debugger=1 as a repo
|
|
# name. This used to actually run the debugger.
|
|
if (
|
|
len(req.args) != 4
|
|
or req.args[0] != "-R"
|
|
or req.args[1].startswith("--")
|
|
or req.args[2] != "serve"
|
|
or req.args[3] != "--stdio"
|
|
):
|
|
raise error.Abort(
|
|
_("potentially unsafe serve --stdio invocation: %r") % (req.args,)
|
|
)
|
|
|
|
try:
|
|
debugger = "pdb"
|
|
debugtrace = {"pdb": pdb.set_trace}
|
|
debugmortem = {"pdb": pdb.post_mortem}
|
|
|
|
# --config takes prescendence over --configfile, so process
|
|
# --configfile first --config second.
|
|
for configfile in req.earlyoptions["configfile"]:
|
|
req.ui.readconfig(configfile)
|
|
|
|
# read --config before doing anything else
|
|
# (e.g. to change trust settings for reading .hg/hgrc)
|
|
cfgs = _parseconfig(req.ui, req.earlyoptions["config"])
|
|
|
|
if req.repo:
|
|
for configfile in req.earlyoptions["configfile"]:
|
|
req.repo.ui.readconfig(configfile)
|
|
# copy configs that were passed on the cmdline (--config) to
|
|
# the repo ui
|
|
for sec, name, val in cfgs:
|
|
req.repo.ui.setconfig(sec, name, val, source="--config")
|
|
|
|
# developer config: ui.debugger
|
|
debugger = ui.config("ui", "debugger")
|
|
debugmod = pdb
|
|
if not debugger or ui.plain():
|
|
# if we are in HGPLAIN mode, then disable custom debugging
|
|
debugger = "pdb"
|
|
elif req.earlyoptions["debugger"]:
|
|
# This import can be slow for fancy debuggers, so only
|
|
# do it when absolutely necessary, i.e. when actual
|
|
# debugging has been requested
|
|
with demandimport.deactivated():
|
|
try:
|
|
debugmod = __import__(debugger)
|
|
except ImportError:
|
|
pass # Leave debugmod = pdb
|
|
|
|
debugtrace[debugger] = debugmod.set_trace
|
|
debugmortem[debugger] = debugmod.post_mortem
|
|
|
|
# enter the debugger before command execution
|
|
if req.earlyoptions["debugger"]:
|
|
ui.warn(
|
|
_(
|
|
"entering debugger - "
|
|
"type c to continue starting hg or h for help\n"
|
|
)
|
|
)
|
|
|
|
if debugger != "pdb" and debugtrace[debugger] == debugtrace["pdb"]:
|
|
ui.warn(
|
|
_("%s debugger specified " "but its module was not found\n")
|
|
% debugger
|
|
)
|
|
with demandimport.deactivated():
|
|
debugtrace[debugger]()
|
|
try:
|
|
return _dispatch(req)
|
|
finally:
|
|
ui.flush()
|
|
except: # re-raises
|
|
# enter the debugger when we hit an exception
|
|
if req.earlyoptions["debugger"]:
|
|
traceback.print_exc()
|
|
debugmortem[debugger](sys.exc_info()[2])
|
|
raise
|
|
|
|
return _callcatch(ui, _runcatchfunc)
|
|
|
|
|
|
def _callcatch(ui, func):
|
|
"""like scmutil.callcatch but handles more high-level exceptions about
|
|
config parsing and commands. besides, use handlecommandexception to handle
|
|
uncaught exceptions.
|
|
"""
|
|
try:
|
|
return scmutil.callcatch(ui, func)
|
|
except error.AmbiguousCommand as inst:
|
|
ui.warn(
|
|
_("hg: command '%s' is ambiguous:\n %s\n")
|
|
% (inst.args[0], " ".join(inst.args[1]))
|
|
)
|
|
except error.CommandError as inst:
|
|
if inst.args[0]:
|
|
msgbytes = pycompat.bytestr(inst.args[1])
|
|
ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
|
|
ui.warn(_("(use 'hg %s -h' to get help)\n") % (inst.args[0],))
|
|
else:
|
|
ui.warn(_("hg: %s\n") % inst.args[1])
|
|
ui.warn(_("(use 'hg -h' to get help)\n"))
|
|
except error.ParseError as inst:
|
|
_formatparse(ui.warn, inst)
|
|
return -1
|
|
except error.UnknownCommand as inst:
|
|
nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
|
|
try:
|
|
# check if the command is in a disabled extension
|
|
# (but don't check for extensions themselves)
|
|
formatted = help.formattedhelp(ui, commands, inst.args[0], unknowncmd=True)
|
|
ui.warn(nocmdmsg)
|
|
ui.write(formatted)
|
|
except (error.UnknownCommand, error.Abort):
|
|
suggested = False
|
|
if len(inst.args) == 2:
|
|
sim = _getsimilar(inst.args[1], inst.args[0])
|
|
if sim:
|
|
ui.warn(nocmdmsg)
|
|
_reportsimilar(ui.warn, sim)
|
|
suggested = True
|
|
if not suggested:
|
|
ui.pager("help")
|
|
ui.warn(nocmdmsg)
|
|
commands.help_(ui)
|
|
except error.UnknownSubcommand as inst:
|
|
cmd, subcmd, allsubcmds = inst.args
|
|
suggested = False
|
|
if subcmd is not None:
|
|
nosubcmdmsg = _("hg %s: unknown subcommand '%s'\n") % (cmd, subcmd)
|
|
sim = _getsimilar(allsubcmds, subcmd)
|
|
if sim:
|
|
ui.warn(nosubcmdmsg)
|
|
_reportsimilar(ui.warn, sim)
|
|
suggested = True
|
|
else:
|
|
nosubcmdmsg = _("hg %s: subcommand required\n") % cmd
|
|
if not suggested:
|
|
ui.pager("help")
|
|
ui.warn(nosubcmdmsg)
|
|
commands.help_(ui, cmd)
|
|
except IOError:
|
|
raise
|
|
except KeyboardInterrupt:
|
|
raise
|
|
except: # probably re-raises
|
|
if not handlecommandexception(ui):
|
|
raise
|
|
|
|
return -1
|
|
|
|
|
|
def aliasargs(fn, givenargs):
|
|
args = []
|
|
# only care about alias 'args', ignore 'args' set by extensions.wrapfunction
|
|
if not util.safehasattr(fn, "_origfunc"):
|
|
args = getattr(fn, "args", args)
|
|
if args:
|
|
cmd = " ".join(map(util.shellquote, args))
|
|
|
|
nums = []
|
|
|
|
def replacer(m):
|
|
num = int(m.group(1)) - 1
|
|
nums.append(num)
|
|
if num < len(givenargs):
|
|
return givenargs[num]
|
|
raise error.Abort(_("too few arguments for command alias"))
|
|
|
|
cmd = re.sub(br"\$(\d+|\$)", replacer, cmd)
|
|
givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
|
|
args = pycompat.shlexsplit(cmd)
|
|
return args + givenargs
|
|
|
|
|
|
def aliasinterpolate(name, args, cmd):
|
|
"""interpolate args into cmd for shell aliases
|
|
|
|
This also handles $0, $@ and "$@".
|
|
"""
|
|
# util.interpolate can't deal with "$@" (with quotes) because it's only
|
|
# built to match prefix + patterns.
|
|
replacemap = dict(("$%d" % (i + 1), arg) for i, arg in enumerate(args))
|
|
replacemap["$0"] = name
|
|
replacemap["$$"] = "$"
|
|
replacemap["$@"] = " ".join(args)
|
|
# Typical Unix shells interpolate "$@" (with quotes) as all the positional
|
|
# parameters, separated out into words. Emulate the same behavior here by
|
|
# quoting the arguments individually. POSIX shells will then typically
|
|
# tokenize each argument into exactly one word.
|
|
replacemap['"$@"'] = " ".join(util.shellquote(arg) for arg in args)
|
|
# escape '\$' for regex
|
|
regex = "|".join(replacemap.keys()).replace("$", br"\$")
|
|
r = re.compile(regex)
|
|
return r.sub(lambda x: replacemap[x.group()], cmd)
|
|
|
|
|
|
class cmdalias(object):
|
|
def __init__(self, name, definition, cmdtable, source, aliasdoc=None):
|
|
self.name = self.cmd = name
|
|
self.cmdname = ""
|
|
self.definition = definition
|
|
self.fn = None
|
|
self.givenargs = []
|
|
self.opts = []
|
|
self.help = ""
|
|
self.badalias = None
|
|
self.unknowncmd = False
|
|
self.source = source
|
|
self.aliasdoc = aliasdoc
|
|
|
|
try:
|
|
aliases, entry = cmdutil.findcmd(self.name, cmdtable)
|
|
for alias, e in cmdtable.iteritems():
|
|
if e is entry:
|
|
self.cmd = alias
|
|
break
|
|
except error.UnknownCommand:
|
|
pass
|
|
|
|
if not self.definition:
|
|
self.badalias = _("no definition for alias '%s'") % self.name
|
|
return
|
|
|
|
if self.definition.startswith("!"):
|
|
self.shell = True
|
|
|
|
def fn(ui, *args):
|
|
env = {"HG_ARGS": " ".join((self.name,) + args)}
|
|
|
|
def _checkvar(m):
|
|
if m.groups()[0] == "$":
|
|
return m.group()
|
|
elif int(m.groups()[0]) <= len(args):
|
|
return m.group()
|
|
else:
|
|
ui.debug(
|
|
"No argument found for substitution "
|
|
"of %i variable in alias '%s' definition."
|
|
% (int(m.groups()[0]), self.name)
|
|
)
|
|
return ""
|
|
|
|
cmd = re.sub(br"\$(\d+|\$)", _checkvar, self.definition[1:])
|
|
cmd = aliasinterpolate(self.name, args, cmd)
|
|
return ui.system(cmd, environ=env, blockedtag="alias")
|
|
|
|
self.fn = fn
|
|
return
|
|
|
|
try:
|
|
args = pycompat.shlexsplit(self.definition)
|
|
except ValueError as inst:
|
|
self.badalias = _("error in definition for alias '%s': %s") % (
|
|
self.name,
|
|
inst,
|
|
)
|
|
return
|
|
earlyopts, args = _earlysplitopts(args)
|
|
if earlyopts:
|
|
self.badalias = _(
|
|
"error in definition for alias '%s': %s may "
|
|
"only be given on the command line"
|
|
) % (self.name, "/".join(zip(*earlyopts)[0]))
|
|
return
|
|
|
|
self.cmdname = cmd = args[0]
|
|
try:
|
|
cmd, args, aliases, entry = cmdutil.findsubcmd(
|
|
args, cmdtable, strict=False, partial=True
|
|
)
|
|
self.cmdname = cmd
|
|
self.givenargs = args
|
|
if len(entry) > 2:
|
|
self.fn, self.opts, self.help = entry
|
|
else:
|
|
self.fn, self.opts = entry
|
|
|
|
if self.help.startswith("hg " + cmd):
|
|
# drop prefix in old-style help lines so hg shows the alias
|
|
self.help = self.help[4 + len(cmd) :]
|
|
self.cmdtemplate = self.fn.cmdtemplate
|
|
self.norepo = self.fn.norepo
|
|
self.__doc__ = self.fn.__doc__
|
|
|
|
except error.UnknownCommand:
|
|
self.badalias = _("alias '%s' resolves to unknown command '%s'") % (
|
|
self.name,
|
|
cmd,
|
|
)
|
|
self.unknowncmd = True
|
|
except error.AmbiguousCommand:
|
|
self.badalias = _("alias '%s' resolves to ambiguous command '%s'") % (
|
|
self.name,
|
|
cmd,
|
|
)
|
|
except error.UnknownSubcommand as e:
|
|
cmd, subcmd, __ = e.args
|
|
self.badalias = _(
|
|
"alias '%s' resolves to unknown subcommand " "'%s %s'"
|
|
) % (self.name, cmd, subcmd)
|
|
|
|
@property
|
|
def args(self):
|
|
args = pycompat.maplist(util.expandpath, self.givenargs)
|
|
return aliasargs(self.fn, args)
|
|
|
|
def __getattr__(self, name):
|
|
adefaults = {
|
|
r"norepo": True,
|
|
r"cmdtemplate": False,
|
|
r"cmdtype": unrecoverablewrite,
|
|
r"optionalrepo": False,
|
|
r"inferrepo": False,
|
|
r"subcommands": {},
|
|
r"subcommandcategories": [],
|
|
r"subonly": False,
|
|
}
|
|
if name not in adefaults:
|
|
raise AttributeError(name)
|
|
if self.badalias or util.safehasattr(self, "shell"):
|
|
return adefaults[name]
|
|
return getattr(self.fn, name)
|
|
|
|
def __call__(self, ui, *args, **opts):
|
|
if self.badalias:
|
|
hint = None
|
|
if self.unknowncmd:
|
|
try:
|
|
# check if the command is in a disabled extension
|
|
cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
|
|
hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
|
|
except error.UnknownCommand:
|
|
pass
|
|
raise error.Abort(self.badalias, hint=hint)
|
|
|
|
ui.log(
|
|
"commandalias", "alias '%s' expands to '%s'\n", self.name, self.definition
|
|
)
|
|
if util.safehasattr(self, "shell"):
|
|
return self.fn(ui, *args, **opts)
|
|
else:
|
|
try:
|
|
return util.checksignature(self.fn)(ui, *args, **opts)
|
|
except error.SignatureError:
|
|
args = " ".join([self.cmdname] + self.args)
|
|
ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
|
|
raise
|
|
|
|
|
|
class lazyaliasentry(object):
|
|
"""like a typical command entry (func, opts, help), but is lazy"""
|
|
|
|
# Keep track of aliases being resolved to detect cycles.
|
|
_resolving = []
|
|
|
|
def __init__(self, name, definition, cmdtable, source):
|
|
self.name = name
|
|
self.definition = definition
|
|
self.cmdtable = cmdtable
|
|
self.source = source
|
|
self.aliasdoc = None
|
|
|
|
# If an alias and its definition have a same command name (ex.
|
|
# "log = log -v"). Copy the command table right now so the old
|
|
# command definition becomes accessible. This is the only special
|
|
# case. Commands referred by other aliases will be resolved as
|
|
# aliases if possible, regardless of the config order.
|
|
# Copying cmdtable is cheaper than resolving a command, which
|
|
# could be O(len(cmdtable)) in Python.
|
|
if not definition.startswith("!"):
|
|
try:
|
|
cmdname = pycompat.shlexsplit(definition)[:1]
|
|
except ValueError:
|
|
cmdname = []
|
|
if [name] == cmdname:
|
|
self.cmdtable = cmdtable.copy()
|
|
|
|
@util.propertycache
|
|
def _aliasdef(self):
|
|
if self.name in self._resolving:
|
|
raise error.Abort(
|
|
_("circular aliases: %s") % " ".join(sorted(self._resolving))
|
|
)
|
|
self._resolving.append(self.name)
|
|
try:
|
|
return cmdalias(
|
|
self.name, self.definition, self.cmdtable, self.source, self.aliasdoc
|
|
)
|
|
finally:
|
|
popped = self._resolving.pop()
|
|
assert popped == self.name
|
|
|
|
def __getitem__(self, n):
|
|
aliasdef = self._aliasdef
|
|
l = [aliasdef, aliasdef.opts, aliasdef.help]
|
|
return l[n]
|
|
|
|
def __iter__(self):
|
|
for i in range(3):
|
|
yield self[i]
|
|
|
|
def __len__(self):
|
|
return 3
|
|
|
|
|
|
class cmdtemplatestate(object):
|
|
"""Template-related state for a command.
|
|
|
|
Used together with cmdtemplate=True.
|
|
|
|
In MVC's sense, this is the "M". The template language takes the "M" and
|
|
renders the "V".
|
|
"""
|
|
|
|
def __init__(self, ui, opts):
|
|
self._ui = ui
|
|
self._templ = opts.get("template")
|
|
if self._templ:
|
|
# Suppress traditional outputs.
|
|
ui.pushbuffer()
|
|
self._props = {}
|
|
|
|
def setprop(self, name, value):
|
|
self._props[name] = value
|
|
|
|
def end(self):
|
|
if self._templ:
|
|
ui = self._ui
|
|
ui.popbuffer()
|
|
text = cmdutil.rendertemplate(self._ui, self._templ, self._props)
|
|
self._ui.write(text)
|
|
|
|
|
|
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.
|
|
aliasdoc = {}
|
|
for alias, definition in ui.configitems("alias"):
|
|
if alias.endswith(":doc"):
|
|
aliasdoc[alias[:-4]] = definition
|
|
continue
|
|
try:
|
|
if cmdtable[alias].definition == definition:
|
|
continue
|
|
except (KeyError, AttributeError):
|
|
# definition might not exist or it might not be a cmdalias
|
|
pass
|
|
|
|
source = ui.configsource("alias", alias)
|
|
entry = lazyaliasentry(alias, definition, cmdtable, source)
|
|
cmdtable[alias] = entry
|
|
for alias, doc in aliasdoc.items():
|
|
if alias in cmdtable:
|
|
cmdtable[alias].aliasdoc = doc
|
|
|
|
|
|
def _parse(ui, args):
|
|
options = {}
|
|
cmdoptions = {}
|
|
|
|
try:
|
|
args = fancyopts.fancyopts(args, commands.globalopts, options)
|
|
except getopt.GetoptError as inst:
|
|
raise error.CommandError(None, inst)
|
|
|
|
if args:
|
|
strict = ui.configbool("ui", "strict")
|
|
cmd, args, aliases, entry = cmdutil.findsubcmd(args, commands.table, strict)
|
|
args = aliasargs(entry[0], args)
|
|
defaults = ui.config("defaults", cmd)
|
|
if defaults:
|
|
args = (
|
|
pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults)) + args
|
|
)
|
|
c = list(entry[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, gnu=True)
|
|
except getopt.GetoptError as inst:
|
|
raise error.CommandError(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 entry[0] or None, args, options, cmdoptions)
|
|
|
|
|
|
def _parseconfig(ui, config):
|
|
"""parse the --config options from the command line"""
|
|
configs = []
|
|
|
|
for cfg in config:
|
|
try:
|
|
name, value = [cfgelem.strip() for cfgelem in cfg.split("=", 1)]
|
|
section, name = name.split(".", 1)
|
|
if not section or not name:
|
|
raise IndexError
|
|
ui.setconfig(section, name, value, "--config")
|
|
configs.append((section, name, value))
|
|
except (IndexError, ValueError):
|
|
raise error.Abort(
|
|
_("malformed --config option: %r " "(use --config section.name=value)")
|
|
% cfg
|
|
)
|
|
|
|
return configs
|
|
|
|
|
|
def _earlyparseopts(ui, args):
|
|
options = {}
|
|
fancyopts.fancyopts(
|
|
args,
|
|
commands.globalopts,
|
|
options,
|
|
gnu=not ui.plain("strictflags"),
|
|
early=True,
|
|
optaliases={"repository": ["repo"]},
|
|
)
|
|
return options
|
|
|
|
|
|
def _earlysplitopts(args):
|
|
"""Split args into a list of possible early options and remainder args"""
|
|
shortoptions = "R:"
|
|
# TODO: perhaps 'debugger' should be included
|
|
longoptions = ["cwd=", "repository=", "repo=", "config="]
|
|
return fancyopts.earlygetopt(
|
|
args, shortoptions, longoptions, gnu=True, keepsep=True
|
|
)
|
|
|
|
|
|
def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
|
|
# run pre-hook, and abort if it fails
|
|
hook.hook(
|
|
lui,
|
|
repo,
|
|
"pre-%s" % cmd,
|
|
True,
|
|
args=" ".join(fullargs),
|
|
pats=cmdpats,
|
|
opts=cmdoptions,
|
|
)
|
|
try:
|
|
hintutil.loadhintconfig(lui)
|
|
ui.log("jobid", jobid=encoding.environ.get("HG_JOB_ID", "unknown"))
|
|
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,
|
|
pats=cmdpats,
|
|
opts=cmdoptions,
|
|
)
|
|
except Exception as e:
|
|
# run failure hook and re-raise
|
|
hook.hook(
|
|
lui,
|
|
repo,
|
|
"fail-%s" % cmd,
|
|
False,
|
|
args=" ".join(fullargs),
|
|
pats=cmdpats,
|
|
opts=cmdoptions,
|
|
)
|
|
_log_exception(lui, e)
|
|
raise
|
|
return ret
|
|
|
|
|
|
def _log_exception(lui, e):
|
|
try:
|
|
lui.log("exceptions", exception_type=type(e).__name__, exception_msg=str(e))
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _getlocal(ui, rpath, wd=None):
|
|
"""Return (path, local ui object) for the given target path.
|
|
|
|
Takes paths in [cwd]/.hg/hgrc into account."
|
|
"""
|
|
if wd is None:
|
|
try:
|
|
wd = pycompat.getcwd()
|
|
except OSError as e:
|
|
if e.errno == errno.ENOTCONN:
|
|
ui.warn(_("current working directory is not connected\n"))
|
|
ui.warn(
|
|
_(
|
|
"(for virtual checkouts, run '@prog@ fs doctor' to diagnose issues with edenfs)\n"
|
|
)
|
|
)
|
|
return "", ui
|
|
raise error.Abort(
|
|
_("error getting current working directory: %s")
|
|
% encoding.strtolocal(e.strerror)
|
|
)
|
|
path = cmdutil.findrepo(wd) or ""
|
|
if not path:
|
|
lui = ui
|
|
else:
|
|
lui = ui.copy()
|
|
lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
|
|
|
|
if rpath:
|
|
path = lui.expandpath(rpath)
|
|
lui = ui.copy()
|
|
lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
|
|
|
|
return path, lui
|
|
|
|
|
|
def _checkshellalias(lui, ui, args):
|
|
"""Return the function to run the shell alias, if it is required"""
|
|
options = {}
|
|
|
|
try:
|
|
args = fancyopts.fancyopts(args, commands.globalopts, options)
|
|
except getopt.GetoptError:
|
|
return
|
|
|
|
if not args:
|
|
return
|
|
|
|
cmdtable = commands.table
|
|
|
|
cmd = args[0]
|
|
try:
|
|
strict = ui.configbool("ui", "strict")
|
|
aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
|
|
except (error.AmbiguousCommand, error.UnknownCommand):
|
|
return
|
|
|
|
cmd = aliases[0]
|
|
fn = entry[0]
|
|
|
|
if cmd and util.safehasattr(fn, "shell"):
|
|
# shell alias shouldn't receive early options which are consumed by hg
|
|
_earlyopts, args = _earlysplitopts(args)
|
|
d = lambda: fn(ui, *args[1:])
|
|
return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
|
|
|
|
|
|
def _dispatch(req):
|
|
args = req.args
|
|
ui = req.ui
|
|
|
|
# check for cwd
|
|
cwd = req.earlyoptions["cwd"]
|
|
if cwd:
|
|
os.chdir(cwd)
|
|
|
|
rpath = req.earlyoptions["repository"]
|
|
path, lui = _getlocal(ui, rpath)
|
|
|
|
uis = {ui, lui}
|
|
|
|
if req.repo:
|
|
uis.add(req.repo.ui)
|
|
|
|
if req.earlyoptions["profile"]:
|
|
for ui_ in uis:
|
|
ui_.setconfig("profiling", "enabled", "true", "--profile")
|
|
|
|
with profiling.profile(lui) as profiler:
|
|
# Configure extensions in phases: uisetup, extsetup, cmdtable, and
|
|
# reposetup
|
|
extensions.loadall(lui)
|
|
# Propagate any changes to lui.__class__ by extensions
|
|
ui.__class__ = lui.__class__
|
|
|
|
# (uisetup and extsetup are handled in extensions.loadall)
|
|
|
|
# (reposetup is handled in hg.repository)
|
|
|
|
addaliases(lui, commands.table)
|
|
|
|
# All aliases and commands are completely defined, now.
|
|
# Check abbreviation/ambiguity of shell alias.
|
|
shellaliasfn = _checkshellalias(lui, ui, args)
|
|
if shellaliasfn:
|
|
return shellaliasfn()
|
|
|
|
# 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["encoding"]:
|
|
encoding.encoding = options["encoding"]
|
|
if options["encodingmode"]:
|
|
encoding.encodingmode = options["encodingmode"]
|
|
i18n.init()
|
|
|
|
if options["config"] != req.earlyoptions["config"]:
|
|
raise error.Abort(_("option --config may not be abbreviated!"))
|
|
if options["configfile"] != req.earlyoptions["configfile"]:
|
|
raise error.Abort(_("option --configfile may not be abbreviated!"))
|
|
if options["cwd"] != req.earlyoptions["cwd"]:
|
|
raise error.Abort(_("option --cwd may not be abbreviated!"))
|
|
if options["repository"] != req.earlyoptions["repository"]:
|
|
raise error.Abort(
|
|
_(
|
|
"option -R has to be separated from other options (e.g. not "
|
|
"-qR) and --repository may only be abbreviated as --repo!"
|
|
)
|
|
)
|
|
if options["debugger"] != req.earlyoptions["debugger"]:
|
|
raise error.Abort(_("option --debugger may not be abbreviated!"))
|
|
# don't validate --profile/--traceback, which can be enabled from now
|
|
|
|
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])
|
|
)
|
|
|
|
ui.atexit(print_time)
|
|
if options["profile"]:
|
|
profiler.start()
|
|
|
|
if options["verbose"] or options["debug"] or options["quiet"]:
|
|
for opt in ("verbose", "debug", "quiet"):
|
|
val = str(bool(options[opt]))
|
|
if pycompat.ispy3:
|
|
val = val.encode("ascii")
|
|
for ui_ in uis:
|
|
ui_.setconfig("ui", opt, val, "--" + opt)
|
|
|
|
if options["traceback"]:
|
|
for ui_ in uis:
|
|
ui_.setconfig("ui", "traceback", "on", "--traceback")
|
|
|
|
if options["noninteractive"]:
|
|
for ui_ in uis:
|
|
ui_.setconfig("ui", "interactive", "off", "-y")
|
|
|
|
if cmdoptions.get("insecure", False):
|
|
for ui_ in uis:
|
|
ui_.insecureconnections = True
|
|
|
|
# setup color handling before pager, because setting up pager
|
|
# might cause incorrect console information
|
|
coloropt = options["color"]
|
|
for ui_ in uis:
|
|
if coloropt:
|
|
ui_.setconfig("ui", "color", coloropt, "--color")
|
|
color.setup(ui_)
|
|
|
|
if util.parsebool(options["pager"]):
|
|
# ui.pager() expects 'internal-always-' prefix in this case
|
|
ui.pager("internal-always-" + cmd)
|
|
elif options["pager"] != "auto":
|
|
for ui_ in uis:
|
|
ui_.disablepager()
|
|
|
|
if options["version"]:
|
|
return commands.version_(ui)
|
|
if options["help"]:
|
|
return commands.help_(ui, cmd, command=cmd is not None)
|
|
elif not cmd:
|
|
return commands.help_(ui)
|
|
|
|
msg = _formatargs(fullargs)
|
|
with perftrace.trace("hg " + msg):
|
|
repo = None
|
|
if func.cmdtemplate:
|
|
templ = cmdtemplatestate(ui, cmdoptions)
|
|
args.insert(0, templ)
|
|
ui.atexit(templ.end)
|
|
cmdpats = args[:]
|
|
if not func.norepo:
|
|
# use the repo from the request only if we don't have -R
|
|
if not rpath and not cwd:
|
|
repo = req.repo
|
|
|
|
if repo:
|
|
# set the descriptors of the repo ui to those of ui
|
|
repo.ui.fin = ui.fin
|
|
repo.ui.fout = ui.fout
|
|
repo.ui.ferr = ui.ferr
|
|
else:
|
|
try:
|
|
repo = hg.repository(
|
|
ui, path=path, presetupfuncs=req.prereposetups
|
|
)
|
|
if not repo.local():
|
|
raise error.Abort(_("repository '%s' is not local") % path)
|
|
repo.ui.setconfig("bundle", "mainreporoot", repo.root, "repo")
|
|
except error.RequirementError:
|
|
raise
|
|
except error.RepoError:
|
|
if rpath: # invalid -R path
|
|
raise
|
|
if not func.optionalrepo:
|
|
if func.inferrepo and args and not path:
|
|
# try to infer -R from command args
|
|
repos = pycompat.maplist(cmdutil.findrepo, args)
|
|
guess = repos[0]
|
|
if guess and repos.count(guess) == len(repos):
|
|
req.args = ["--repository", guess] + fullargs
|
|
req.earlyoptions["repository"] = guess
|
|
return _dispatch(req)
|
|
if not path:
|
|
raise error.RepoError(
|
|
_("no repository found in" " '%s' (.hg not found)")
|
|
% pycompat.getcwd()
|
|
)
|
|
raise
|
|
if repo:
|
|
ui = repo.ui
|
|
if options["hidden"]:
|
|
repo = repo.unfiltered()
|
|
if repo != req.repo:
|
|
ui.atexit(repo.close)
|
|
args.insert(0, repo)
|
|
elif rpath:
|
|
ui.warn(_("warning: --repository ignored\n"))
|
|
|
|
from . import mdiff
|
|
|
|
mdiff.init(ui)
|
|
|
|
ui.log("command", "%s\n", msg)
|
|
if repo:
|
|
repo.dirstate.loginfo(ui, "pre")
|
|
strcmdopt = pycompat.strkwargs(cmdoptions)
|
|
d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
|
|
ret = runcommand(
|
|
lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
|
|
)
|
|
hintutil.show(lui)
|
|
if repo:
|
|
repo.dirstate.loginfo(ui, "post")
|
|
return ret
|
|
|
|
|
|
def _runcommand(ui, options, cmd, cmdfunc):
|
|
"""Run a command function, possibly with profiling enabled."""
|
|
try:
|
|
return cmdfunc()
|
|
except error.SignatureError:
|
|
raise error.CommandError(cmd, _("invalid arguments"))
|
|
|
|
|
|
def _exceptionwarning(ui):
|
|
"""Produce a warning message for the current active exception"""
|
|
|
|
return _("** @LongProduct@ (version %s) has crashed:\n") % util.version()
|
|
|
|
|
|
def handlecommandexception(ui):
|
|
"""Produce a warning message for broken commands
|
|
|
|
Called when handling an exception; the exception is reraised if
|
|
this function returns False, ignored otherwise.
|
|
"""
|
|
warning = _exceptionwarning(ui)
|
|
ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
|
|
ui.warn(warning)
|
|
return False # re-raise the exception
|
|
|
|
|
|
def rejectpush(ui, **kwargs):
|
|
ui.warn(("Permission denied - blocked by readonlyrejectpush hook\n"))
|
|
# mercurial hooks use unix process conventions for hook return values
|
|
# so a truthy return means failure
|
|
return True
|