##// END OF EJS Templates
extensions: trace the total time of running all extsetup callbacks...
Boris Feld -
r39545:3a86f7eb default
parent child Browse files
Show More
@@ -1,810 +1,812 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import ast
10 import ast
11 import collections
11 import collections
12 import functools
12 import functools
13 import imp
13 import imp
14 import inspect
14 import inspect
15 import os
15 import os
16
16
17 from .i18n import (
17 from .i18n import (
18 _,
18 _,
19 gettext,
19 gettext,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 cmdutil,
23 cmdutil,
24 configitems,
24 configitems,
25 error,
25 error,
26 pycompat,
26 pycompat,
27 util,
27 util,
28 )
28 )
29
29
30 from .utils import (
30 from .utils import (
31 stringutil,
31 stringutil,
32 )
32 )
33
33
34 _extensions = {}
34 _extensions = {}
35 _disabledextensions = {}
35 _disabledextensions = {}
36 _aftercallbacks = {}
36 _aftercallbacks = {}
37 _order = []
37 _order = []
38 _builtin = {
38 _builtin = {
39 'hbisect',
39 'hbisect',
40 'bookmarks',
40 'bookmarks',
41 'color',
41 'color',
42 'parentrevspec',
42 'parentrevspec',
43 'progress',
43 'progress',
44 'interhg',
44 'interhg',
45 'inotify',
45 'inotify',
46 'hgcia'
46 'hgcia'
47 }
47 }
48
48
49 def extensions(ui=None):
49 def extensions(ui=None):
50 if ui:
50 if ui:
51 def enabled(name):
51 def enabled(name):
52 for format in ['%s', 'hgext.%s']:
52 for format in ['%s', 'hgext.%s']:
53 conf = ui.config('extensions', format % name)
53 conf = ui.config('extensions', format % name)
54 if conf is not None and not conf.startswith('!'):
54 if conf is not None and not conf.startswith('!'):
55 return True
55 return True
56 else:
56 else:
57 enabled = lambda name: True
57 enabled = lambda name: True
58 for name in _order:
58 for name in _order:
59 module = _extensions[name]
59 module = _extensions[name]
60 if module and enabled(name):
60 if module and enabled(name):
61 yield name, module
61 yield name, module
62
62
63 def find(name):
63 def find(name):
64 '''return module with given extension name'''
64 '''return module with given extension name'''
65 mod = None
65 mod = None
66 try:
66 try:
67 mod = _extensions[name]
67 mod = _extensions[name]
68 except KeyError:
68 except KeyError:
69 for k, v in _extensions.iteritems():
69 for k, v in _extensions.iteritems():
70 if k.endswith('.' + name) or k.endswith('/' + name):
70 if k.endswith('.' + name) or k.endswith('/' + name):
71 mod = v
71 mod = v
72 break
72 break
73 if not mod:
73 if not mod:
74 raise KeyError(name)
74 raise KeyError(name)
75 return mod
75 return mod
76
76
77 def loadpath(path, module_name):
77 def loadpath(path, module_name):
78 module_name = module_name.replace('.', '_')
78 module_name = module_name.replace('.', '_')
79 path = util.normpath(util.expandpath(path))
79 path = util.normpath(util.expandpath(path))
80 module_name = pycompat.fsdecode(module_name)
80 module_name = pycompat.fsdecode(module_name)
81 path = pycompat.fsdecode(path)
81 path = pycompat.fsdecode(path)
82 if os.path.isdir(path):
82 if os.path.isdir(path):
83 # module/__init__.py style
83 # module/__init__.py style
84 d, f = os.path.split(path)
84 d, f = os.path.split(path)
85 fd, fpath, desc = imp.find_module(f, [d])
85 fd, fpath, desc = imp.find_module(f, [d])
86 return imp.load_module(module_name, fd, fpath, desc)
86 return imp.load_module(module_name, fd, fpath, desc)
87 else:
87 else:
88 try:
88 try:
89 return imp.load_source(module_name, path)
89 return imp.load_source(module_name, path)
90 except IOError as exc:
90 except IOError as exc:
91 if not exc.filename:
91 if not exc.filename:
92 exc.filename = path # python does not fill this
92 exc.filename = path # python does not fill this
93 raise
93 raise
94
94
95 def _importh(name):
95 def _importh(name):
96 """import and return the <name> module"""
96 """import and return the <name> module"""
97 mod = __import__(pycompat.sysstr(name))
97 mod = __import__(pycompat.sysstr(name))
98 components = name.split('.')
98 components = name.split('.')
99 for comp in components[1:]:
99 for comp in components[1:]:
100 mod = getattr(mod, comp)
100 mod = getattr(mod, comp)
101 return mod
101 return mod
102
102
103 def _importext(name, path=None, reportfunc=None):
103 def _importext(name, path=None, reportfunc=None):
104 if path:
104 if path:
105 # the module will be loaded in sys.modules
105 # the module will be loaded in sys.modules
106 # choose an unique name so that it doesn't
106 # choose an unique name so that it doesn't
107 # conflicts with other modules
107 # conflicts with other modules
108 mod = loadpath(path, 'hgext.%s' % name)
108 mod = loadpath(path, 'hgext.%s' % name)
109 else:
109 else:
110 try:
110 try:
111 mod = _importh("hgext.%s" % name)
111 mod = _importh("hgext.%s" % name)
112 except ImportError as err:
112 except ImportError as err:
113 if reportfunc:
113 if reportfunc:
114 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
114 reportfunc(err, "hgext.%s" % name, "hgext3rd.%s" % name)
115 try:
115 try:
116 mod = _importh("hgext3rd.%s" % name)
116 mod = _importh("hgext3rd.%s" % name)
117 except ImportError as err:
117 except ImportError as err:
118 if reportfunc:
118 if reportfunc:
119 reportfunc(err, "hgext3rd.%s" % name, name)
119 reportfunc(err, "hgext3rd.%s" % name, name)
120 mod = _importh(name)
120 mod = _importh(name)
121 return mod
121 return mod
122
122
123 def _reportimporterror(ui, err, failed, next):
123 def _reportimporterror(ui, err, failed, next):
124 # note: this ui.debug happens before --debug is processed,
124 # note: this ui.debug happens before --debug is processed,
125 # Use --config ui.debug=1 to see them.
125 # Use --config ui.debug=1 to see them.
126 if ui.configbool('devel', 'debug.extensions'):
126 if ui.configbool('devel', 'debug.extensions'):
127 ui.debug('debug.extensions: - could not import %s (%s): trying %s\n'
127 ui.debug('debug.extensions: - could not import %s (%s): trying %s\n'
128 % (failed, stringutil.forcebytestr(err), next))
128 % (failed, stringutil.forcebytestr(err), next))
129 if ui.debugflag:
129 if ui.debugflag:
130 ui.traceback()
130 ui.traceback()
131
131
132 def _rejectunicode(name, xs):
132 def _rejectunicode(name, xs):
133 if isinstance(xs, (list, set, tuple)):
133 if isinstance(xs, (list, set, tuple)):
134 for x in xs:
134 for x in xs:
135 _rejectunicode(name, x)
135 _rejectunicode(name, x)
136 elif isinstance(xs, dict):
136 elif isinstance(xs, dict):
137 for k, v in xs.items():
137 for k, v in xs.items():
138 _rejectunicode(name, k)
138 _rejectunicode(name, k)
139 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
139 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
140 elif isinstance(xs, type(u'')):
140 elif isinstance(xs, type(u'')):
141 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
141 raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name),
142 hint="use b'' to make it byte string")
142 hint="use b'' to make it byte string")
143
143
144 # attributes set by registrar.command
144 # attributes set by registrar.command
145 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
145 _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo')
146
146
147 def _validatecmdtable(ui, cmdtable):
147 def _validatecmdtable(ui, cmdtable):
148 """Check if extension commands have required attributes"""
148 """Check if extension commands have required attributes"""
149 for c, e in cmdtable.iteritems():
149 for c, e in cmdtable.iteritems():
150 f = e[0]
150 f = e[0]
151 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
151 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
152 if not missing:
152 if not missing:
153 continue
153 continue
154 raise error.ProgrammingError(
154 raise error.ProgrammingError(
155 'missing attributes: %s' % ', '.join(missing),
155 'missing attributes: %s' % ', '.join(missing),
156 hint="use @command decorator to register '%s'" % c)
156 hint="use @command decorator to register '%s'" % c)
157
157
158 def _validatetables(ui, mod):
158 def _validatetables(ui, mod):
159 """Sanity check for loadable tables provided by extension module"""
159 """Sanity check for loadable tables provided by extension module"""
160 for t in ['cmdtable', 'colortable', 'configtable']:
160 for t in ['cmdtable', 'colortable', 'configtable']:
161 _rejectunicode(t, getattr(mod, t, {}))
161 _rejectunicode(t, getattr(mod, t, {}))
162 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
162 for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate',
163 'templatefilter', 'templatefunc', 'templatekeyword']:
163 'templatefilter', 'templatefunc', 'templatekeyword']:
164 o = getattr(mod, t, None)
164 o = getattr(mod, t, None)
165 if o:
165 if o:
166 _rejectunicode(t, o._table)
166 _rejectunicode(t, o._table)
167 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
167 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
168
168
169 def load(ui, name, path, log=lambda *a: None):
169 def load(ui, name, path, log=lambda *a: None):
170 if name.startswith('hgext.') or name.startswith('hgext/'):
170 if name.startswith('hgext.') or name.startswith('hgext/'):
171 shortname = name[6:]
171 shortname = name[6:]
172 else:
172 else:
173 shortname = name
173 shortname = name
174 if shortname in _builtin:
174 if shortname in _builtin:
175 return None
175 return None
176 if shortname in _extensions:
176 if shortname in _extensions:
177 return _extensions[shortname]
177 return _extensions[shortname]
178 log(' - loading extension: %r\n', shortname)
178 log(' - loading extension: %r\n', shortname)
179 _extensions[shortname] = None
179 _extensions[shortname] = None
180 with util.timedcm('load extension %r', shortname) as stats:
180 with util.timedcm('load extension %r', shortname) as stats:
181 mod = _importext(name, path, bind(_reportimporterror, ui))
181 mod = _importext(name, path, bind(_reportimporterror, ui))
182 log(' > %r extension loaded in %s\n', shortname, stats)
182 log(' > %r extension loaded in %s\n', shortname, stats)
183
183
184 # Before we do anything with the extension, check against minimum stated
184 # Before we do anything with the extension, check against minimum stated
185 # compatibility. This gives extension authors a mechanism to have their
185 # compatibility. This gives extension authors a mechanism to have their
186 # extensions short circuit when loaded with a known incompatible version
186 # extensions short circuit when loaded with a known incompatible version
187 # of Mercurial.
187 # of Mercurial.
188 minver = getattr(mod, 'minimumhgversion', None)
188 minver = getattr(mod, 'minimumhgversion', None)
189 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
189 if minver and util.versiontuple(minver, 2) > util.versiontuple(n=2):
190 ui.warn(_('(third party extension %s requires version %s or newer '
190 ui.warn(_('(third party extension %s requires version %s or newer '
191 'of Mercurial; disabling)\n') % (shortname, minver))
191 'of Mercurial; disabling)\n') % (shortname, minver))
192 return
192 return
193 log(' - validating extension tables: %r\n', shortname)
193 log(' - validating extension tables: %r\n', shortname)
194 _validatetables(ui, mod)
194 _validatetables(ui, mod)
195
195
196 _extensions[shortname] = mod
196 _extensions[shortname] = mod
197 _order.append(shortname)
197 _order.append(shortname)
198 log(' - invoking registered callbacks: %r\n', shortname)
198 log(' - invoking registered callbacks: %r\n', shortname)
199 with util.timedcm('callbacks extension %r', shortname) as stats:
199 with util.timedcm('callbacks extension %r', shortname) as stats:
200 for fn in _aftercallbacks.get(shortname, []):
200 for fn in _aftercallbacks.get(shortname, []):
201 fn(loaded=True)
201 fn(loaded=True)
202 log(' > callbacks completed in %s\n', stats)
202 log(' > callbacks completed in %s\n', stats)
203 return mod
203 return mod
204
204
205 def _runuisetup(name, ui):
205 def _runuisetup(name, ui):
206 uisetup = getattr(_extensions[name], 'uisetup', None)
206 uisetup = getattr(_extensions[name], 'uisetup', None)
207 if uisetup:
207 if uisetup:
208 try:
208 try:
209 uisetup(ui)
209 uisetup(ui)
210 except Exception as inst:
210 except Exception as inst:
211 ui.traceback(force=True)
211 ui.traceback(force=True)
212 msg = stringutil.forcebytestr(inst)
212 msg = stringutil.forcebytestr(inst)
213 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
213 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
214 return False
214 return False
215 return True
215 return True
216
216
217 def _runextsetup(name, ui):
217 def _runextsetup(name, ui):
218 extsetup = getattr(_extensions[name], 'extsetup', None)
218 extsetup = getattr(_extensions[name], 'extsetup', None)
219 if extsetup:
219 if extsetup:
220 try:
220 try:
221 try:
221 try:
222 extsetup(ui)
222 extsetup(ui)
223 except TypeError:
223 except TypeError:
224 if pycompat.getargspec(extsetup).args:
224 if pycompat.getargspec(extsetup).args:
225 raise
225 raise
226 extsetup() # old extsetup with no ui argument
226 extsetup() # old extsetup with no ui argument
227 except Exception as inst:
227 except Exception as inst:
228 ui.traceback(force=True)
228 ui.traceback(force=True)
229 msg = stringutil.forcebytestr(inst)
229 msg = stringutil.forcebytestr(inst)
230 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
230 ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
231 return False
231 return False
232 return True
232 return True
233
233
234 def loadall(ui, whitelist=None):
234 def loadall(ui, whitelist=None):
235 if ui.configbool('devel', 'debug.extensions'):
235 if ui.configbool('devel', 'debug.extensions'):
236 log = lambda msg, *values: ui.debug('debug.extensions: ',
236 log = lambda msg, *values: ui.debug('debug.extensions: ',
237 msg % values, label='debug.extensions')
237 msg % values, label='debug.extensions')
238 else:
238 else:
239 log = lambda *a, **kw: None
239 log = lambda *a, **kw: None
240 result = ui.configitems("extensions")
240 result = ui.configitems("extensions")
241 if whitelist is not None:
241 if whitelist is not None:
242 result = [(k, v) for (k, v) in result if k in whitelist]
242 result = [(k, v) for (k, v) in result if k in whitelist]
243 newindex = len(_order)
243 newindex = len(_order)
244 log('loading %sextensions\n', 'additional ' if newindex else '')
244 log('loading %sextensions\n', 'additional ' if newindex else '')
245 log('- processing %d entries\n', len(result))
245 log('- processing %d entries\n', len(result))
246 with util.timedcm('load all extensions') as stats:
246 with util.timedcm('load all extensions') as stats:
247 for (name, path) in result:
247 for (name, path) in result:
248 if path:
248 if path:
249 if path[0:1] == '!':
249 if path[0:1] == '!':
250 if name not in _disabledextensions:
250 if name not in _disabledextensions:
251 log(' - skipping disabled extension: %r\n', name)
251 log(' - skipping disabled extension: %r\n', name)
252 _disabledextensions[name] = path[1:]
252 _disabledextensions[name] = path[1:]
253 continue
253 continue
254 try:
254 try:
255 load(ui, name, path, log)
255 load(ui, name, path, log)
256 except Exception as inst:
256 except Exception as inst:
257 msg = stringutil.forcebytestr(inst)
257 msg = stringutil.forcebytestr(inst)
258 if path:
258 if path:
259 ui.warn(_("*** failed to import extension %s from %s: %s\n")
259 ui.warn(_("*** failed to import extension %s from %s: %s\n")
260 % (name, path, msg))
260 % (name, path, msg))
261 else:
261 else:
262 ui.warn(_("*** failed to import extension %s: %s\n")
262 ui.warn(_("*** failed to import extension %s: %s\n")
263 % (name, msg))
263 % (name, msg))
264 if isinstance(inst, error.Hint) and inst.hint:
264 if isinstance(inst, error.Hint) and inst.hint:
265 ui.warn(_("*** (%s)\n") % inst.hint)
265 ui.warn(_("*** (%s)\n") % inst.hint)
266 ui.traceback()
266 ui.traceback()
267
267
268 log('> loaded %d extensions, total time %s\n',
268 log('> loaded %d extensions, total time %s\n',
269 len(_order) - newindex, stats)
269 len(_order) - newindex, stats)
270 # list of (objname, loadermod, loadername) tuple:
270 # list of (objname, loadermod, loadername) tuple:
271 # - objname is the name of an object in extension module,
271 # - objname is the name of an object in extension module,
272 # from which extra information is loaded
272 # from which extra information is loaded
273 # - loadermod is the module where loader is placed
273 # - loadermod is the module where loader is placed
274 # - loadername is the name of the function,
274 # - loadername is the name of the function,
275 # which takes (ui, extensionname, extraobj) arguments
275 # which takes (ui, extensionname, extraobj) arguments
276 #
276 #
277 # This one is for the list of item that must be run before running any setup
277 # This one is for the list of item that must be run before running any setup
278 earlyextraloaders = [
278 earlyextraloaders = [
279 ('configtable', configitems, 'loadconfigtable'),
279 ('configtable', configitems, 'loadconfigtable'),
280 ]
280 ]
281
281
282 log('- loading configtable attributes\n')
282 log('- loading configtable attributes\n')
283 _loadextra(ui, newindex, earlyextraloaders)
283 _loadextra(ui, newindex, earlyextraloaders)
284
284
285 broken = set()
285 broken = set()
286 log('- executing uisetup hooks\n')
286 log('- executing uisetup hooks\n')
287 with util.timedcm('all uisetup') as alluisetupstats:
287 with util.timedcm('all uisetup') as alluisetupstats:
288 for name in _order[newindex:]:
288 for name in _order[newindex:]:
289 log(' - running uisetup for %r\n', name)
289 log(' - running uisetup for %r\n', name)
290 with util.timedcm('uisetup %r', name) as stats:
290 with util.timedcm('uisetup %r', name) as stats:
291 if not _runuisetup(name, ui):
291 if not _runuisetup(name, ui):
292 log(' - the %r extension uisetup failed\n', name)
292 log(' - the %r extension uisetup failed\n', name)
293 broken.add(name)
293 broken.add(name)
294 log(' > uisetup for %r took %s\n', name, stats)
294 log(' > uisetup for %r took %s\n', name, stats)
295 log('> all uisetup took %s\n', alluisetupstats)
295 log('> all uisetup took %s\n', alluisetupstats)
296
296
297 log('- executing extsetup hooks\n')
297 log('- executing extsetup hooks\n')
298 with util.timedcm('all extsetup') as allextetupstats:
298 for name in _order[newindex:]:
299 for name in _order[newindex:]:
299 if name in broken:
300 if name in broken:
300 continue
301 continue
301 log(' - running extsetup for %r\n', name)
302 log(' - running extsetup for %r\n', name)
302 with util.timedcm('extsetup %r', name) as stats:
303 with util.timedcm('extsetup %r', name) as stats:
303 if not _runextsetup(name, ui):
304 if not _runextsetup(name, ui):
304 log(' - the %r extension extsetup failed\n', name)
305 log(' - the %r extension extsetup failed\n', name)
305 broken.add(name)
306 broken.add(name)
306 log(' > extsetup for %r took %s\n', name, stats)
307 log(' > extsetup for %r took %s\n', name, stats)
308 log('> all extsetup took %s\n', allextetupstats)
307
309
308 for name in broken:
310 for name in broken:
309 log(' - disabling broken %r extension\n', name)
311 log(' - disabling broken %r extension\n', name)
310 _extensions[name] = None
312 _extensions[name] = None
311
313
312 # Call aftercallbacks that were never met.
314 # Call aftercallbacks that were never met.
313 log('- executing remaining aftercallbacks\n')
315 log('- executing remaining aftercallbacks\n')
314 with util.timedcm('aftercallbacks') as stats:
316 with util.timedcm('aftercallbacks') as stats:
315 for shortname in _aftercallbacks:
317 for shortname in _aftercallbacks:
316 if shortname in _extensions:
318 if shortname in _extensions:
317 continue
319 continue
318
320
319 for fn in _aftercallbacks[shortname]:
321 for fn in _aftercallbacks[shortname]:
320 log(' - extension %r not loaded, notify callbacks\n',
322 log(' - extension %r not loaded, notify callbacks\n',
321 shortname)
323 shortname)
322 fn(loaded=False)
324 fn(loaded=False)
323 log('> remaining aftercallbacks completed in %s\n', stats)
325 log('> remaining aftercallbacks completed in %s\n', stats)
324
326
325 # loadall() is called multiple times and lingering _aftercallbacks
327 # loadall() is called multiple times and lingering _aftercallbacks
326 # entries could result in double execution. See issue4646.
328 # entries could result in double execution. See issue4646.
327 _aftercallbacks.clear()
329 _aftercallbacks.clear()
328
330
329 # delay importing avoids cyclic dependency (especially commands)
331 # delay importing avoids cyclic dependency (especially commands)
330 from . import (
332 from . import (
331 color,
333 color,
332 commands,
334 commands,
333 filemerge,
335 filemerge,
334 fileset,
336 fileset,
335 revset,
337 revset,
336 templatefilters,
338 templatefilters,
337 templatefuncs,
339 templatefuncs,
338 templatekw,
340 templatekw,
339 )
341 )
340
342
341 # list of (objname, loadermod, loadername) tuple:
343 # list of (objname, loadermod, loadername) tuple:
342 # - objname is the name of an object in extension module,
344 # - objname is the name of an object in extension module,
343 # from which extra information is loaded
345 # from which extra information is loaded
344 # - loadermod is the module where loader is placed
346 # - loadermod is the module where loader is placed
345 # - loadername is the name of the function,
347 # - loadername is the name of the function,
346 # which takes (ui, extensionname, extraobj) arguments
348 # which takes (ui, extensionname, extraobj) arguments
347 log('- loading extension registration objects\n')
349 log('- loading extension registration objects\n')
348 extraloaders = [
350 extraloaders = [
349 ('cmdtable', commands, 'loadcmdtable'),
351 ('cmdtable', commands, 'loadcmdtable'),
350 ('colortable', color, 'loadcolortable'),
352 ('colortable', color, 'loadcolortable'),
351 ('filesetpredicate', fileset, 'loadpredicate'),
353 ('filesetpredicate', fileset, 'loadpredicate'),
352 ('internalmerge', filemerge, 'loadinternalmerge'),
354 ('internalmerge', filemerge, 'loadinternalmerge'),
353 ('revsetpredicate', revset, 'loadpredicate'),
355 ('revsetpredicate', revset, 'loadpredicate'),
354 ('templatefilter', templatefilters, 'loadfilter'),
356 ('templatefilter', templatefilters, 'loadfilter'),
355 ('templatefunc', templatefuncs, 'loadfunction'),
357 ('templatefunc', templatefuncs, 'loadfunction'),
356 ('templatekeyword', templatekw, 'loadkeyword'),
358 ('templatekeyword', templatekw, 'loadkeyword'),
357 ]
359 ]
358 with util.timedcm('load registration objects') as stats:
360 with util.timedcm('load registration objects') as stats:
359 _loadextra(ui, newindex, extraloaders)
361 _loadextra(ui, newindex, extraloaders)
360 log('> extension registration object loading took %s\n', stats)
362 log('> extension registration object loading took %s\n', stats)
361 log('extension loading complete\n')
363 log('extension loading complete\n')
362
364
363 def _loadextra(ui, newindex, extraloaders):
365 def _loadextra(ui, newindex, extraloaders):
364 for name in _order[newindex:]:
366 for name in _order[newindex:]:
365 module = _extensions[name]
367 module = _extensions[name]
366 if not module:
368 if not module:
367 continue # loading this module failed
369 continue # loading this module failed
368
370
369 for objname, loadermod, loadername in extraloaders:
371 for objname, loadermod, loadername in extraloaders:
370 extraobj = getattr(module, objname, None)
372 extraobj = getattr(module, objname, None)
371 if extraobj is not None:
373 if extraobj is not None:
372 getattr(loadermod, loadername)(ui, name, extraobj)
374 getattr(loadermod, loadername)(ui, name, extraobj)
373
375
374 def afterloaded(extension, callback):
376 def afterloaded(extension, callback):
375 '''Run the specified function after a named extension is loaded.
377 '''Run the specified function after a named extension is loaded.
376
378
377 If the named extension is already loaded, the callback will be called
379 If the named extension is already loaded, the callback will be called
378 immediately.
380 immediately.
379
381
380 If the named extension never loads, the callback will be called after
382 If the named extension never loads, the callback will be called after
381 all extensions have been loaded.
383 all extensions have been loaded.
382
384
383 The callback receives the named argument ``loaded``, which is a boolean
385 The callback receives the named argument ``loaded``, which is a boolean
384 indicating whether the dependent extension actually loaded.
386 indicating whether the dependent extension actually loaded.
385 '''
387 '''
386
388
387 if extension in _extensions:
389 if extension in _extensions:
388 # Report loaded as False if the extension is disabled
390 # Report loaded as False if the extension is disabled
389 loaded = (_extensions[extension] is not None)
391 loaded = (_extensions[extension] is not None)
390 callback(loaded=loaded)
392 callback(loaded=loaded)
391 else:
393 else:
392 _aftercallbacks.setdefault(extension, []).append(callback)
394 _aftercallbacks.setdefault(extension, []).append(callback)
393
395
394 def bind(func, *args):
396 def bind(func, *args):
395 '''Partial function application
397 '''Partial function application
396
398
397 Returns a new function that is the partial application of args and kwargs
399 Returns a new function that is the partial application of args and kwargs
398 to func. For example,
400 to func. For example,
399
401
400 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
402 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
401 assert callable(func)
403 assert callable(func)
402 def closure(*a, **kw):
404 def closure(*a, **kw):
403 return func(*(args + a), **kw)
405 return func(*(args + a), **kw)
404 return closure
406 return closure
405
407
406 def _updatewrapper(wrap, origfn, unboundwrapper):
408 def _updatewrapper(wrap, origfn, unboundwrapper):
407 '''Copy and add some useful attributes to wrapper'''
409 '''Copy and add some useful attributes to wrapper'''
408 try:
410 try:
409 wrap.__name__ = origfn.__name__
411 wrap.__name__ = origfn.__name__
410 except AttributeError:
412 except AttributeError:
411 pass
413 pass
412 wrap.__module__ = getattr(origfn, '__module__')
414 wrap.__module__ = getattr(origfn, '__module__')
413 wrap.__doc__ = getattr(origfn, '__doc__')
415 wrap.__doc__ = getattr(origfn, '__doc__')
414 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
416 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
415 wrap._origfunc = origfn
417 wrap._origfunc = origfn
416 wrap._unboundwrapper = unboundwrapper
418 wrap._unboundwrapper = unboundwrapper
417
419
418 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
420 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
419 '''Wrap the command named `command' in table
421 '''Wrap the command named `command' in table
420
422
421 Replace command in the command table with wrapper. The wrapped command will
423 Replace command in the command table with wrapper. The wrapped command will
422 be inserted into the command table specified by the table argument.
424 be inserted into the command table specified by the table argument.
423
425
424 The wrapper will be called like
426 The wrapper will be called like
425
427
426 wrapper(orig, *args, **kwargs)
428 wrapper(orig, *args, **kwargs)
427
429
428 where orig is the original (wrapped) function, and *args, **kwargs
430 where orig is the original (wrapped) function, and *args, **kwargs
429 are the arguments passed to it.
431 are the arguments passed to it.
430
432
431 Optionally append to the command synopsis and docstring, used for help.
433 Optionally append to the command synopsis and docstring, used for help.
432 For example, if your extension wraps the ``bookmarks`` command to add the
434 For example, if your extension wraps the ``bookmarks`` command to add the
433 flags ``--remote`` and ``--all`` you might call this function like so:
435 flags ``--remote`` and ``--all`` you might call this function like so:
434
436
435 synopsis = ' [-a] [--remote]'
437 synopsis = ' [-a] [--remote]'
436 docstring = """
438 docstring = """
437
439
438 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
440 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
439 flags to the bookmarks command. Either flag will show the remote bookmarks
441 flags to the bookmarks command. Either flag will show the remote bookmarks
440 known to the repository; ``--remote`` will also suppress the output of the
442 known to the repository; ``--remote`` will also suppress the output of the
441 local bookmarks.
443 local bookmarks.
442 """
444 """
443
445
444 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
446 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
445 synopsis, docstring)
447 synopsis, docstring)
446 '''
448 '''
447 assert callable(wrapper)
449 assert callable(wrapper)
448 aliases, entry = cmdutil.findcmd(command, table)
450 aliases, entry = cmdutil.findcmd(command, table)
449 for alias, e in table.iteritems():
451 for alias, e in table.iteritems():
450 if e is entry:
452 if e is entry:
451 key = alias
453 key = alias
452 break
454 break
453
455
454 origfn = entry[0]
456 origfn = entry[0]
455 wrap = functools.partial(util.checksignature(wrapper),
457 wrap = functools.partial(util.checksignature(wrapper),
456 util.checksignature(origfn))
458 util.checksignature(origfn))
457 _updatewrapper(wrap, origfn, wrapper)
459 _updatewrapper(wrap, origfn, wrapper)
458 if docstring is not None:
460 if docstring is not None:
459 wrap.__doc__ += docstring
461 wrap.__doc__ += docstring
460
462
461 newentry = list(entry)
463 newentry = list(entry)
462 newentry[0] = wrap
464 newentry[0] = wrap
463 if synopsis is not None:
465 if synopsis is not None:
464 newentry[2] += synopsis
466 newentry[2] += synopsis
465 table[key] = tuple(newentry)
467 table[key] = tuple(newentry)
466 return entry
468 return entry
467
469
468 def wrapfilecache(cls, propname, wrapper):
470 def wrapfilecache(cls, propname, wrapper):
469 """Wraps a filecache property.
471 """Wraps a filecache property.
470
472
471 These can't be wrapped using the normal wrapfunction.
473 These can't be wrapped using the normal wrapfunction.
472 """
474 """
473 propname = pycompat.sysstr(propname)
475 propname = pycompat.sysstr(propname)
474 assert callable(wrapper)
476 assert callable(wrapper)
475 for currcls in cls.__mro__:
477 for currcls in cls.__mro__:
476 if propname in currcls.__dict__:
478 if propname in currcls.__dict__:
477 origfn = currcls.__dict__[propname].func
479 origfn = currcls.__dict__[propname].func
478 assert callable(origfn)
480 assert callable(origfn)
479 def wrap(*args, **kwargs):
481 def wrap(*args, **kwargs):
480 return wrapper(origfn, *args, **kwargs)
482 return wrapper(origfn, *args, **kwargs)
481 currcls.__dict__[propname].func = wrap
483 currcls.__dict__[propname].func = wrap
482 break
484 break
483
485
484 if currcls is object:
486 if currcls is object:
485 raise AttributeError(r"type '%s' has no property '%s'" % (
487 raise AttributeError(r"type '%s' has no property '%s'" % (
486 cls, propname))
488 cls, propname))
487
489
488 class wrappedfunction(object):
490 class wrappedfunction(object):
489 '''context manager for temporarily wrapping a function'''
491 '''context manager for temporarily wrapping a function'''
490
492
491 def __init__(self, container, funcname, wrapper):
493 def __init__(self, container, funcname, wrapper):
492 assert callable(wrapper)
494 assert callable(wrapper)
493 self._container = container
495 self._container = container
494 self._funcname = funcname
496 self._funcname = funcname
495 self._wrapper = wrapper
497 self._wrapper = wrapper
496
498
497 def __enter__(self):
499 def __enter__(self):
498 wrapfunction(self._container, self._funcname, self._wrapper)
500 wrapfunction(self._container, self._funcname, self._wrapper)
499
501
500 def __exit__(self, exctype, excvalue, traceback):
502 def __exit__(self, exctype, excvalue, traceback):
501 unwrapfunction(self._container, self._funcname, self._wrapper)
503 unwrapfunction(self._container, self._funcname, self._wrapper)
502
504
503 def wrapfunction(container, funcname, wrapper):
505 def wrapfunction(container, funcname, wrapper):
504 '''Wrap the function named funcname in container
506 '''Wrap the function named funcname in container
505
507
506 Replace the funcname member in the given container with the specified
508 Replace the funcname member in the given container with the specified
507 wrapper. The container is typically a module, class, or instance.
509 wrapper. The container is typically a module, class, or instance.
508
510
509 The wrapper will be called like
511 The wrapper will be called like
510
512
511 wrapper(orig, *args, **kwargs)
513 wrapper(orig, *args, **kwargs)
512
514
513 where orig is the original (wrapped) function, and *args, **kwargs
515 where orig is the original (wrapped) function, and *args, **kwargs
514 are the arguments passed to it.
516 are the arguments passed to it.
515
517
516 Wrapping methods of the repository object is not recommended since
518 Wrapping methods of the repository object is not recommended since
517 it conflicts with extensions that extend the repository by
519 it conflicts with extensions that extend the repository by
518 subclassing. All extensions that need to extend methods of
520 subclassing. All extensions that need to extend methods of
519 localrepository should use this subclassing trick: namely,
521 localrepository should use this subclassing trick: namely,
520 reposetup() should look like
522 reposetup() should look like
521
523
522 def reposetup(ui, repo):
524 def reposetup(ui, repo):
523 class myrepo(repo.__class__):
525 class myrepo(repo.__class__):
524 def whatever(self, *args, **kwargs):
526 def whatever(self, *args, **kwargs):
525 [...extension stuff...]
527 [...extension stuff...]
526 super(myrepo, self).whatever(*args, **kwargs)
528 super(myrepo, self).whatever(*args, **kwargs)
527 [...extension stuff...]
529 [...extension stuff...]
528
530
529 repo.__class__ = myrepo
531 repo.__class__ = myrepo
530
532
531 In general, combining wrapfunction() with subclassing does not
533 In general, combining wrapfunction() with subclassing does not
532 work. Since you cannot control what other extensions are loaded by
534 work. Since you cannot control what other extensions are loaded by
533 your end users, you should play nicely with others by using the
535 your end users, you should play nicely with others by using the
534 subclass trick.
536 subclass trick.
535 '''
537 '''
536 assert callable(wrapper)
538 assert callable(wrapper)
537
539
538 origfn = getattr(container, funcname)
540 origfn = getattr(container, funcname)
539 assert callable(origfn)
541 assert callable(origfn)
540 if inspect.ismodule(container):
542 if inspect.ismodule(container):
541 # origfn is not an instance or class method. "partial" can be used.
543 # origfn is not an instance or class method. "partial" can be used.
542 # "partial" won't insert a frame in traceback.
544 # "partial" won't insert a frame in traceback.
543 wrap = functools.partial(wrapper, origfn)
545 wrap = functools.partial(wrapper, origfn)
544 else:
546 else:
545 # "partial" cannot be safely used. Emulate its effect by using "bind".
547 # "partial" cannot be safely used. Emulate its effect by using "bind".
546 # The downside is one more frame in traceback.
548 # The downside is one more frame in traceback.
547 wrap = bind(wrapper, origfn)
549 wrap = bind(wrapper, origfn)
548 _updatewrapper(wrap, origfn, wrapper)
550 _updatewrapper(wrap, origfn, wrapper)
549 setattr(container, funcname, wrap)
551 setattr(container, funcname, wrap)
550 return origfn
552 return origfn
551
553
552 def unwrapfunction(container, funcname, wrapper=None):
554 def unwrapfunction(container, funcname, wrapper=None):
553 '''undo wrapfunction
555 '''undo wrapfunction
554
556
555 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
557 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
556 from the chain of wrappers.
558 from the chain of wrappers.
557
559
558 Return the removed wrapper.
560 Return the removed wrapper.
559 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
561 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
560 wrapper is not None but is not found in the wrapper chain.
562 wrapper is not None but is not found in the wrapper chain.
561 '''
563 '''
562 chain = getwrapperchain(container, funcname)
564 chain = getwrapperchain(container, funcname)
563 origfn = chain.pop()
565 origfn = chain.pop()
564 if wrapper is None:
566 if wrapper is None:
565 wrapper = chain[0]
567 wrapper = chain[0]
566 chain.remove(wrapper)
568 chain.remove(wrapper)
567 setattr(container, funcname, origfn)
569 setattr(container, funcname, origfn)
568 for w in reversed(chain):
570 for w in reversed(chain):
569 wrapfunction(container, funcname, w)
571 wrapfunction(container, funcname, w)
570 return wrapper
572 return wrapper
571
573
572 def getwrapperchain(container, funcname):
574 def getwrapperchain(container, funcname):
573 '''get a chain of wrappers of a function
575 '''get a chain of wrappers of a function
574
576
575 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
577 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
576
578
577 The wrapper functions are the ones passed to wrapfunction, whose first
579 The wrapper functions are the ones passed to wrapfunction, whose first
578 argument is origfunc.
580 argument is origfunc.
579 '''
581 '''
580 result = []
582 result = []
581 fn = getattr(container, funcname)
583 fn = getattr(container, funcname)
582 while fn:
584 while fn:
583 assert callable(fn)
585 assert callable(fn)
584 result.append(getattr(fn, '_unboundwrapper', fn))
586 result.append(getattr(fn, '_unboundwrapper', fn))
585 fn = getattr(fn, '_origfunc', None)
587 fn = getattr(fn, '_origfunc', None)
586 return result
588 return result
587
589
588 def _disabledpaths():
590 def _disabledpaths():
589 '''find paths of disabled extensions. returns a dict of {name: path}'''
591 '''find paths of disabled extensions. returns a dict of {name: path}'''
590 import hgext
592 import hgext
591 extpath = os.path.dirname(
593 extpath = os.path.dirname(
592 os.path.abspath(pycompat.fsencode(hgext.__file__)))
594 os.path.abspath(pycompat.fsencode(hgext.__file__)))
593 try: # might not be a filesystem path
595 try: # might not be a filesystem path
594 files = os.listdir(extpath)
596 files = os.listdir(extpath)
595 except OSError:
597 except OSError:
596 return {}
598 return {}
597
599
598 exts = {}
600 exts = {}
599 for e in files:
601 for e in files:
600 if e.endswith('.py'):
602 if e.endswith('.py'):
601 name = e.rsplit('.', 1)[0]
603 name = e.rsplit('.', 1)[0]
602 path = os.path.join(extpath, e)
604 path = os.path.join(extpath, e)
603 else:
605 else:
604 name = e
606 name = e
605 path = os.path.join(extpath, e, '__init__.py')
607 path = os.path.join(extpath, e, '__init__.py')
606 if not os.path.exists(path):
608 if not os.path.exists(path):
607 continue
609 continue
608 if name in exts or name in _order or name == '__init__':
610 if name in exts or name in _order or name == '__init__':
609 continue
611 continue
610 exts[name] = path
612 exts[name] = path
611 for name, path in _disabledextensions.iteritems():
613 for name, path in _disabledextensions.iteritems():
612 # If no path was provided for a disabled extension (e.g. "color=!"),
614 # If no path was provided for a disabled extension (e.g. "color=!"),
613 # don't replace the path we already found by the scan above.
615 # don't replace the path we already found by the scan above.
614 if path:
616 if path:
615 exts[name] = path
617 exts[name] = path
616 return exts
618 return exts
617
619
618 def _moduledoc(file):
620 def _moduledoc(file):
619 '''return the top-level python documentation for the given file
621 '''return the top-level python documentation for the given file
620
622
621 Loosely inspired by pydoc.source_synopsis(), but rewritten to
623 Loosely inspired by pydoc.source_synopsis(), but rewritten to
622 handle triple quotes and to return the whole text instead of just
624 handle triple quotes and to return the whole text instead of just
623 the synopsis'''
625 the synopsis'''
624 result = []
626 result = []
625
627
626 line = file.readline()
628 line = file.readline()
627 while line[:1] == '#' or not line.strip():
629 while line[:1] == '#' or not line.strip():
628 line = file.readline()
630 line = file.readline()
629 if not line:
631 if not line:
630 break
632 break
631
633
632 start = line[:3]
634 start = line[:3]
633 if start == '"""' or start == "'''":
635 if start == '"""' or start == "'''":
634 line = line[3:]
636 line = line[3:]
635 while line:
637 while line:
636 if line.rstrip().endswith(start):
638 if line.rstrip().endswith(start):
637 line = line.split(start)[0]
639 line = line.split(start)[0]
638 if line:
640 if line:
639 result.append(line)
641 result.append(line)
640 break
642 break
641 elif not line:
643 elif not line:
642 return None # unmatched delimiter
644 return None # unmatched delimiter
643 result.append(line)
645 result.append(line)
644 line = file.readline()
646 line = file.readline()
645 else:
647 else:
646 return None
648 return None
647
649
648 return ''.join(result)
650 return ''.join(result)
649
651
650 def _disabledhelp(path):
652 def _disabledhelp(path):
651 '''retrieve help synopsis of a disabled extension (without importing)'''
653 '''retrieve help synopsis of a disabled extension (without importing)'''
652 try:
654 try:
653 with open(path, 'rb') as src:
655 with open(path, 'rb') as src:
654 doc = _moduledoc(src)
656 doc = _moduledoc(src)
655 except IOError:
657 except IOError:
656 return
658 return
657
659
658 if doc: # extracting localized synopsis
660 if doc: # extracting localized synopsis
659 return gettext(doc)
661 return gettext(doc)
660 else:
662 else:
661 return _('(no help text available)')
663 return _('(no help text available)')
662
664
663 def disabled():
665 def disabled():
664 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
666 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
665 try:
667 try:
666 from hgext import __index__
668 from hgext import __index__
667 return dict((name, gettext(desc))
669 return dict((name, gettext(desc))
668 for name, desc in __index__.docs.iteritems()
670 for name, desc in __index__.docs.iteritems()
669 if name not in _order)
671 if name not in _order)
670 except (ImportError, AttributeError):
672 except (ImportError, AttributeError):
671 pass
673 pass
672
674
673 paths = _disabledpaths()
675 paths = _disabledpaths()
674 if not paths:
676 if not paths:
675 return {}
677 return {}
676
678
677 exts = {}
679 exts = {}
678 for name, path in paths.iteritems():
680 for name, path in paths.iteritems():
679 doc = _disabledhelp(path)
681 doc = _disabledhelp(path)
680 if doc:
682 if doc:
681 exts[name] = doc.splitlines()[0]
683 exts[name] = doc.splitlines()[0]
682
684
683 return exts
685 return exts
684
686
685 def disabledext(name):
687 def disabledext(name):
686 '''find a specific disabled extension from hgext. returns desc'''
688 '''find a specific disabled extension from hgext. returns desc'''
687 try:
689 try:
688 from hgext import __index__
690 from hgext import __index__
689 if name in _order: # enabled
691 if name in _order: # enabled
690 return
692 return
691 else:
693 else:
692 return gettext(__index__.docs.get(name))
694 return gettext(__index__.docs.get(name))
693 except (ImportError, AttributeError):
695 except (ImportError, AttributeError):
694 pass
696 pass
695
697
696 paths = _disabledpaths()
698 paths = _disabledpaths()
697 if name in paths:
699 if name in paths:
698 return _disabledhelp(paths[name])
700 return _disabledhelp(paths[name])
699
701
700 def _walkcommand(node):
702 def _walkcommand(node):
701 """Scan @command() decorators in the tree starting at node"""
703 """Scan @command() decorators in the tree starting at node"""
702 todo = collections.deque([node])
704 todo = collections.deque([node])
703 while todo:
705 while todo:
704 node = todo.popleft()
706 node = todo.popleft()
705 if not isinstance(node, ast.FunctionDef):
707 if not isinstance(node, ast.FunctionDef):
706 todo.extend(ast.iter_child_nodes(node))
708 todo.extend(ast.iter_child_nodes(node))
707 continue
709 continue
708 for d in node.decorator_list:
710 for d in node.decorator_list:
709 if not isinstance(d, ast.Call):
711 if not isinstance(d, ast.Call):
710 continue
712 continue
711 if not isinstance(d.func, ast.Name):
713 if not isinstance(d.func, ast.Name):
712 continue
714 continue
713 if d.func.id != r'command':
715 if d.func.id != r'command':
714 continue
716 continue
715 yield d
717 yield d
716
718
717 def _disabledcmdtable(path):
719 def _disabledcmdtable(path):
718 """Construct a dummy command table without loading the extension module
720 """Construct a dummy command table without loading the extension module
719
721
720 This may raise IOError or SyntaxError.
722 This may raise IOError or SyntaxError.
721 """
723 """
722 with open(path, 'rb') as src:
724 with open(path, 'rb') as src:
723 root = ast.parse(src.read(), path)
725 root = ast.parse(src.read(), path)
724 cmdtable = {}
726 cmdtable = {}
725 for node in _walkcommand(root):
727 for node in _walkcommand(root):
726 if not node.args:
728 if not node.args:
727 continue
729 continue
728 a = node.args[0]
730 a = node.args[0]
729 if isinstance(a, ast.Str):
731 if isinstance(a, ast.Str):
730 name = pycompat.sysbytes(a.s)
732 name = pycompat.sysbytes(a.s)
731 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
733 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
732 name = a.s
734 name = a.s
733 else:
735 else:
734 continue
736 continue
735 cmdtable[name] = (None, [], b'')
737 cmdtable[name] = (None, [], b'')
736 return cmdtable
738 return cmdtable
737
739
738 def _finddisabledcmd(ui, cmd, name, path, strict):
740 def _finddisabledcmd(ui, cmd, name, path, strict):
739 try:
741 try:
740 cmdtable = _disabledcmdtable(path)
742 cmdtable = _disabledcmdtable(path)
741 except (IOError, SyntaxError):
743 except (IOError, SyntaxError):
742 return
744 return
743 try:
745 try:
744 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
746 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
745 except (error.AmbiguousCommand, error.UnknownCommand):
747 except (error.AmbiguousCommand, error.UnknownCommand):
746 return
748 return
747 for c in aliases:
749 for c in aliases:
748 if c.startswith(cmd):
750 if c.startswith(cmd):
749 cmd = c
751 cmd = c
750 break
752 break
751 else:
753 else:
752 cmd = aliases[0]
754 cmd = aliases[0]
753 doc = _disabledhelp(path)
755 doc = _disabledhelp(path)
754 return (cmd, name, doc)
756 return (cmd, name, doc)
755
757
756 def disabledcmd(ui, cmd, strict=False):
758 def disabledcmd(ui, cmd, strict=False):
757 '''find cmd from disabled extensions without importing.
759 '''find cmd from disabled extensions without importing.
758 returns (cmdname, extname, doc)'''
760 returns (cmdname, extname, doc)'''
759
761
760 paths = _disabledpaths()
762 paths = _disabledpaths()
761 if not paths:
763 if not paths:
762 raise error.UnknownCommand(cmd)
764 raise error.UnknownCommand(cmd)
763
765
764 ext = None
766 ext = None
765 # first, search for an extension with the same name as the command
767 # first, search for an extension with the same name as the command
766 path = paths.pop(cmd, None)
768 path = paths.pop(cmd, None)
767 if path:
769 if path:
768 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
770 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
769 if not ext:
771 if not ext:
770 # otherwise, interrogate each extension until there's a match
772 # otherwise, interrogate each extension until there's a match
771 for name, path in paths.iteritems():
773 for name, path in paths.iteritems():
772 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
774 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
773 if ext:
775 if ext:
774 break
776 break
775 if ext:
777 if ext:
776 return ext
778 return ext
777
779
778 raise error.UnknownCommand(cmd)
780 raise error.UnknownCommand(cmd)
779
781
780 def enabled(shortname=True):
782 def enabled(shortname=True):
781 '''return a dict of {name: desc} of extensions'''
783 '''return a dict of {name: desc} of extensions'''
782 exts = {}
784 exts = {}
783 for ename, ext in extensions():
785 for ename, ext in extensions():
784 doc = (gettext(ext.__doc__) or _('(no help text available)'))
786 doc = (gettext(ext.__doc__) or _('(no help text available)'))
785 if shortname:
787 if shortname:
786 ename = ename.split('.')[-1]
788 ename = ename.split('.')[-1]
787 exts[ename] = doc.splitlines()[0].strip()
789 exts[ename] = doc.splitlines()[0].strip()
788
790
789 return exts
791 return exts
790
792
791 def notloaded():
793 def notloaded():
792 '''return short names of extensions that failed to load'''
794 '''return short names of extensions that failed to load'''
793 return [name for name, mod in _extensions.iteritems() if mod is None]
795 return [name for name, mod in _extensions.iteritems() if mod is None]
794
796
795 def moduleversion(module):
797 def moduleversion(module):
796 '''return version information from given module as a string'''
798 '''return version information from given module as a string'''
797 if (util.safehasattr(module, 'getversion')
799 if (util.safehasattr(module, 'getversion')
798 and callable(module.getversion)):
800 and callable(module.getversion)):
799 version = module.getversion()
801 version = module.getversion()
800 elif util.safehasattr(module, '__version__'):
802 elif util.safehasattr(module, '__version__'):
801 version = module.__version__
803 version = module.__version__
802 else:
804 else:
803 version = ''
805 version = ''
804 if isinstance(version, (list, tuple)):
806 if isinstance(version, (list, tuple)):
805 version = '.'.join(pycompat.bytestr(o) for o in version)
807 version = '.'.join(pycompat.bytestr(o) for o in version)
806 return version
808 return version
807
809
808 def ismoduleinternal(module):
810 def ismoduleinternal(module):
809 exttestedwith = getattr(module, 'testedwith', None)
811 exttestedwith = getattr(module, 'testedwith', None)
810 return exttestedwith == "ships-with-hg-core"
812 return exttestedwith == "ships-with-hg-core"
@@ -1,134 +1,135 b''
1 ensure that failing ui.atexit handlers report sensibly
1 ensure that failing ui.atexit handlers report sensibly
2
2
3 $ cat > $TESTTMP/bailatexit.py <<EOF
3 $ cat > $TESTTMP/bailatexit.py <<EOF
4 > from mercurial import util
4 > from mercurial import util
5 > def bail():
5 > def bail():
6 > raise RuntimeError('ui.atexit handler exception')
6 > raise RuntimeError('ui.atexit handler exception')
7 >
7 >
8 > def extsetup(ui):
8 > def extsetup(ui):
9 > ui.atexit(bail)
9 > ui.atexit(bail)
10 > EOF
10 > EOF
11 $ hg -q --config extensions.bailatexit=$TESTTMP/bailatexit.py \
11 $ hg -q --config extensions.bailatexit=$TESTTMP/bailatexit.py \
12 > help help
12 > help help
13 hg help [-ecks] [TOPIC]
13 hg help [-ecks] [TOPIC]
14
14
15 show help for a given topic or a help overview
15 show help for a given topic or a help overview
16 error in exit handlers:
16 error in exit handlers:
17 Traceback (most recent call last):
17 Traceback (most recent call last):
18 File "*/mercurial/dispatch.py", line *, in _runexithandlers (glob)
18 File "*/mercurial/dispatch.py", line *, in _runexithandlers (glob)
19 func(*args, **kwargs)
19 func(*args, **kwargs)
20 File "$TESTTMP/bailatexit.py", line *, in bail (glob)
20 File "$TESTTMP/bailatexit.py", line *, in bail (glob)
21 raise RuntimeError('ui.atexit handler exception')
21 raise RuntimeError('ui.atexit handler exception')
22 RuntimeError: ui.atexit handler exception
22 RuntimeError: ui.atexit handler exception
23 [255]
23 [255]
24
24
25 $ rm $TESTTMP/bailatexit.py
25 $ rm $TESTTMP/bailatexit.py
26
26
27 another bad extension
27 another bad extension
28
28
29 $ echo 'raise Exception("bit bucket overflow")' > badext.py
29 $ echo 'raise Exception("bit bucket overflow")' > badext.py
30 $ abspathexc=`pwd`/badext.py
30 $ abspathexc=`pwd`/badext.py
31
31
32 $ cat >baddocext.py <<EOF
32 $ cat >baddocext.py <<EOF
33 > """
33 > """
34 > baddocext is bad
34 > baddocext is bad
35 > """
35 > """
36 > EOF
36 > EOF
37 $ abspathdoc=`pwd`/baddocext.py
37 $ abspathdoc=`pwd`/baddocext.py
38
38
39 $ cat <<EOF >> $HGRCPATH
39 $ cat <<EOF >> $HGRCPATH
40 > [extensions]
40 > [extensions]
41 > gpg =
41 > gpg =
42 > hgext.gpg =
42 > hgext.gpg =
43 > badext = $abspathexc
43 > badext = $abspathexc
44 > baddocext = $abspathdoc
44 > baddocext = $abspathdoc
45 > badext2 =
45 > badext2 =
46 > EOF
46 > EOF
47
47
48 $ hg -q help help 2>&1 |grep extension
48 $ hg -q help help 2>&1 |grep extension
49 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
49 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
50 *** failed to import extension badext2: No module named badext2
50 *** failed to import extension badext2: No module named badext2
51
51
52 show traceback
52 show traceback
53
53
54 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError'
54 $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError'
55 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
55 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
56 Traceback (most recent call last):
56 Traceback (most recent call last):
57 Exception: bit bucket overflow
57 Exception: bit bucket overflow
58 *** failed to import extension badext2: No module named badext2
58 *** failed to import extension badext2: No module named badext2
59 Traceback (most recent call last):
59 Traceback (most recent call last):
60 ImportError: No module named badext2
60 ImportError: No module named badext2
61
61
62 names of extensions failed to load can be accessed via extensions.notloaded()
62 names of extensions failed to load can be accessed via extensions.notloaded()
63
63
64 $ cat <<EOF > showbadexts.py
64 $ cat <<EOF > showbadexts.py
65 > from mercurial import commands, extensions, registrar
65 > from mercurial import commands, extensions, registrar
66 > cmdtable = {}
66 > cmdtable = {}
67 > command = registrar.command(cmdtable)
67 > command = registrar.command(cmdtable)
68 > @command(b'showbadexts', norepo=True)
68 > @command(b'showbadexts', norepo=True)
69 > def showbadexts(ui, *pats, **opts):
69 > def showbadexts(ui, *pats, **opts):
70 > ui.write('BADEXTS: %s\n' % ' '.join(sorted(extensions.notloaded())))
70 > ui.write('BADEXTS: %s\n' % ' '.join(sorted(extensions.notloaded())))
71 > EOF
71 > EOF
72 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
72 $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
73 BADEXTS: badext badext2
73 BADEXTS: badext badext2
74
74
75 #if no-extraextensions
75 #if no-extraextensions
76 show traceback for ImportError of hgext.name if devel.debug.extensions is set
76 show traceback for ImportError of hgext.name if devel.debug.extensions is set
77
77
78 $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
78 $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
79 > | grep -v '^ ' \
79 > | grep -v '^ ' \
80 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
80 > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
81 debug.extensions: loading extensions
81 debug.extensions: loading extensions
82 debug.extensions: - processing 5 entries
82 debug.extensions: - processing 5 entries
83 debug.extensions: - loading extension: 'gpg'
83 debug.extensions: - loading extension: 'gpg'
84 debug.extensions: > 'gpg' extension loaded in * (glob)
84 debug.extensions: > 'gpg' extension loaded in * (glob)
85 debug.extensions: - validating extension tables: 'gpg'
85 debug.extensions: - validating extension tables: 'gpg'
86 debug.extensions: - invoking registered callbacks: 'gpg'
86 debug.extensions: - invoking registered callbacks: 'gpg'
87 debug.extensions: > callbacks completed in * (glob)
87 debug.extensions: > callbacks completed in * (glob)
88 debug.extensions: - loading extension: 'badext'
88 debug.extensions: - loading extension: 'badext'
89 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
89 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
90 Traceback (most recent call last):
90 Traceback (most recent call last):
91 Exception: bit bucket overflow
91 Exception: bit bucket overflow
92 debug.extensions: - loading extension: 'baddocext'
92 debug.extensions: - loading extension: 'baddocext'
93 debug.extensions: > 'baddocext' extension loaded in * (glob)
93 debug.extensions: > 'baddocext' extension loaded in * (glob)
94 debug.extensions: - validating extension tables: 'baddocext'
94 debug.extensions: - validating extension tables: 'baddocext'
95 debug.extensions: - invoking registered callbacks: 'baddocext'
95 debug.extensions: - invoking registered callbacks: 'baddocext'
96 debug.extensions: > callbacks completed in * (glob)
96 debug.extensions: > callbacks completed in * (glob)
97 debug.extensions: - loading extension: 'badext2'
97 debug.extensions: - loading extension: 'badext2'
98 debug.extensions: - could not import hgext.badext2 (No module named badext2): trying hgext3rd.badext2
98 debug.extensions: - could not import hgext.badext2 (No module named badext2): trying hgext3rd.badext2
99 Traceback (most recent call last):
99 Traceback (most recent call last):
100 ImportError: No module named *badext2 (glob)
100 ImportError: No module named *badext2 (glob)
101 debug.extensions: - could not import hgext3rd.badext2 (No module named badext2): trying badext2
101 debug.extensions: - could not import hgext3rd.badext2 (No module named badext2): trying badext2
102 Traceback (most recent call last):
102 Traceback (most recent call last):
103 ImportError: No module named *badext2 (glob)
103 ImportError: No module named *badext2 (glob)
104 *** failed to import extension badext2: No module named badext2
104 *** failed to import extension badext2: No module named badext2
105 Traceback (most recent call last):
105 Traceback (most recent call last):
106 ImportError: No module named badext2
106 ImportError: No module named badext2
107 debug.extensions: > loaded 2 extensions, total time * (glob)
107 debug.extensions: > loaded 2 extensions, total time * (glob)
108 debug.extensions: - loading configtable attributes
108 debug.extensions: - loading configtable attributes
109 debug.extensions: - executing uisetup hooks
109 debug.extensions: - executing uisetup hooks
110 debug.extensions: - running uisetup for 'gpg'
110 debug.extensions: - running uisetup for 'gpg'
111 debug.extensions: > uisetup for 'gpg' took * (glob)
111 debug.extensions: > uisetup for 'gpg' took * (glob)
112 debug.extensions: - running uisetup for 'baddocext'
112 debug.extensions: - running uisetup for 'baddocext'
113 debug.extensions: > uisetup for 'baddocext' took * (glob)
113 debug.extensions: > uisetup for 'baddocext' took * (glob)
114 debug.extensions: > all uisetup took * (glob)
114 debug.extensions: > all uisetup took * (glob)
115 debug.extensions: - executing extsetup hooks
115 debug.extensions: - executing extsetup hooks
116 debug.extensions: - running extsetup for 'gpg'
116 debug.extensions: - running extsetup for 'gpg'
117 debug.extensions: > extsetup for 'gpg' took * (glob)
117 debug.extensions: > extsetup for 'gpg' took * (glob)
118 debug.extensions: - running extsetup for 'baddocext'
118 debug.extensions: - running extsetup for 'baddocext'
119 debug.extensions: > extsetup for 'baddocext' took * (glob)
119 debug.extensions: > extsetup for 'baddocext' took * (glob)
120 debug.extensions: > all extsetup took * (glob)
120 debug.extensions: - executing remaining aftercallbacks
121 debug.extensions: - executing remaining aftercallbacks
121 debug.extensions: > remaining aftercallbacks completed in * (glob)
122 debug.extensions: > remaining aftercallbacks completed in * (glob)
122 debug.extensions: - loading extension registration objects
123 debug.extensions: - loading extension registration objects
123 debug.extensions: > extension registration object loading took * (glob)
124 debug.extensions: > extension registration object loading took * (glob)
124 debug.extensions: extension loading complete
125 debug.extensions: extension loading complete
125 #endif
126 #endif
126
127
127 confirm that there's no crash when an extension's documentation is bad
128 confirm that there's no crash when an extension's documentation is bad
128
129
129 $ hg help --keyword baddocext
130 $ hg help --keyword baddocext
130 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
131 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
131 *** failed to import extension badext2: No module named badext2
132 *** failed to import extension badext2: No module named badext2
132 Topics:
133 Topics:
133
134
134 extensions Using Additional Features
135 extensions Using Additional Features
@@ -1,92 +1,94 b''
1 Test basic extension support
1 Test basic extension support
2
2
3 $ cat > foobar.py <<EOF
3 $ cat > foobar.py <<EOF
4 > import os
4 > import os
5 > from mercurial import commands, registrar
5 > from mercurial import commands, registrar
6 > cmdtable = {}
6 > cmdtable = {}
7 > command = registrar.command(cmdtable)
7 > command = registrar.command(cmdtable)
8 > configtable = {}
8 > configtable = {}
9 > configitem = registrar.configitem(configtable)
9 > configitem = registrar.configitem(configtable)
10 > configitem(b'tests', b'foo', default=b"Foo")
10 > configitem(b'tests', b'foo', default=b"Foo")
11 > def uisetup(ui):
11 > def uisetup(ui):
12 > ui.debug(b"uisetup called [debug]\\n")
12 > ui.debug(b"uisetup called [debug]\\n")
13 > ui.write(b"uisetup called\\n")
13 > ui.write(b"uisetup called\\n")
14 > ui.status(b"uisetup called [status]\\n")
14 > ui.status(b"uisetup called [status]\\n")
15 > ui.flush()
15 > ui.flush()
16 > def reposetup(ui, repo):
16 > def reposetup(ui, repo):
17 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
17 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
18 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
18 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
19 > ui.flush()
19 > ui.flush()
20 > @command(b'foo', [], b'hg foo')
20 > @command(b'foo', [], b'hg foo')
21 > def foo(ui, *args, **kwargs):
21 > def foo(ui, *args, **kwargs):
22 > foo = ui.config(b'tests', b'foo')
22 > foo = ui.config(b'tests', b'foo')
23 > ui.write(foo)
23 > ui.write(foo)
24 > ui.write(b"\\n")
24 > ui.write(b"\\n")
25 > @command(b'bar', [], b'hg bar', norepo=True)
25 > @command(b'bar', [], b'hg bar', norepo=True)
26 > def bar(ui, *args, **kwargs):
26 > def bar(ui, *args, **kwargs):
27 > ui.write(b"Bar\\n")
27 > ui.write(b"Bar\\n")
28 > EOF
28 > EOF
29 $ abspath=`pwd`/foobar.py
29 $ abspath=`pwd`/foobar.py
30
30
31 $ mkdir barfoo
31 $ mkdir barfoo
32 $ cp foobar.py barfoo/__init__.py
32 $ cp foobar.py barfoo/__init__.py
33 $ barfoopath=`pwd`/barfoo
33 $ barfoopath=`pwd`/barfoo
34
34
35 $ hg init a
35 $ hg init a
36 $ cd a
36 $ cd a
37 $ echo foo > file
37 $ echo foo > file
38 $ hg add file
38 $ hg add file
39 $ hg commit -m 'add file'
39 $ hg commit -m 'add file'
40
40
41 $ echo '[extensions]' >> $HGRCPATH
41 $ echo '[extensions]' >> $HGRCPATH
42 $ echo "foobar = $abspath" >> $HGRCPATH
42 $ echo "foobar = $abspath" >> $HGRCPATH
43
43
44 Test extension setup timings
44 Test extension setup timings
45
45
46 $ hg foo --traceback --config devel.debug.extensions=yes --debug 2>&1
46 $ hg foo --traceback --config devel.debug.extensions=yes --debug 2>&1
47 debug.extensions: loading extensions
47 debug.extensions: loading extensions
48 debug.extensions: - processing 1 entries
48 debug.extensions: - processing 1 entries
49 debug.extensions: - loading extension: 'foobar'
49 debug.extensions: - loading extension: 'foobar'
50 debug.extensions: > 'foobar' extension loaded in * (glob)
50 debug.extensions: > 'foobar' extension loaded in * (glob)
51 debug.extensions: - validating extension tables: 'foobar'
51 debug.extensions: - validating extension tables: 'foobar'
52 debug.extensions: - invoking registered callbacks: 'foobar'
52 debug.extensions: - invoking registered callbacks: 'foobar'
53 debug.extensions: > callbacks completed in * (glob)
53 debug.extensions: > callbacks completed in * (glob)
54 debug.extensions: > loaded 1 extensions, total time * (glob)
54 debug.extensions: > loaded 1 extensions, total time * (glob)
55 debug.extensions: - loading configtable attributes
55 debug.extensions: - loading configtable attributes
56 debug.extensions: - executing uisetup hooks
56 debug.extensions: - executing uisetup hooks
57 debug.extensions: - running uisetup for 'foobar'
57 debug.extensions: - running uisetup for 'foobar'
58 uisetup called [debug]
58 uisetup called [debug]
59 uisetup called
59 uisetup called
60 uisetup called [status]
60 uisetup called [status]
61 debug.extensions: > uisetup for 'foobar' took * (glob)
61 debug.extensions: > uisetup for 'foobar' took * (glob)
62 debug.extensions: > all uisetup took * (glob)
62 debug.extensions: > all uisetup took * (glob)
63 debug.extensions: - executing extsetup hooks
63 debug.extensions: - executing extsetup hooks
64 debug.extensions: - running extsetup for 'foobar'
64 debug.extensions: - running extsetup for 'foobar'
65 debug.extensions: > extsetup for 'foobar' took * (glob)
65 debug.extensions: > extsetup for 'foobar' took * (glob)
66 debug.extensions: > all extsetup took * (glob)
66 debug.extensions: - executing remaining aftercallbacks
67 debug.extensions: - executing remaining aftercallbacks
67 debug.extensions: > remaining aftercallbacks completed in * (glob)
68 debug.extensions: > remaining aftercallbacks completed in * (glob)
68 debug.extensions: - loading extension registration objects
69 debug.extensions: - loading extension registration objects
69 debug.extensions: > extension registration object loading took * (glob)
70 debug.extensions: > extension registration object loading took * (glob)
70 debug.extensions: extension loading complete
71 debug.extensions: extension loading complete
71 debug.extensions: loading additional extensions
72 debug.extensions: loading additional extensions
72 debug.extensions: - processing 1 entries
73 debug.extensions: - processing 1 entries
73 debug.extensions: > loaded 0 extensions, total time * (glob)
74 debug.extensions: > loaded 0 extensions, total time * (glob)
74 debug.extensions: - loading configtable attributes
75 debug.extensions: - loading configtable attributes
75 debug.extensions: - executing uisetup hooks
76 debug.extensions: - executing uisetup hooks
76 debug.extensions: > all uisetup took * (glob)
77 debug.extensions: > all uisetup took * (glob)
77 debug.extensions: - executing extsetup hooks
78 debug.extensions: - executing extsetup hooks
79 debug.extensions: > all extsetup took * (glob)
78 debug.extensions: - executing remaining aftercallbacks
80 debug.extensions: - executing remaining aftercallbacks
79 debug.extensions: > remaining aftercallbacks completed in * (glob)
81 debug.extensions: > remaining aftercallbacks completed in * (glob)
80 debug.extensions: - loading extension registration objects
82 debug.extensions: - loading extension registration objects
81 debug.extensions: > extension registration object loading took * (glob)
83 debug.extensions: > extension registration object loading took * (glob)
82 debug.extensions: extension loading complete
84 debug.extensions: extension loading complete
83 debug.extensions: - executing reposetup hooks
85 debug.extensions: - executing reposetup hooks
84 debug.extensions: - running reposetup for foobar
86 debug.extensions: - running reposetup for foobar
85 reposetup called for a
87 reposetup called for a
86 ui == repo.ui
88 ui == repo.ui
87 debug.extensions: > reposetup for 'foobar' took * (glob)
89 debug.extensions: > reposetup for 'foobar' took * (glob)
88 Foo
90 Foo
89
91
90 $ cd ..
92 $ cd ..
91
93
92 $ echo 'foobar = !' >> $HGRCPATH
94 $ echo 'foobar = !' >> $HGRCPATH
General Comments 0
You need to be logged in to leave comments. Login now