extensions.py
1005 lines
| 31.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / extensions.py
Matt Mackall
|
r4544 | # extensions.py - extension handling for mercurial | ||
# | ||||
Raphaël Gomès
|
r47575 | # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> | ||
Matt Mackall
|
r4544 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r4544 | |||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Gregory Szorc
|
r25946 | |||
Yuya Nishihara
|
r38180 | import ast | ||
import collections | ||||
Jun Wu
|
r34089 | import functools | ||
Mads Kiilerich
|
r51650 | import importlib | ||
Augie Fackler
|
r31263 | import inspect | ||
Gregory Szorc
|
r25946 | import os | ||
Mads Kiilerich
|
r51650 | import sys | ||
Gregory Szorc
|
r25946 | |||
from .i18n import ( | ||||
_, | ||||
gettext, | ||||
) | ||||
from . import ( | ||||
cmdutil, | ||||
r33127 | configitems, | |||
Gregory Szorc
|
r25946 | error, | ||
Pulkit Goyal
|
r30570 | pycompat, | ||
Gregory Szorc
|
r25946 | util, | ||
) | ||||
Matt Mackall
|
r4544 | |||
Augie Fackler
|
r43346 | from .utils import stringutil | ||
Yuya Nishihara
|
r37102 | |||
Matt Mackall
|
r4544 | _extensions = {} | ||
liscju
|
r29895 | _disabledextensions = {} | ||
Gregory Szorc
|
r24065 | _aftercallbacks = {} | ||
Alexis S. L. Carvalho
|
r5192 | _order = [] | ||
Boris Feld
|
r33526 | _builtin = { | ||
Augie Fackler
|
r43347 | b'hbisect', | ||
b'bookmarks', | ||||
b'color', | ||||
b'parentrevspec', | ||||
b'progress', | ||||
b'interhg', | ||||
b'inotify', | ||||
b'hgcia', | ||||
b'shelve', | ||||
Boris Feld
|
r33526 | } | ||
Alexis S. L. Carvalho
|
r5192 | |||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r19777 | def extensions(ui=None): | ||
if ui: | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r19777 | def enabled(name): | ||
Augie Fackler
|
r43347 | for format in [b'%s', b'hgext.%s']: | ||
conf = ui.config(b'extensions', format % name) | ||||
if conf is not None and not conf.startswith(b'!'): | ||||
FUJIWARA Katsunori
|
r19777 | return True | ||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r19777 | else: | ||
enabled = lambda name: True | ||||
Alexis S. L. Carvalho
|
r5192 | for name in _order: | ||
module = _extensions[name] | ||||
FUJIWARA Katsunori
|
r19777 | if module and enabled(name): | ||
Alexis S. L. Carvalho
|
r5192 | yield name, module | ||
Matt Mackall
|
r4544 | |||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r4544 | def find(name): | ||
'''return module with given extension name''' | ||||
Idan Kamara
|
r14415 | mod = None | ||
Matt Mackall
|
r4544 | try: | ||
timeless
|
r27637 | mod = _extensions[name] | ||
Matt Mackall
|
r4544 | except KeyError: | ||
Gregory Szorc
|
r49768 | for k, v in _extensions.items(): | ||
Augie Fackler
|
r43347 | if k.endswith(b'.' + name) or k.endswith(b'/' + name): | ||
Idan Kamara
|
r14415 | mod = v | ||
break | ||||
if not mod: | ||||
Matt Mackall
|
r4544 | raise KeyError(name) | ||
Idan Kamara
|
r14415 | return mod | ||
Matt Mackall
|
r4544 | |||
Augie Fackler
|
r43346 | |||
Alexander Solovyov
|
r7916 | def loadpath(path, module_name): | ||
r51816 | module_name = module_name.replace('.', '_') | |||
Ed Morley
|
r20645 | path = util.normpath(util.expandpath(path)) | ||
Pulkit Goyal
|
r30575 | path = pycompat.fsdecode(path) | ||
Alexander Solovyov
|
r7916 | if os.path.isdir(path): | ||
# module/__init__.py style | ||||
Mads Kiilerich
|
r51650 | init_py_path = os.path.join(path, '__init__.py') | ||
if not os.path.exists(init_py_path): | ||||
raise ImportError("No module named '%s'" % os.path.basename(path)) | ||||
path = init_py_path | ||||
loader = importlib.machinery.SourceFileLoader(module_name, path) | ||||
spec = importlib.util.spec_from_file_location(module_name, loader=loader) | ||||
assert spec is not None # help Pytype | ||||
module = importlib.util.module_from_spec(spec) | ||||
sys.modules[module_name] = module | ||||
spec.loader.exec_module(module) | ||||
return module | ||||
Alexander Solovyov
|
r7916 | |||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r28505 | def _importh(name): | ||
"""import and return the <name> module""" | ||||
r51816 | mod = __import__(name) | |||
components = name.split('.') | ||||
Pierre-Yves David
|
r28505 | for comp in components[1:]: | ||
mod = getattr(mod, comp) | ||||
return mod | ||||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r30058 | def _importext(name, path=None, reportfunc=None): | ||
r51816 | name = pycompat.fsdecode(name) | |||
Jun Wu
|
r30058 | if path: | ||
# the module will be loaded in sys.modules | ||||
# choose an unique name so that it doesn't | ||||
# conflicts with other modules | ||||
r51816 | mod = loadpath(path, 'hgext.%s' % name) | |||
Jun Wu
|
r30058 | else: | ||
try: | ||||
r51816 | mod = _importh("hgext.%s" % name) | |||
Jun Wu
|
r30058 | except ImportError as err: | ||
if reportfunc: | ||||
r51816 | reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name) | |||
Jun Wu
|
r30058 | try: | ||
r51816 | mod = _importh("hgext3rd.%s" % name) | |||
Jun Wu
|
r30058 | except ImportError as err: | ||
if reportfunc: | ||||
r51816 | reportfunc(err, "hgext3rd.%s" % name, name) | |||
Jun Wu
|
r30058 | mod = _importh(name) | ||
return mod | ||||
Augie Fackler
|
r43346 | |||
Pierre-Yves David
|
r28506 | def _reportimporterror(ui, err, failed, next): | ||
Yuya Nishihara
|
r41032 | # note: this ui.log happens before --debug is processed, | ||
Pierre-Yves David
|
r30028 | # Use --config ui.debug=1 to see them. | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b' - could not import %s (%s): trying %s\n', | ||||
r51816 | stringutil.forcebytestr(failed), | |||
Augie Fackler
|
r43346 | stringutil.forcebytestr(err), | ||
r51816 | stringutil.forcebytestr(next), | |||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r43347 | if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'): | ||
Yuya Nishihara
|
r41032 | ui.traceback() | ||
Pierre-Yves David
|
r28506 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r36283 | def _rejectunicode(name, xs): | ||
if isinstance(xs, (list, set, tuple)): | ||||
for x in xs: | ||||
_rejectunicode(name, x) | ||||
elif isinstance(xs, dict): | ||||
for k, v in xs.items(): | ||||
_rejectunicode(name, k) | ||||
r51814 | k = pycompat.sysstr(k) | |||
_rejectunicode('%s.%s' % (name, k), v) | ||||
elif isinstance(xs, str): | ||||
Augie Fackler
|
r43346 | raise error.ProgrammingError( | ||
r51814 | b"unicode %r found in %s" % (xs, stringutil.forcebytestr(name)), | |||
Augie Fackler
|
r43347 | hint=b"use b'' to make it byte string", | ||
Augie Fackler
|
r43346 | ) | ||
Yuya Nishihara
|
r36283 | |||
Yuya Nishihara
|
r32342 | # attributes set by registrar.command | ||
r51815 | _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo') | |||
Yuya Nishihara
|
r32342 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r32343 | def _validatecmdtable(ui, cmdtable): | ||
Yuya Nishihara
|
r32342 | """Check if extension commands have required attributes""" | ||
Gregory Szorc
|
r49768 | for c, e in cmdtable.items(): | ||
Yuya Nishihara
|
r32342 | f = e[0] | ||
r51821 | missing = [a for a in _cmdfuncattrs if not hasattr(f, a)] | |||
Yuya Nishihara
|
r32342 | if not missing: | ||
continue | ||||
r51815 | msg = b'missing attributes: %s' | |||
msg %= b', '.join([stringutil.forcebytestr(m) for m in missing]) | ||||
hint = b"use @command decorator to register '%s'" % c | ||||
raise error.ProgrammingError(msg, hint=hint) | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r32342 | |||
Yuya Nishihara
|
r36283 | def _validatetables(ui, mod): | ||
"""Sanity check for loadable tables provided by extension module""" | ||||
r51814 | for t in ['cmdtable', 'colortable', 'configtable']: | |||
Yuya Nishihara
|
r36283 | _rejectunicode(t, getattr(mod, t, {})) | ||
Augie Fackler
|
r43346 | for t in [ | ||
r51814 | 'filesetpredicate', | |||
'internalmerge', | ||||
'revsetpredicate', | ||||
'templatefilter', | ||||
'templatefunc', | ||||
'templatekeyword', | ||||
Augie Fackler
|
r43346 | ]: | ||
Yuya Nishihara
|
r36283 | o = getattr(mod, t, None) | ||
if o: | ||||
_rejectunicode(t, o._table) | ||||
_validatecmdtable(ui, getattr(mod, 'cmdtable', {})) | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r41032 | def load(ui, name, path, loadingtime=None): | ||
Augie Fackler
|
r43347 | if name.startswith(b'hgext.') or name.startswith(b'hgext/'): | ||
Bryan O'Sullivan
|
r5031 | shortname = name[6:] | ||
else: | ||||
shortname = name | ||||
Bryan O'Sullivan
|
r27111 | if shortname in _builtin: | ||
Matt Mackall
|
r13349 | return None | ||
Bryan O'Sullivan
|
r5031 | if shortname in _extensions: | ||
Erik Zielke
|
r12779 | return _extensions[shortname] | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' - loading extension: %s\n', shortname) | ||
Brendan Cully
|
r5087 | _extensions[shortname] = None | ||
Augie Fackler
|
r43532 | with util.timedcm('load extension %s', shortname) as stats: | ||
Martijn Pieters
|
r38834 | mod = _importext(name, path, bind(_reportimporterror, ui)) | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats) | ||
Boris Feld
|
r39547 | if loadingtime is not None: | ||
loadingtime[shortname] += stats.elapsed | ||||
Gregory Szorc
|
r27142 | |||
# Before we do anything with the extension, check against minimum stated | ||||
# compatibility. This gives extension authors a mechanism to have their | ||||
# extensions short circuit when loaded with a known incompatible version | ||||
# of Mercurial. | ||||
minver = getattr(mod, 'minimumhgversion', None) | ||||
Matt Harbison
|
r46511 | if minver: | ||
curver = util.versiontuple(n=2) | ||||
Matt Harbison
|
r48829 | extmin = util.versiontuple(stringutil.forcebytestr(minver), 2) | ||
Matt Harbison
|
r46511 | |||
Matt Harbison
|
r48828 | if None in extmin: | ||
extmin = (extmin[0] or 0, extmin[1] or 0) | ||||
if None in curver or extmin > curver: | ||||
Matt Harbison
|
r46511 | msg = _( | ||
b'(third party extension %s requires version %s or newer ' | ||||
b'of Mercurial (current: %s); disabling)\n' | ||||
) | ||||
ui.warn(msg % (shortname, minver, util.version())) | ||||
return | ||||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' - validating extension tables: %s\n', shortname) | ||
Yuya Nishihara
|
r36283 | _validatetables(ui, mod) | ||
Gregory Szorc
|
r27142 | |||
Bryan O'Sullivan
|
r5031 | _extensions[shortname] = mod | ||
Alexis S. L. Carvalho
|
r5192 | _order.append(shortname) | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', b' - invoking registered callbacks: %s\n', shortname | ||||
) | ||||
Augie Fackler
|
r43532 | with util.timedcm('callbacks extension %s', shortname) as stats: | ||
Martijn Pieters
|
r38834 | for fn in _aftercallbacks.get(shortname, []): | ||
fn(loaded=True) | ||||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' > callbacks completed in %s\n', stats) | ||
Erik Zielke
|
r12779 | return mod | ||
Matt Mackall
|
r4544 | |||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r29461 | def _runuisetup(name, ui): | ||
uisetup = getattr(_extensions[name], 'uisetup', None) | ||||
if uisetup: | ||||
Augie Fackler
|
r32724 | try: | ||
uisetup(ui) | ||||
except Exception as inst: | ||||
Martin von Zweigbergk
|
r34846 | ui.traceback(force=True) | ||
Yuya Nishihara
|
r37102 | msg = stringutil.forcebytestr(inst) | ||
Augie Fackler
|
r43347 | ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg)) | ||
Augie Fackler
|
r32724 | return False | ||
return True | ||||
Jun Wu
|
r29461 | |||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r29461 | def _runextsetup(name, ui): | ||
extsetup = getattr(_extensions[name], 'extsetup', None) | ||||
if extsetup: | ||||
try: | ||||
Matt Harbison
|
r42522 | extsetup(ui) | ||
Augie Fackler
|
r32724 | except Exception as inst: | ||
Martin von Zweigbergk
|
r34846 | ui.traceback(force=True) | ||
Yuya Nishihara
|
r37102 | msg = stringutil.forcebytestr(inst) | ||
Augie Fackler
|
r43347 | ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg)) | ||
Augie Fackler
|
r32724 | return False | ||
return True | ||||
Jun Wu
|
r29461 | |||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r32416 | def loadall(ui, whitelist=None): | ||
Boris Feld
|
r39547 | loadingtime = collections.defaultdict(int) | ||
Augie Fackler
|
r43347 | result = ui.configitems(b"extensions") | ||
Jun Wu
|
r32417 | if whitelist is not None: | ||
Jun Wu
|
r32416 | result = [(k, v) for (k, v) in result if k in whitelist] | ||
r49181 | result = [(k, v) for (k, v) in result if b':' not in k] | |||
Martin Geisler
|
r9410 | newindex = len(_order) | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b'loading %sextensions\n', | ||||
Augie Fackler
|
r43347 | b'additional ' if newindex else b'', | ||
Augie Fackler
|
r43346 | ) | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- processing %d entries\n', len(result)) | ||
Augie Fackler
|
r43532 | with util.timedcm('load all extensions') as stats: | ||
r49185 | default_sub_options = ui.configsuboptions(b"extensions", b"*")[1] | |||
Raphaël Gomès
|
r52596 | for name, path in result: | ||
Jesse Glick
|
r6204 | if path: | ||
Augie Fackler
|
r43347 | if path[0:1] == b'!': | ||
Martijn Pieters
|
r38834 | if name not in _disabledextensions: | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b' - skipping disabled extension: %s\n', | ||||
name, | ||||
) | ||||
Martijn Pieters
|
r38834 | _disabledextensions[name] = path[1:] | ||
continue | ||||
try: | ||||
Yuya Nishihara
|
r41032 | load(ui, name, path, loadingtime) | ||
Martijn Pieters
|
r38834 | except Exception as inst: | ||
msg = stringutil.forcebytestr(inst) | ||||
if path: | ||||
r49183 | error_msg = _( | |||
b'failed to import extension "%s" from %s: %s' | ||||
) | ||||
r49182 | error_msg %= (name, path, msg) | |||
Martijn Pieters
|
r38834 | else: | ||
r49183 | error_msg = _(b'failed to import extension "%s": %s') | |||
r49182 | error_msg %= (name, msg) | |||
r49184 | ||||
r49185 | options = default_sub_options.copy() | |||
r49184 | ext_options = ui.configsuboptions(b"extensions", name)[1] | |||
r49185 | options.update(ext_options) | |||
if stringutil.parsebool(options.get(b"required", b'no')): | ||||
r49184 | hint = None | |||
if isinstance(inst, error.Hint) and inst.hint: | ||||
hint = inst.hint | ||||
if hint is None: | ||||
hint = _( | ||||
b"loading of this extension was required, " | ||||
b"see `hg help config.extensions` for details" | ||||
) | ||||
raise error.Abort(error_msg, hint=hint) | ||||
else: | ||||
ui.warn((b"*** %s\n") % error_msg) | ||||
if isinstance(inst, error.Hint) and inst.hint: | ||||
ui.warn(_(b"*** (%s)\n") % inst.hint) | ||||
ui.traceback() | ||||
Martijn Pieters
|
r38834 | |||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b'> loaded %d extensions, total time %s\n', | ||||
len(_order) - newindex, | ||||
stats, | ||||
) | ||||
Boris Feld
|
r34188 | # list of (objname, loadermod, loadername) tuple: | ||
# - objname is the name of an object in extension module, | ||||
# from which extra information is loaded | ||||
# - loadermod is the module where loader is placed | ||||
# - loadername is the name of the function, | ||||
# which takes (ui, extensionname, extraobj) arguments | ||||
# | ||||
# This one is for the list of item that must be run before running any setup | ||||
earlyextraloaders = [ | ||||
r51814 | ('configtable', configitems, 'loadconfigtable'), | |||
Boris Feld
|
r34188 | ] | ||
Martijn Pieters
|
r38834 | |||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- loading configtable attributes\n') | ||
Boris Feld
|
r34188 | _loadextra(ui, newindex, earlyextraloaders) | ||
Matt Mackall
|
r4544 | |||
Augie Fackler
|
r32724 | broken = set() | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- executing uisetup hooks\n') | ||
Augie Fackler
|
r43532 | with util.timedcm('all uisetup') as alluisetupstats: | ||
Boris Feld
|
r39544 | for name in _order[newindex:]: | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' - running uisetup for %s\n', name) | ||
Augie Fackler
|
r43532 | with util.timedcm('uisetup %s', name) as stats: | ||
Boris Feld
|
r39544 | if not _runuisetup(name, ui): | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b' - the %s extension uisetup failed\n', | ||||
name, | ||||
) | ||||
Boris Feld
|
r39544 | broken.add(name) | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats) | ||
Boris Feld
|
r39547 | loadingtime[name] += stats.elapsed | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats) | ||
Martin Geisler
|
r9410 | |||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- executing extsetup hooks\n') | ||
Augie Fackler
|
r43532 | with util.timedcm('all extsetup') as allextetupstats: | ||
Boris Feld
|
r39545 | for name in _order[newindex:]: | ||
if name in broken: | ||||
continue | ||||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' - running extsetup for %s\n', name) | ||
Augie Fackler
|
r43532 | with util.timedcm('extsetup %s', name) as stats: | ||
Boris Feld
|
r39545 | if not _runextsetup(name, ui): | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b' - the %s extension extsetup failed\n', | ||||
name, | ||||
) | ||||
Boris Feld
|
r39545 | broken.add(name) | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats) | ||
Boris Feld
|
r39547 | loadingtime[name] += stats.elapsed | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats) | ||
Augie Fackler
|
r32724 | |||
for name in broken: | ||||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b' - disabling broken %s extension\n', name) | ||
Augie Fackler
|
r32724 | _extensions[name] = None | ||
Yuya Nishihara
|
r9660 | |||
Gregory Szorc
|
r24065 | # Call aftercallbacks that were never met. | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- executing remaining aftercallbacks\n') | ||
Augie Fackler
|
r43532 | with util.timedcm('aftercallbacks') as stats: | ||
Martijn Pieters
|
r38834 | for shortname in _aftercallbacks: | ||
if shortname in _extensions: | ||||
continue | ||||
Gregory Szorc
|
r24065 | |||
Martijn Pieters
|
r38834 | for fn in _aftercallbacks[shortname]: | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b' - extension %s not loaded, notify callbacks\n', | ||||
shortname, | ||||
) | ||||
Martijn Pieters
|
r38834 | fn(loaded=False) | ||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats) | ||
Gregory Szorc
|
r24065 | |||
Gregory Szorc
|
r24950 | # loadall() is called multiple times and lingering _aftercallbacks | ||
# entries could result in double execution. See issue4646. | ||||
_aftercallbacks.clear() | ||||
FUJIWARA Katsunori
|
r33052 | # delay importing avoids cyclic dependency (especially commands) | ||
from . import ( | ||||
color, | ||||
commands, | ||||
FUJIWARA Katsunori
|
r33663 | filemerge, | ||
FUJIWARA Katsunori
|
r33052 | fileset, | ||
revset, | ||||
templatefilters, | ||||
Yuya Nishihara
|
r36940 | templatefuncs, | ||
FUJIWARA Katsunori
|
r33052 | templatekw, | ||
) | ||||
# list of (objname, loadermod, loadername) tuple: | ||||
# - objname is the name of an object in extension module, | ||||
# from which extra information is loaded | ||||
# - loadermod is the module where loader is placed | ||||
# - loadername is the name of the function, | ||||
# which takes (ui, extensionname, extraobj) arguments | ||||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'- loading extension registration objects\n') | ||
FUJIWARA Katsunori
|
r33052 | extraloaders = [ | ||
r51814 | ('cmdtable', commands, 'loadcmdtable'), | |||
('colortable', color, 'loadcolortable'), | ||||
('filesetpredicate', fileset, 'loadpredicate'), | ||||
('internalmerge', filemerge, 'loadinternalmerge'), | ||||
('revsetpredicate', revset, 'loadpredicate'), | ||||
('templatefilter', templatefilters, 'loadfilter'), | ||||
('templatefunc', templatefuncs, 'loadfunction'), | ||||
('templatekeyword', templatekw, 'loadkeyword'), | ||||
FUJIWARA Katsunori
|
r33052 | ] | ||
Augie Fackler
|
r43532 | with util.timedcm('load registration objects') as stats: | ||
Martijn Pieters
|
r38834 | _loadextra(ui, newindex, extraloaders) | ||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b'> extension registration object loading took %s\n', | ||||
stats, | ||||
) | ||||
Boris Feld
|
r39547 | |||
# Report per extension loading time (except reposetup) | ||||
for name in sorted(loadingtime): | ||||
Augie Fackler
|
r43346 | ui.log( | ||
b'extension', | ||||
b'> extension %s take a total of %s to load\n', | ||||
name, | ||||
util.timecount(loadingtime[name]), | ||||
) | ||||
Boris Feld
|
r39547 | |||
Yuya Nishihara
|
r41032 | ui.log(b'extension', b'extension loading complete\n') | ||
FUJIWARA Katsunori
|
r33052 | |||
Augie Fackler
|
r43346 | |||
Boris Feld
|
r34187 | def _loadextra(ui, newindex, extraloaders): | ||
FUJIWARA Katsunori
|
r33052 | for name in _order[newindex:]: | ||
module = _extensions[name] | ||||
if not module: | ||||
Augie Fackler
|
r43346 | continue # loading this module failed | ||
FUJIWARA Katsunori
|
r33052 | |||
for objname, loadermod, loadername in extraloaders: | ||||
extraobj = getattr(module, objname, None) | ||||
if extraobj is not None: | ||||
getattr(loadermod, loadername)(ui, name, extraobj) | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r24065 | def afterloaded(extension, callback): | ||
Augie Fackler
|
r46554 | """Run the specified function after a named extension is loaded. | ||
Gregory Szorc
|
r24065 | |||
If the named extension is already loaded, the callback will be called | ||||
immediately. | ||||
If the named extension never loads, the callback will be called after | ||||
all extensions have been loaded. | ||||
The callback receives the named argument ``loaded``, which is a boolean | ||||
indicating whether the dependent extension actually loaded. | ||||
Augie Fackler
|
r46554 | """ | ||
Gregory Szorc
|
r24065 | |||
if extension in _extensions: | ||||
Adam Simpkins
|
r33014 | # Report loaded as False if the extension is disabled | ||
Augie Fackler
|
r43346 | loaded = _extensions[extension] is not None | ||
Adam Simpkins
|
r33014 | callback(loaded=loaded) | ||
Gregory Szorc
|
r24065 | else: | ||
_aftercallbacks.setdefault(extension, []).append(callback) | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r40760 | def populateui(ui): | ||
"""Run extension hooks on the given ui to populate additional members, | ||||
extend the class dynamically, etc. | ||||
This will be called after the configuration is loaded, and/or extensions | ||||
are loaded. In general, it's once per ui instance, but in command-server | ||||
and hgweb, this may be called more than once with the same ui. | ||||
""" | ||||
for name, mod in extensions(ui): | ||||
hook = getattr(mod, 'uipopulate', None) | ||||
if not hook: | ||||
continue | ||||
try: | ||||
hook(ui) | ||||
except Exception as inst: | ||||
ui.traceback(force=True) | ||||
Augie Fackler
|
r43346 | ui.warn( | ||
Augie Fackler
|
r43347 | _(b'*** failed to populate ui by extension %s: %s\n') | ||
Augie Fackler
|
r43346 | % (name, stringutil.forcebytestr(inst)) | ||
) | ||||
Yuya Nishihara
|
r40760 | |||
Eric Sumner
|
r24734 | def bind(func, *args): | ||
Augie Fackler
|
r46554 | """Partial function application | ||
Eric Sumner
|
r24734 | |||
Augie Fackler
|
r46554 | Returns a new function that is the partial application of args and kwargs | ||
to func. For example, | ||||
Eric Sumner
|
r24734 | |||
Augie Fackler
|
r46554 | f(1, 2, bar=3) === bind(f, 1)(2, bar=3)""" | ||
Eric Sumner
|
r24734 | assert callable(func) | ||
Augie Fackler
|
r43346 | |||
Eric Sumner
|
r24734 | def closure(*a, **kw): | ||
return func(*(args + a), **kw) | ||||
Augie Fackler
|
r43346 | |||
Eric Sumner
|
r24734 | return closure | ||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r29763 | def _updatewrapper(wrap, origfn, unboundwrapper): | ||
'''Copy and add some useful attributes to wrapper''' | ||||
Yuya Nishihara
|
r34130 | try: | ||
wrap.__name__ = origfn.__name__ | ||||
except AttributeError: | ||||
pass | ||||
Yuya Nishihara
|
r28310 | wrap.__module__ = getattr(origfn, '__module__') | ||
wrap.__doc__ = getattr(origfn, '__doc__') | ||||
Yuya Nishihara
|
r28312 | wrap.__dict__.update(getattr(origfn, '__dict__', {})) | ||
Jun Wu
|
r29763 | wrap._origfunc = origfn | ||
wrap._unboundwrapper = unboundwrapper | ||||
Yuya Nishihara
|
r28310 | |||
Augie Fackler
|
r43346 | |||
Ryan McElroy
|
r24124 | def wrapcommand(table, command, wrapper, synopsis=None, docstring=None): | ||
Dan Villiom Podlaski Christiansen
|
r11519 | '''Wrap the command named `command' in table | ||
Replace command in the command table with wrapper. The wrapped command will | ||||
be inserted into the command table specified by the table argument. | ||||
The wrapper will be called like | ||||
wrapper(orig, *args, **kwargs) | ||||
where orig is the original (wrapped) function, and *args, **kwargs | ||||
are the arguments passed to it. | ||||
Ryan McElroy
|
r24124 | |||
Optionally append to the command synopsis and docstring, used for help. | ||||
For example, if your extension wraps the ``bookmarks`` command to add the | ||||
flags ``--remote`` and ``--all`` you might call this function like so: | ||||
synopsis = ' [-a] [--remote]' | ||||
docstring = """ | ||||
The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``) | ||||
flags to the bookmarks command. Either flag will show the remote bookmarks | ||||
Mads Kiilerich
|
r26781 | known to the repository; ``--remote`` will also suppress the output of the | ||
Ryan McElroy
|
r24124 | local bookmarks. | ||
""" | ||||
extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks, | ||||
synopsis, docstring) | ||||
Dan Villiom Podlaski Christiansen
|
r11519 | ''' | ||
Augie Fackler
|
r21795 | assert callable(wrapper) | ||
Matt Mackall
|
r7215 | aliases, entry = cmdutil.findcmd(command, table) | ||
Gregory Szorc
|
r49768 | for alias, e in table.items(): | ||
Matt Mackall
|
r7215 | if e is entry: | ||
key = alias | ||||
break | ||||
origfn = entry[0] | ||||
Augie Fackler
|
r43346 | wrap = functools.partial( | ||
util.checksignature(wrapper), util.checksignature(origfn) | ||||
) | ||||
Jun Wu
|
r29763 | _updatewrapper(wrap, origfn, wrapper) | ||
Ryan McElroy
|
r24124 | if docstring is not None: | ||
Yuya Nishihara
|
r28310 | wrap.__doc__ += docstring | ||
Ryan McElroy
|
r24124 | |||
Matt Mackall
|
r7215 | newentry = list(entry) | ||
newentry[0] = wrap | ||||
Ryan McElroy
|
r24124 | if synopsis is not None: | ||
newentry[2] += synopsis | ||||
Matt Mackall
|
r7215 | table[key] = tuple(newentry) | ||
return entry | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r32722 | def wrapfilecache(cls, propname, wrapper): | ||
"""Wraps a filecache property. | ||||
These can't be wrapped using the normal wrapfunction. | ||||
""" | ||||
Augie Fackler
|
r33835 | propname = pycompat.sysstr(propname) | ||
Augie Fackler
|
r32722 | assert callable(wrapper) | ||
for currcls in cls.__mro__: | ||||
if propname in currcls.__dict__: | ||||
origfn = currcls.__dict__[propname].func | ||||
assert callable(origfn) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r32722 | def wrap(*args, **kwargs): | ||
return wrapper(origfn, *args, **kwargs) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r32722 | currcls.__dict__[propname].func = wrap | ||
break | ||||
if currcls is object: | ||||
Augie Fackler
|
r43809 | raise AttributeError("type '%s' has no property '%s'" % (cls, propname)) | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r32722 | |||
Gregory Szorc
|
r49801 | class wrappedfunction: | ||
Martin von Zweigbergk
|
r34016 | '''context manager for temporarily wrapping a function''' | ||
def __init__(self, container, funcname, wrapper): | ||||
assert callable(wrapper) | ||||
r51693 | if not isinstance(funcname, str): | |||
r52032 | msg = b"wrappedfunction target name should be `str`, not `bytes`" | |||
raise TypeError(msg) | ||||
Martin von Zweigbergk
|
r34016 | self._container = container | ||
self._funcname = funcname | ||||
self._wrapper = wrapper | ||||
def __enter__(self): | ||||
wrapfunction(self._container, self._funcname, self._wrapper) | ||||
def __exit__(self, exctype, excvalue, traceback): | ||||
unwrapfunction(self._container, self._funcname, self._wrapper) | ||||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r7215 | def wrapfunction(container, funcname, wrapper): | ||
Augie Fackler
|
r46554 | """Wrap the function named funcname in container | ||
Greg Ward
|
r11402 | |||
Dan Villiom Podlaski Christiansen
|
r11520 | Replace the funcname member in the given container with the specified | ||
wrapper. The container is typically a module, class, or instance. | ||||
Greg Ward
|
r11402 | |||
The wrapper will be called like | ||||
wrapper(orig, *args, **kwargs) | ||||
where orig is the original (wrapped) function, and *args, **kwargs | ||||
are the arguments passed to it. | ||||
Wrapping methods of the repository object is not recommended since | ||||
it conflicts with extensions that extend the repository by | ||||
subclassing. All extensions that need to extend methods of | ||||
localrepository should use this subclassing trick: namely, | ||||
reposetup() should look like | ||||
def reposetup(ui, repo): | ||||
class myrepo(repo.__class__): | ||||
def whatever(self, *args, **kwargs): | ||||
[...extension stuff...] | ||||
super(myrepo, self).whatever(*args, **kwargs) | ||||
[...extension stuff...] | ||||
repo.__class__ = myrepo | ||||
In general, combining wrapfunction() with subclassing does not | ||||
work. Since you cannot control what other extensions are loaded by | ||||
your end users, you should play nicely with others by using the | ||||
subclass trick. | ||||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r21795 | assert callable(wrapper) | ||
Matt Mackall
|
r7215 | |||
r51694 | if not isinstance(funcname, str): | |||
r52033 | msg = b"wrapfunction target name should be `str`, not `bytes`" | |||
raise TypeError(msg) | ||||
r51694 | ||||
Matt Mackall
|
r7215 | origfn = getattr(container, funcname) | ||
Augie Fackler
|
r21795 | assert callable(origfn) | ||
Jun Wu
|
r34089 | if inspect.ismodule(container): | ||
# origfn is not an instance or class method. "partial" can be used. | ||||
# "partial" won't insert a frame in traceback. | ||||
wrap = functools.partial(wrapper, origfn) | ||||
else: | ||||
# "partial" cannot be safely used. Emulate its effect by using "bind". | ||||
# The downside is one more frame in traceback. | ||||
wrap = bind(wrapper, origfn) | ||||
Jun Wu
|
r29763 | _updatewrapper(wrap, origfn, wrapper) | ||
Yuya Nishihara
|
r28311 | setattr(container, funcname, wrap) | ||
Matt Mackall
|
r7215 | return origfn | ||
Cédric Duval
|
r8871 | |||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r29765 | def unwrapfunction(container, funcname, wrapper=None): | ||
Augie Fackler
|
r46554 | """undo wrapfunction | ||
Jun Wu
|
r29765 | |||
If wrappers is None, undo the last wrap. Otherwise removes the wrapper | ||||
from the chain of wrappers. | ||||
Return the removed wrapper. | ||||
Raise IndexError if wrapper is None and nothing to unwrap; ValueError if | ||||
wrapper is not None but is not found in the wrapper chain. | ||||
Augie Fackler
|
r46554 | """ | ||
Jun Wu
|
r29765 | chain = getwrapperchain(container, funcname) | ||
origfn = chain.pop() | ||||
if wrapper is None: | ||||
wrapper = chain[0] | ||||
chain.remove(wrapper) | ||||
setattr(container, funcname, origfn) | ||||
for w in reversed(chain): | ||||
wrapfunction(container, funcname, w) | ||||
return wrapper | ||||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r29764 | def getwrapperchain(container, funcname): | ||
Augie Fackler
|
r46554 | """get a chain of wrappers of a function | ||
Jun Wu
|
r29764 | |||
Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc] | ||||
The wrapper functions are the ones passed to wrapfunction, whose first | ||||
argument is origfunc. | ||||
Augie Fackler
|
r46554 | """ | ||
Jun Wu
|
r29764 | result = [] | ||
fn = getattr(container, funcname) | ||||
while fn: | ||||
assert callable(fn) | ||||
result.append(getattr(fn, '_unboundwrapper', fn)) | ||||
fn = getattr(fn, '_origfunc', None) | ||||
return result | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r38181 | def _disabledpaths(): | ||
'''find paths of disabled extensions. returns a dict of {name: path}''' | ||||
Dirkjan Ochtman
|
r8872 | import hgext | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r50671 | exts = {} | ||
Martin von Zweigbergk
|
r45665 | # The hgext might not have a __file__ attribute (e.g. in PyOxidizer) and | ||
# it might not be on a filesystem even if it does. | ||||
r51821 | if hasattr(hgext, '__file__'): | |||
Martin von Zweigbergk
|
r45665 | extpath = os.path.dirname( | ||
r48425 | util.abspath(pycompat.fsencode(hgext.__file__)) | |||
Martin von Zweigbergk
|
r45665 | ) | ||
try: | ||||
files = os.listdir(extpath) | ||||
except OSError: | ||||
Matt Harbison
|
r50671 | pass | ||
else: | ||||
for e in files: | ||||
if e.endswith(b'.py'): | ||||
name = e.rsplit(b'.', 1)[0] | ||||
path = os.path.join(extpath, e) | ||||
else: | ||||
name = e | ||||
path = os.path.join(extpath, e, b'__init__.py') | ||||
if not os.path.exists(path): | ||||
continue | ||||
if name in exts or name in _order or name == b'__init__': | ||||
continue | ||||
exts[name] = path | ||||
Cédric Duval
|
r8964 | |||
Gregory Szorc
|
r49768 | for name, path in _disabledextensions.items(): | ||
Martin von Zweigbergk
|
r33327 | # If no path was provided for a disabled extension (e.g. "color=!"), | ||
# don't replace the path we already found by the scan above. | ||||
if path: | ||||
exts[name] = path | ||||
Brodie Rao
|
r10363 | return exts | ||
Dirkjan Ochtman
|
r8872 | |||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r14317 | def _moduledoc(file): | ||
Augie Fackler
|
r46554 | """return the top-level python documentation for the given file | ||
Matt Mackall
|
r14317 | |||
Loosely inspired by pydoc.source_synopsis(), but rewritten to | ||||
handle triple quotes and to return the whole text instead of just | ||||
Augie Fackler
|
r46554 | the synopsis""" | ||
Matt Mackall
|
r14317 | result = [] | ||
line = file.readline() | ||||
Augie Fackler
|
r43347 | while line[:1] == b'#' or not line.strip(): | ||
Matt Mackall
|
r14317 | line = file.readline() | ||
if not line: | ||||
break | ||||
start = line[:3] | ||||
Augie Fackler
|
r43347 | if start == b'"""' or start == b"'''": | ||
Matt Mackall
|
r14317 | line = line[3:] | ||
while line: | ||||
if line.rstrip().endswith(start): | ||||
line = line.split(start)[0] | ||||
if line: | ||||
result.append(line) | ||||
break | ||||
elif not line: | ||||
Augie Fackler
|
r43346 | return None # unmatched delimiter | ||
Matt Mackall
|
r14317 | result.append(line) | ||
line = file.readline() | ||||
else: | ||||
return None | ||||
Augie Fackler
|
r43347 | return b''.join(result) | ||
Matt Mackall
|
r14317 | |||
Augie Fackler
|
r43346 | |||
Brodie Rao
|
r10363 | def _disabledhelp(path): | ||
'''retrieve help synopsis of a disabled extension (without importing)''' | ||||
try: | ||||
Matt Harbison
|
r53264 | with open(path, 'rb') as src: | ||
Yuya Nishihara
|
r38363 | doc = _moduledoc(src) | ||
Brodie Rao
|
r10363 | except IOError: | ||
return | ||||
Augie Fackler
|
r43346 | if doc: # extracting localized synopsis | ||
Pulkit Goyal
|
r30306 | return gettext(doc) | ||
Brodie Rao
|
r10363 | else: | ||
Augie Fackler
|
r43347 | return _(b'(no help text available)') | ||
Brodie Rao
|
r10363 | |||
Augie Fackler
|
r43346 | |||
Brodie Rao
|
r10363 | def disabled(): | ||
Yuya Nishihara
|
r14530 | '''find disabled extensions from hgext. returns a dict of {name: desc}''' | ||
Yuya Nishihara
|
r14539 | try: | ||
Augie Fackler
|
r44104 | from hgext import __index__ # pytype: disable=import-error | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r44937 | return { | ||
name: gettext(desc) | ||||
Gregory Szorc
|
r49768 | for name, desc in __index__.docs.items() | ||
Augie Fackler
|
r43346 | if name not in _order | ||
Augie Fackler
|
r44937 | } | ||
Thomas Arendsen Hein
|
r21229 | except (ImportError, AttributeError): | ||
Yuya Nishihara
|
r14539 | pass | ||
Brodie Rao
|
r10363 | paths = _disabledpaths() | ||
if not paths: | ||||
Augie Fackler
|
r16709 | return {} | ||
Brodie Rao
|
r10363 | |||
exts = {} | ||||
Gregory Szorc
|
r49768 | for name, path in paths.items(): | ||
Brodie Rao
|
r10363 | doc = _disabledhelp(path) | ||
Matt Harbison
|
r46710 | if doc and name != b'__index__': | ||
Martin von Zweigbergk
|
r49890 | exts[name] = stringutil.firstline(doc) | ||
Cédric Duval
|
r8871 | |||
Matt Mackall
|
r14316 | return exts | ||
Cédric Duval
|
r8871 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r45145 | def disabled_help(name): | ||
"""Obtain the full help text for a disabled extension, or None.""" | ||||
Brodie Rao
|
r10364 | paths = _disabledpaths() | ||
if name in paths: | ||||
return _disabledhelp(paths[name]) | ||||
Matt Harbison
|
r50672 | else: | ||
try: | ||||
import hgext | ||||
from hgext import __index__ # pytype: disable=import-error | ||||
# The extensions are filesystem based, so either an error occurred | ||||
# or all are enabled. | ||||
r51821 | if hasattr(hgext, '__file__'): | |||
Matt Harbison
|
r50672 | return | ||
if name in _order: # enabled | ||||
return | ||||
else: | ||||
return gettext(__index__.docs.get(name)) | ||||
except (ImportError, AttributeError): | ||||
pass | ||||
Brodie Rao
|
r10364 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r38180 | def _walkcommand(node): | ||
"""Scan @command() decorators in the tree starting at node""" | ||||
todo = collections.deque([node]) | ||||
while todo: | ||||
node = todo.popleft() | ||||
if not isinstance(node, ast.FunctionDef): | ||||
todo.extend(ast.iter_child_nodes(node)) | ||||
continue | ||||
for d in node.decorator_list: | ||||
if not isinstance(d, ast.Call): | ||||
continue | ||||
if not isinstance(d.func, ast.Name): | ||||
continue | ||||
Augie Fackler
|
r43906 | if d.func.id != 'command': | ||
Yuya Nishihara
|
r38180 | continue | ||
yield d | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r38180 | def _disabledcmdtable(path): | ||
"""Construct a dummy command table without loading the extension module | ||||
This may raise IOError or SyntaxError. | ||||
""" | ||||
Matt Harbison
|
r53264 | with open(path, 'rb') as src: | ||
Yuya Nishihara
|
r38180 | root = ast.parse(src.read(), path) | ||
cmdtable = {} | ||||
Mads Kiilerich
|
r51648 | |||
# Python 3.12 started removing Bytes and Str and deprecate harder | ||||
use_constant = 'Bytes' not in vars(ast) | ||||
Yuya Nishihara
|
r38180 | for node in _walkcommand(root): | ||
if not node.args: | ||||
continue | ||||
a = node.args[0] | ||||
Mads Kiilerich
|
r51648 | if use_constant: # Valid since Python 3.8 | ||
if isinstance(a, ast.Constant): | ||||
if isinstance(a.value, str): | ||||
name = pycompat.sysbytes(a.value) | ||||
elif isinstance(a.value, bytes): | ||||
name = a.value | ||||
else: | ||||
continue | ||||
else: | ||||
continue | ||||
else: # Valid until 3.11 | ||||
if isinstance(a, ast.Str): | ||||
name = pycompat.sysbytes(a.s) | ||||
elif isinstance(a, ast.Bytes): | ||||
name = a.s | ||||
else: | ||||
continue | ||||
Yuya Nishihara
|
r38180 | cmdtable[name] = (None, [], b'') | ||
return cmdtable | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r37995 | def _finddisabledcmd(ui, cmd, name, path, strict): | ||
try: | ||||
Yuya Nishihara
|
r38180 | cmdtable = _disabledcmdtable(path) | ||
except (IOError, SyntaxError): | ||||
Yuya Nishihara
|
r37995 | return | ||
try: | ||||
Yuya Nishihara
|
r38180 | aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict) | ||
Yuya Nishihara
|
r37995 | except (error.AmbiguousCommand, error.UnknownCommand): | ||
return | ||||
for c in aliases: | ||||
if c.startswith(cmd): | ||||
cmd = c | ||||
break | ||||
else: | ||||
cmd = aliases[0] | ||||
Yuya Nishihara
|
r38180 | doc = _disabledhelp(path) | ||
Yuya Nishihara
|
r37996 | return (cmd, name, doc) | ||
Yuya Nishihara
|
r37995 | |||
Augie Fackler
|
r43346 | |||
Mads Kiilerich
|
r13191 | def disabledcmd(ui, cmd, strict=False): | ||
Augie Fackler
|
r46554 | """find cmd from disabled extensions without importing. | ||
returns (cmdname, extname, doc)""" | ||||
Brodie Rao
|
r10364 | |||
Yuya Nishihara
|
r38180 | paths = _disabledpaths() | ||
Brodie Rao
|
r10364 | if not paths: | ||
raise error.UnknownCommand(cmd) | ||||
Martin Geisler
|
r16667 | ext = None | ||
Brodie Rao
|
r10364 | # first, search for an extension with the same name as the command | ||
path = paths.pop(cmd, None) | ||||
if path: | ||||
Yuya Nishihara
|
r37995 | ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict) | ||
Martin Geisler
|
r16667 | if not ext: | ||
# otherwise, interrogate each extension until there's a match | ||||
Gregory Szorc
|
r49768 | for name, path in paths.items(): | ||
Yuya Nishihara
|
r37995 | ext = _finddisabledcmd(ui, cmd, name, path, strict=strict) | ||
Martin Geisler
|
r16667 | if ext: | ||
break | ||||
Yuya Nishihara
|
r37994 | if ext: | ||
Martin Geisler
|
r16667 | return ext | ||
Brodie Rao
|
r10364 | |||
raise error.UnknownCommand(cmd) | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r19769 | def enabled(shortname=True): | ||
Yuya Nishihara
|
r14530 | '''return a dict of {name: desc} of extensions''' | ||
Cédric Duval
|
r8871 | exts = {} | ||
for ename, ext in extensions(): | ||||
Augie Fackler
|
r43347 | doc = gettext(ext.__doc__) or _(b'(no help text available)') | ||
Matt Harbison
|
r47517 | assert doc is not None # help pytype | ||
FUJIWARA Katsunori
|
r19769 | if shortname: | ||
Augie Fackler
|
r43347 | ename = ename.split(b'.')[-1] | ||
Martin von Zweigbergk
|
r49890 | exts[ename] = stringutil.firstline(doc).strip() | ||
Cédric Duval
|
r8871 | |||
Matt Mackall
|
r14316 | return exts | ||
anatoly techtonik
|
r21848 | |||
Augie Fackler
|
r43346 | |||
Jun Wu
|
r28155 | def notloaded(): | ||
'''return short names of extensions that failed to load''' | ||||
Gregory Szorc
|
r49768 | return [name for name, mod in _extensions.items() if mod is None] | ||
Jun Wu
|
r28155 | |||
Augie Fackler
|
r43346 | |||
anatoly techtonik
|
r21848 | def moduleversion(module): | ||
'''return version information from given module as a string''' | ||||
r51821 | if hasattr(module, 'getversion') and callable(module.getversion): | |||
Matt Harbison
|
r47829 | try: | ||
version = module.getversion() | ||||
except Exception: | ||||
version = b'unknown' | ||||
r51821 | elif hasattr(module, '__version__'): | |||
anatoly techtonik
|
r21848 | version = module.__version__ | ||
else: | ||||
Augie Fackler
|
r43347 | version = b'' | ||
anatoly techtonik
|
r21848 | if isinstance(version, (list, tuple)): | ||
Augie Fackler
|
r43347 | version = b'.'.join(pycompat.bytestr(o) for o in version) | ||
Matt Harbison
|
r46629 | else: | ||
# version data should be bytes, but not all extensions are ported | ||||
# to py3. | ||||
version = stringutil.forcebytestr(version) | ||||
anatoly techtonik
|
r21848 | return version | ||
liscju
|
r27990 | |||
Augie Fackler
|
r43346 | |||
liscju
|
r27990 | def ismoduleinternal(module): | ||
exttestedwith = getattr(module, 'testedwith', None) | ||||
Augie Fackler
|
r43347 | return exttestedwith == b"ships-with-hg-core" | ||