##// END OF EJS Templates
extensions: show traceback on load failure if --traceback flag is set...
Yuya Nishihara -
r25364:de23a552 default
parent child Browse files
Show More
@@ -1,443 +1,446
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 import imp, os
8 import imp, os
9 import util, cmdutil, error
9 import util, cmdutil, error
10 from i18n import _, gettext
10 from i18n import _, gettext
11
11
12 _extensions = {}
12 _extensions = {}
13 _aftercallbacks = {}
13 _aftercallbacks = {}
14 _order = []
14 _order = []
15 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
15 _ignore = ['hbisect', 'bookmarks', 'parentrevspec', 'interhg', 'inotify']
16
16
17 def extensions(ui=None):
17 def extensions(ui=None):
18 if ui:
18 if ui:
19 def enabled(name):
19 def enabled(name):
20 for format in ['%s', 'hgext.%s']:
20 for format in ['%s', 'hgext.%s']:
21 conf = ui.config('extensions', format % name)
21 conf = ui.config('extensions', format % name)
22 if conf is not None and not conf.startswith('!'):
22 if conf is not None and not conf.startswith('!'):
23 return True
23 return True
24 else:
24 else:
25 enabled = lambda name: True
25 enabled = lambda name: True
26 for name in _order:
26 for name in _order:
27 module = _extensions[name]
27 module = _extensions[name]
28 if module and enabled(name):
28 if module and enabled(name):
29 yield name, module
29 yield name, module
30
30
31 def find(name):
31 def find(name):
32 '''return module with given extension name'''
32 '''return module with given extension name'''
33 mod = None
33 mod = None
34 try:
34 try:
35 mod = _extensions[name]
35 mod = _extensions[name]
36 except KeyError:
36 except KeyError:
37 for k, v in _extensions.iteritems():
37 for k, v in _extensions.iteritems():
38 if k.endswith('.' + name) or k.endswith('/' + name):
38 if k.endswith('.' + name) or k.endswith('/' + name):
39 mod = v
39 mod = v
40 break
40 break
41 if not mod:
41 if not mod:
42 raise KeyError(name)
42 raise KeyError(name)
43 return mod
43 return mod
44
44
45 def loadpath(path, module_name):
45 def loadpath(path, module_name):
46 module_name = module_name.replace('.', '_')
46 module_name = module_name.replace('.', '_')
47 path = util.normpath(util.expandpath(path))
47 path = util.normpath(util.expandpath(path))
48 if os.path.isdir(path):
48 if os.path.isdir(path):
49 # module/__init__.py style
49 # module/__init__.py style
50 d, f = os.path.split(path)
50 d, f = os.path.split(path)
51 fd, fpath, desc = imp.find_module(f, [d])
51 fd, fpath, desc = imp.find_module(f, [d])
52 return imp.load_module(module_name, fd, fpath, desc)
52 return imp.load_module(module_name, fd, fpath, desc)
53 else:
53 else:
54 try:
54 try:
55 return imp.load_source(module_name, path)
55 return imp.load_source(module_name, path)
56 except IOError, exc:
56 except IOError, exc:
57 if not exc.filename:
57 if not exc.filename:
58 exc.filename = path # python does not fill this
58 exc.filename = path # python does not fill this
59 raise
59 raise
60
60
61 def load(ui, name, path):
61 def load(ui, name, path):
62 if name.startswith('hgext.') or name.startswith('hgext/'):
62 if name.startswith('hgext.') or name.startswith('hgext/'):
63 shortname = name[6:]
63 shortname = name[6:]
64 else:
64 else:
65 shortname = name
65 shortname = name
66 if shortname in _ignore:
66 if shortname in _ignore:
67 return None
67 return None
68 if shortname in _extensions:
68 if shortname in _extensions:
69 return _extensions[shortname]
69 return _extensions[shortname]
70 _extensions[shortname] = None
70 _extensions[shortname] = None
71 if path:
71 if path:
72 # the module will be loaded in sys.modules
72 # the module will be loaded in sys.modules
73 # choose an unique name so that it doesn't
73 # choose an unique name so that it doesn't
74 # conflicts with other modules
74 # conflicts with other modules
75 mod = loadpath(path, 'hgext.%s' % name)
75 mod = loadpath(path, 'hgext.%s' % name)
76 else:
76 else:
77 def importh(name):
77 def importh(name):
78 mod = __import__(name)
78 mod = __import__(name)
79 components = name.split('.')
79 components = name.split('.')
80 for comp in components[1:]:
80 for comp in components[1:]:
81 mod = getattr(mod, comp)
81 mod = getattr(mod, comp)
82 return mod
82 return mod
83 try:
83 try:
84 mod = importh("hgext.%s" % name)
84 mod = importh("hgext.%s" % name)
85 except ImportError, err:
85 except ImportError, err:
86 ui.debug('could not import hgext.%s (%s): trying %s\n'
86 ui.debug('could not import hgext.%s (%s): trying %s\n'
87 % (name, err, name))
87 % (name, err, name))
88 if ui.debugflag:
89 ui.traceback()
88 mod = importh(name)
90 mod = importh(name)
89 _extensions[shortname] = mod
91 _extensions[shortname] = mod
90 _order.append(shortname)
92 _order.append(shortname)
91 for fn in _aftercallbacks.get(shortname, []):
93 for fn in _aftercallbacks.get(shortname, []):
92 fn(loaded=True)
94 fn(loaded=True)
93 return mod
95 return mod
94
96
95 def loadall(ui):
97 def loadall(ui):
96 result = ui.configitems("extensions")
98 result = ui.configitems("extensions")
97 newindex = len(_order)
99 newindex = len(_order)
98 for (name, path) in result:
100 for (name, path) in result:
99 if path:
101 if path:
100 if path[0] == '!':
102 if path[0] == '!':
101 continue
103 continue
102 try:
104 try:
103 load(ui, name, path)
105 load(ui, name, path)
104 except KeyboardInterrupt:
106 except KeyboardInterrupt:
105 raise
107 raise
106 except Exception, inst:
108 except Exception, inst:
107 if path:
109 if path:
108 ui.warn(_("*** failed to import extension %s from %s: %s\n")
110 ui.warn(_("*** failed to import extension %s from %s: %s\n")
109 % (name, path, inst))
111 % (name, path, inst))
110 else:
112 else:
111 ui.warn(_("*** failed to import extension %s: %s\n")
113 ui.warn(_("*** failed to import extension %s: %s\n")
112 % (name, inst))
114 % (name, inst))
115 ui.traceback()
113
116
114 for name in _order[newindex:]:
117 for name in _order[newindex:]:
115 uisetup = getattr(_extensions[name], 'uisetup', None)
118 uisetup = getattr(_extensions[name], 'uisetup', None)
116 if uisetup:
119 if uisetup:
117 uisetup(ui)
120 uisetup(ui)
118
121
119 for name in _order[newindex:]:
122 for name in _order[newindex:]:
120 extsetup = getattr(_extensions[name], 'extsetup', None)
123 extsetup = getattr(_extensions[name], 'extsetup', None)
121 if extsetup:
124 if extsetup:
122 try:
125 try:
123 extsetup(ui)
126 extsetup(ui)
124 except TypeError:
127 except TypeError:
125 if extsetup.func_code.co_argcount != 0:
128 if extsetup.func_code.co_argcount != 0:
126 raise
129 raise
127 extsetup() # old extsetup with no ui argument
130 extsetup() # old extsetup with no ui argument
128
131
129 # Call aftercallbacks that were never met.
132 # Call aftercallbacks that were never met.
130 for shortname in _aftercallbacks:
133 for shortname in _aftercallbacks:
131 if shortname in _extensions:
134 if shortname in _extensions:
132 continue
135 continue
133
136
134 for fn in _aftercallbacks[shortname]:
137 for fn in _aftercallbacks[shortname]:
135 fn(loaded=False)
138 fn(loaded=False)
136
139
137 # loadall() is called multiple times and lingering _aftercallbacks
140 # loadall() is called multiple times and lingering _aftercallbacks
138 # entries could result in double execution. See issue4646.
141 # entries could result in double execution. See issue4646.
139 _aftercallbacks.clear()
142 _aftercallbacks.clear()
140
143
141 def afterloaded(extension, callback):
144 def afterloaded(extension, callback):
142 '''Run the specified function after a named extension is loaded.
145 '''Run the specified function after a named extension is loaded.
143
146
144 If the named extension is already loaded, the callback will be called
147 If the named extension is already loaded, the callback will be called
145 immediately.
148 immediately.
146
149
147 If the named extension never loads, the callback will be called after
150 If the named extension never loads, the callback will be called after
148 all extensions have been loaded.
151 all extensions have been loaded.
149
152
150 The callback receives the named argument ``loaded``, which is a boolean
153 The callback receives the named argument ``loaded``, which is a boolean
151 indicating whether the dependent extension actually loaded.
154 indicating whether the dependent extension actually loaded.
152 '''
155 '''
153
156
154 if extension in _extensions:
157 if extension in _extensions:
155 callback(loaded=True)
158 callback(loaded=True)
156 else:
159 else:
157 _aftercallbacks.setdefault(extension, []).append(callback)
160 _aftercallbacks.setdefault(extension, []).append(callback)
158
161
159 def bind(func, *args):
162 def bind(func, *args):
160 '''Partial function application
163 '''Partial function application
161
164
162 Returns a new function that is the partial application of args and kwargs
165 Returns a new function that is the partial application of args and kwargs
163 to func. For example,
166 to func. For example,
164
167
165 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
168 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)'''
166 assert callable(func)
169 assert callable(func)
167 def closure(*a, **kw):
170 def closure(*a, **kw):
168 return func(*(args + a), **kw)
171 return func(*(args + a), **kw)
169 return closure
172 return closure
170
173
171 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
174 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
172 '''Wrap the command named `command' in table
175 '''Wrap the command named `command' in table
173
176
174 Replace command in the command table with wrapper. The wrapped command will
177 Replace command in the command table with wrapper. The wrapped command will
175 be inserted into the command table specified by the table argument.
178 be inserted into the command table specified by the table argument.
176
179
177 The wrapper will be called like
180 The wrapper will be called like
178
181
179 wrapper(orig, *args, **kwargs)
182 wrapper(orig, *args, **kwargs)
180
183
181 where orig is the original (wrapped) function, and *args, **kwargs
184 where orig is the original (wrapped) function, and *args, **kwargs
182 are the arguments passed to it.
185 are the arguments passed to it.
183
186
184 Optionally append to the command synopsis and docstring, used for help.
187 Optionally append to the command synopsis and docstring, used for help.
185 For example, if your extension wraps the ``bookmarks`` command to add the
188 For example, if your extension wraps the ``bookmarks`` command to add the
186 flags ``--remote`` and ``--all`` you might call this function like so:
189 flags ``--remote`` and ``--all`` you might call this function like so:
187
190
188 synopsis = ' [-a] [--remote]'
191 synopsis = ' [-a] [--remote]'
189 docstring = """
192 docstring = """
190
193
191 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
194 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
192 flags to the bookmarks command. Either flag will show the remote bookmarks
195 flags to the bookmarks command. Either flag will show the remote bookmarks
193 known to the repository; ``--remote`` will also supress the output of the
196 known to the repository; ``--remote`` will also supress the output of the
194 local bookmarks.
197 local bookmarks.
195 """
198 """
196
199
197 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
200 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
198 synopsis, docstring)
201 synopsis, docstring)
199 '''
202 '''
200 assert callable(wrapper)
203 assert callable(wrapper)
201 aliases, entry = cmdutil.findcmd(command, table)
204 aliases, entry = cmdutil.findcmd(command, table)
202 for alias, e in table.iteritems():
205 for alias, e in table.iteritems():
203 if e is entry:
206 if e is entry:
204 key = alias
207 key = alias
205 break
208 break
206
209
207 origfn = entry[0]
210 origfn = entry[0]
208 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
211 wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
209
212
210 wrap.__module__ = getattr(origfn, '__module__')
213 wrap.__module__ = getattr(origfn, '__module__')
211
214
212 doc = getattr(origfn, '__doc__')
215 doc = getattr(origfn, '__doc__')
213 if docstring is not None:
216 if docstring is not None:
214 doc += docstring
217 doc += docstring
215 wrap.__doc__ = doc
218 wrap.__doc__ = doc
216
219
217 newentry = list(entry)
220 newentry = list(entry)
218 newentry[0] = wrap
221 newentry[0] = wrap
219 if synopsis is not None:
222 if synopsis is not None:
220 newentry[2] += synopsis
223 newentry[2] += synopsis
221 table[key] = tuple(newentry)
224 table[key] = tuple(newentry)
222 return entry
225 return entry
223
226
224 def wrapfunction(container, funcname, wrapper):
227 def wrapfunction(container, funcname, wrapper):
225 '''Wrap the function named funcname in container
228 '''Wrap the function named funcname in container
226
229
227 Replace the funcname member in the given container with the specified
230 Replace the funcname member in the given container with the specified
228 wrapper. The container is typically a module, class, or instance.
231 wrapper. The container is typically a module, class, or instance.
229
232
230 The wrapper will be called like
233 The wrapper will be called like
231
234
232 wrapper(orig, *args, **kwargs)
235 wrapper(orig, *args, **kwargs)
233
236
234 where orig is the original (wrapped) function, and *args, **kwargs
237 where orig is the original (wrapped) function, and *args, **kwargs
235 are the arguments passed to it.
238 are the arguments passed to it.
236
239
237 Wrapping methods of the repository object is not recommended since
240 Wrapping methods of the repository object is not recommended since
238 it conflicts with extensions that extend the repository by
241 it conflicts with extensions that extend the repository by
239 subclassing. All extensions that need to extend methods of
242 subclassing. All extensions that need to extend methods of
240 localrepository should use this subclassing trick: namely,
243 localrepository should use this subclassing trick: namely,
241 reposetup() should look like
244 reposetup() should look like
242
245
243 def reposetup(ui, repo):
246 def reposetup(ui, repo):
244 class myrepo(repo.__class__):
247 class myrepo(repo.__class__):
245 def whatever(self, *args, **kwargs):
248 def whatever(self, *args, **kwargs):
246 [...extension stuff...]
249 [...extension stuff...]
247 super(myrepo, self).whatever(*args, **kwargs)
250 super(myrepo, self).whatever(*args, **kwargs)
248 [...extension stuff...]
251 [...extension stuff...]
249
252
250 repo.__class__ = myrepo
253 repo.__class__ = myrepo
251
254
252 In general, combining wrapfunction() with subclassing does not
255 In general, combining wrapfunction() with subclassing does not
253 work. Since you cannot control what other extensions are loaded by
256 work. Since you cannot control what other extensions are loaded by
254 your end users, you should play nicely with others by using the
257 your end users, you should play nicely with others by using the
255 subclass trick.
258 subclass trick.
256 '''
259 '''
257 assert callable(wrapper)
260 assert callable(wrapper)
258
261
259 origfn = getattr(container, funcname)
262 origfn = getattr(container, funcname)
260 assert callable(origfn)
263 assert callable(origfn)
261 setattr(container, funcname, bind(wrapper, origfn))
264 setattr(container, funcname, bind(wrapper, origfn))
262 return origfn
265 return origfn
263
266
264 def _disabledpaths(strip_init=False):
267 def _disabledpaths(strip_init=False):
265 '''find paths of disabled extensions. returns a dict of {name: path}
268 '''find paths of disabled extensions. returns a dict of {name: path}
266 removes /__init__.py from packages if strip_init is True'''
269 removes /__init__.py from packages if strip_init is True'''
267 import hgext
270 import hgext
268 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
271 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
269 try: # might not be a filesystem path
272 try: # might not be a filesystem path
270 files = os.listdir(extpath)
273 files = os.listdir(extpath)
271 except OSError:
274 except OSError:
272 return {}
275 return {}
273
276
274 exts = {}
277 exts = {}
275 for e in files:
278 for e in files:
276 if e.endswith('.py'):
279 if e.endswith('.py'):
277 name = e.rsplit('.', 1)[0]
280 name = e.rsplit('.', 1)[0]
278 path = os.path.join(extpath, e)
281 path = os.path.join(extpath, e)
279 else:
282 else:
280 name = e
283 name = e
281 path = os.path.join(extpath, e, '__init__.py')
284 path = os.path.join(extpath, e, '__init__.py')
282 if not os.path.exists(path):
285 if not os.path.exists(path):
283 continue
286 continue
284 if strip_init:
287 if strip_init:
285 path = os.path.dirname(path)
288 path = os.path.dirname(path)
286 if name in exts or name in _order or name == '__init__':
289 if name in exts or name in _order or name == '__init__':
287 continue
290 continue
288 exts[name] = path
291 exts[name] = path
289 return exts
292 return exts
290
293
291 def _moduledoc(file):
294 def _moduledoc(file):
292 '''return the top-level python documentation for the given file
295 '''return the top-level python documentation for the given file
293
296
294 Loosely inspired by pydoc.source_synopsis(), but rewritten to
297 Loosely inspired by pydoc.source_synopsis(), but rewritten to
295 handle triple quotes and to return the whole text instead of just
298 handle triple quotes and to return the whole text instead of just
296 the synopsis'''
299 the synopsis'''
297 result = []
300 result = []
298
301
299 line = file.readline()
302 line = file.readline()
300 while line[:1] == '#' or not line.strip():
303 while line[:1] == '#' or not line.strip():
301 line = file.readline()
304 line = file.readline()
302 if not line:
305 if not line:
303 break
306 break
304
307
305 start = line[:3]
308 start = line[:3]
306 if start == '"""' or start == "'''":
309 if start == '"""' or start == "'''":
307 line = line[3:]
310 line = line[3:]
308 while line:
311 while line:
309 if line.rstrip().endswith(start):
312 if line.rstrip().endswith(start):
310 line = line.split(start)[0]
313 line = line.split(start)[0]
311 if line:
314 if line:
312 result.append(line)
315 result.append(line)
313 break
316 break
314 elif not line:
317 elif not line:
315 return None # unmatched delimiter
318 return None # unmatched delimiter
316 result.append(line)
319 result.append(line)
317 line = file.readline()
320 line = file.readline()
318 else:
321 else:
319 return None
322 return None
320
323
321 return ''.join(result)
324 return ''.join(result)
322
325
323 def _disabledhelp(path):
326 def _disabledhelp(path):
324 '''retrieve help synopsis of a disabled extension (without importing)'''
327 '''retrieve help synopsis of a disabled extension (without importing)'''
325 try:
328 try:
326 file = open(path)
329 file = open(path)
327 except IOError:
330 except IOError:
328 return
331 return
329 else:
332 else:
330 doc = _moduledoc(file)
333 doc = _moduledoc(file)
331 file.close()
334 file.close()
332
335
333 if doc: # extracting localized synopsis
336 if doc: # extracting localized synopsis
334 return gettext(doc).splitlines()[0]
337 return gettext(doc).splitlines()[0]
335 else:
338 else:
336 return _('(no help text available)')
339 return _('(no help text available)')
337
340
338 def disabled():
341 def disabled():
339 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
342 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
340 try:
343 try:
341 from hgext import __index__
344 from hgext import __index__
342 return dict((name, gettext(desc))
345 return dict((name, gettext(desc))
343 for name, desc in __index__.docs.iteritems()
346 for name, desc in __index__.docs.iteritems()
344 if name not in _order)
347 if name not in _order)
345 except (ImportError, AttributeError):
348 except (ImportError, AttributeError):
346 pass
349 pass
347
350
348 paths = _disabledpaths()
351 paths = _disabledpaths()
349 if not paths:
352 if not paths:
350 return {}
353 return {}
351
354
352 exts = {}
355 exts = {}
353 for name, path in paths.iteritems():
356 for name, path in paths.iteritems():
354 doc = _disabledhelp(path)
357 doc = _disabledhelp(path)
355 if doc:
358 if doc:
356 exts[name] = doc
359 exts[name] = doc
357
360
358 return exts
361 return exts
359
362
360 def disabledext(name):
363 def disabledext(name):
361 '''find a specific disabled extension from hgext. returns desc'''
364 '''find a specific disabled extension from hgext. returns desc'''
362 try:
365 try:
363 from hgext import __index__
366 from hgext import __index__
364 if name in _order: # enabled
367 if name in _order: # enabled
365 return
368 return
366 else:
369 else:
367 return gettext(__index__.docs.get(name))
370 return gettext(__index__.docs.get(name))
368 except (ImportError, AttributeError):
371 except (ImportError, AttributeError):
369 pass
372 pass
370
373
371 paths = _disabledpaths()
374 paths = _disabledpaths()
372 if name in paths:
375 if name in paths:
373 return _disabledhelp(paths[name])
376 return _disabledhelp(paths[name])
374
377
375 def disabledcmd(ui, cmd, strict=False):
378 def disabledcmd(ui, cmd, strict=False):
376 '''import disabled extensions until cmd is found.
379 '''import disabled extensions until cmd is found.
377 returns (cmdname, extname, module)'''
380 returns (cmdname, extname, module)'''
378
381
379 paths = _disabledpaths(strip_init=True)
382 paths = _disabledpaths(strip_init=True)
380 if not paths:
383 if not paths:
381 raise error.UnknownCommand(cmd)
384 raise error.UnknownCommand(cmd)
382
385
383 def findcmd(cmd, name, path):
386 def findcmd(cmd, name, path):
384 try:
387 try:
385 mod = loadpath(path, 'hgext.%s' % name)
388 mod = loadpath(path, 'hgext.%s' % name)
386 except Exception:
389 except Exception:
387 return
390 return
388 try:
391 try:
389 aliases, entry = cmdutil.findcmd(cmd,
392 aliases, entry = cmdutil.findcmd(cmd,
390 getattr(mod, 'cmdtable', {}), strict)
393 getattr(mod, 'cmdtable', {}), strict)
391 except (error.AmbiguousCommand, error.UnknownCommand):
394 except (error.AmbiguousCommand, error.UnknownCommand):
392 return
395 return
393 except Exception:
396 except Exception:
394 ui.warn(_('warning: error finding commands in %s\n') % path)
397 ui.warn(_('warning: error finding commands in %s\n') % path)
395 ui.traceback()
398 ui.traceback()
396 return
399 return
397 for c in aliases:
400 for c in aliases:
398 if c.startswith(cmd):
401 if c.startswith(cmd):
399 cmd = c
402 cmd = c
400 break
403 break
401 else:
404 else:
402 cmd = aliases[0]
405 cmd = aliases[0]
403 return (cmd, name, mod)
406 return (cmd, name, mod)
404
407
405 ext = None
408 ext = None
406 # first, search for an extension with the same name as the command
409 # first, search for an extension with the same name as the command
407 path = paths.pop(cmd, None)
410 path = paths.pop(cmd, None)
408 if path:
411 if path:
409 ext = findcmd(cmd, cmd, path)
412 ext = findcmd(cmd, cmd, path)
410 if not ext:
413 if not ext:
411 # otherwise, interrogate each extension until there's a match
414 # otherwise, interrogate each extension until there's a match
412 for name, path in paths.iteritems():
415 for name, path in paths.iteritems():
413 ext = findcmd(cmd, name, path)
416 ext = findcmd(cmd, name, path)
414 if ext:
417 if ext:
415 break
418 break
416 if ext and 'DEPRECATED' not in ext.__doc__:
419 if ext and 'DEPRECATED' not in ext.__doc__:
417 return ext
420 return ext
418
421
419 raise error.UnknownCommand(cmd)
422 raise error.UnknownCommand(cmd)
420
423
421 def enabled(shortname=True):
424 def enabled(shortname=True):
422 '''return a dict of {name: desc} of extensions'''
425 '''return a dict of {name: desc} of extensions'''
423 exts = {}
426 exts = {}
424 for ename, ext in extensions():
427 for ename, ext in extensions():
425 doc = (gettext(ext.__doc__) or _('(no help text available)'))
428 doc = (gettext(ext.__doc__) or _('(no help text available)'))
426 if shortname:
429 if shortname:
427 ename = ename.split('.')[-1]
430 ename = ename.split('.')[-1]
428 exts[ename] = doc.splitlines()[0].strip()
431 exts[ename] = doc.splitlines()[0].strip()
429
432
430 return exts
433 return exts
431
434
432 def moduleversion(module):
435 def moduleversion(module):
433 '''return version information from given module as a string'''
436 '''return version information from given module as a string'''
434 if (util.safehasattr(module, 'getversion')
437 if (util.safehasattr(module, 'getversion')
435 and callable(module.getversion)):
438 and callable(module.getversion)):
436 version = module.getversion()
439 version = module.getversion()
437 elif util.safehasattr(module, '__version__'):
440 elif util.safehasattr(module, '__version__'):
438 version = module.__version__
441 version = module.__version__
439 else:
442 else:
440 version = ''
443 version = ''
441 if isinstance(version, (list, tuple)):
444 if isinstance(version, (list, tuple)):
442 version = '.'.join(str(o) for o in version)
445 version = '.'.join(str(o) for o in version)
443 return version
446 return version
@@ -1,17 +1,46
1 $ echo 'raise Exception("bit bucket overflow")' > badext.py
1 $ echo 'raise Exception("bit bucket overflow")' > badext.py
2 $ abspath=`pwd`/badext.py
2 $ abspath=`pwd`/badext.py
3
3
4 $ cat <<EOF >> $HGRCPATH
4 $ cat <<EOF >> $HGRCPATH
5 > [extensions]
5 > [extensions]
6 > gpg =
6 > gpg =
7 > hgext.gpg =
7 > hgext.gpg =
8 > badext = $abspath
8 > badext = $abspath
9 > badext2 =
9 > badext2 =
10 > EOF
10 > EOF
11
11
12 $ hg -q help help
12 $ hg -q help help
13 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
13 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
14 *** failed to import extension badext2: No module named badext2
14 *** failed to import extension badext2: No module named badext2
15 hg help [-ec] [TOPIC]
15 hg help [-ec] [TOPIC]
16
16
17 show help for a given topic or a help overview
17 show help for a given topic or a help overview
18
19 show traceback
20
21 $ hg -q help help --traceback 2>&1 | grep -v '^ '
22 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
23 Traceback (most recent call last):
24 Exception: bit bucket overflow
25 *** failed to import extension badext2: No module named badext2
26 Traceback (most recent call last):
27 ImportError: No module named badext2
28 hg help [-ec] [TOPIC]
29
30 show help for a given topic or a help overview
31
32 show traceback for ImportError of hgext.name if debug is set
33 (note that --debug option isn't applied yet when loading extensions)
34
35 $ hg help help --traceback --config ui.debug=True 2>&1 \
36 > | grep -v '^ ' | head -n10
37 *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
38 Traceback (most recent call last):
39 Exception: bit bucket overflow
40 could not import hgext.badext2 (No module named badext2): trying badext2
41 Traceback (most recent call last):
42 ImportError: No module named badext2
43 *** failed to import extension badext2: No module named badext2
44 Traceback (most recent call last):
45 ImportError: No module named badext2
46 hg help [-ec] [TOPIC]
General Comments 0
You need to be logged in to leave comments. Login now