##// END OF EJS Templates
extensions: prevent a crash on py3 when testing a bad extension minimum...
Matt Harbison -
r48828:a9bedc56 default
parent child Browse files
Show More
@@ -1,953 +1,957 b''
1 # extensions.py - extension handling for mercurial
1 # extensions.py - extension handling for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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 from .pycompat import (
21 from .pycompat import (
22 getattr,
22 getattr,
23 open,
23 open,
24 setattr,
24 setattr,
25 )
25 )
26
26
27 from . import (
27 from . import (
28 cmdutil,
28 cmdutil,
29 configitems,
29 configitems,
30 error,
30 error,
31 pycompat,
31 pycompat,
32 util,
32 util,
33 )
33 )
34
34
35 from .utils import stringutil
35 from .utils import stringutil
36
36
37 _extensions = {}
37 _extensions = {}
38 _disabledextensions = {}
38 _disabledextensions = {}
39 _aftercallbacks = {}
39 _aftercallbacks = {}
40 _order = []
40 _order = []
41 _builtin = {
41 _builtin = {
42 b'hbisect',
42 b'hbisect',
43 b'bookmarks',
43 b'bookmarks',
44 b'color',
44 b'color',
45 b'parentrevspec',
45 b'parentrevspec',
46 b'progress',
46 b'progress',
47 b'interhg',
47 b'interhg',
48 b'inotify',
48 b'inotify',
49 b'hgcia',
49 b'hgcia',
50 b'shelve',
50 b'shelve',
51 }
51 }
52
52
53
53
54 def extensions(ui=None):
54 def extensions(ui=None):
55 if ui:
55 if ui:
56
56
57 def enabled(name):
57 def enabled(name):
58 for format in [b'%s', b'hgext.%s']:
58 for format in [b'%s', b'hgext.%s']:
59 conf = ui.config(b'extensions', format % name)
59 conf = ui.config(b'extensions', format % name)
60 if conf is not None and not conf.startswith(b'!'):
60 if conf is not None and not conf.startswith(b'!'):
61 return True
61 return True
62
62
63 else:
63 else:
64 enabled = lambda name: True
64 enabled = lambda name: True
65 for name in _order:
65 for name in _order:
66 module = _extensions[name]
66 module = _extensions[name]
67 if module and enabled(name):
67 if module and enabled(name):
68 yield name, module
68 yield name, module
69
69
70
70
71 def find(name):
71 def find(name):
72 '''return module with given extension name'''
72 '''return module with given extension name'''
73 mod = None
73 mod = None
74 try:
74 try:
75 mod = _extensions[name]
75 mod = _extensions[name]
76 except KeyError:
76 except KeyError:
77 for k, v in pycompat.iteritems(_extensions):
77 for k, v in pycompat.iteritems(_extensions):
78 if k.endswith(b'.' + name) or k.endswith(b'/' + name):
78 if k.endswith(b'.' + name) or k.endswith(b'/' + name):
79 mod = v
79 mod = v
80 break
80 break
81 if not mod:
81 if not mod:
82 raise KeyError(name)
82 raise KeyError(name)
83 return mod
83 return mod
84
84
85
85
86 def loadpath(path, module_name):
86 def loadpath(path, module_name):
87 module_name = module_name.replace(b'.', b'_')
87 module_name = module_name.replace(b'.', b'_')
88 path = util.normpath(util.expandpath(path))
88 path = util.normpath(util.expandpath(path))
89 module_name = pycompat.fsdecode(module_name)
89 module_name = pycompat.fsdecode(module_name)
90 path = pycompat.fsdecode(path)
90 path = pycompat.fsdecode(path)
91 if os.path.isdir(path):
91 if os.path.isdir(path):
92 # module/__init__.py style
92 # module/__init__.py style
93 d, f = os.path.split(path)
93 d, f = os.path.split(path)
94 fd, fpath, desc = imp.find_module(f, [d])
94 fd, fpath, desc = imp.find_module(f, [d])
95 # When https://github.com/python/typeshed/issues/3466 is fixed
95 # When https://github.com/python/typeshed/issues/3466 is fixed
96 # and in a pytype release we can drop this disable.
96 # and in a pytype release we can drop this disable.
97 return imp.load_module(
97 return imp.load_module(
98 module_name, fd, fpath, desc # pytype: disable=wrong-arg-types
98 module_name, fd, fpath, desc # pytype: disable=wrong-arg-types
99 )
99 )
100 else:
100 else:
101 try:
101 try:
102 return imp.load_source(module_name, path)
102 return imp.load_source(module_name, path)
103 except IOError as exc:
103 except IOError as exc:
104 if not exc.filename:
104 if not exc.filename:
105 exc.filename = path # python does not fill this
105 exc.filename = path # python does not fill this
106 raise
106 raise
107
107
108
108
109 def _importh(name):
109 def _importh(name):
110 """import and return the <name> module"""
110 """import and return the <name> module"""
111 mod = __import__(pycompat.sysstr(name))
111 mod = __import__(pycompat.sysstr(name))
112 components = name.split(b'.')
112 components = name.split(b'.')
113 for comp in components[1:]:
113 for comp in components[1:]:
114 mod = getattr(mod, comp)
114 mod = getattr(mod, comp)
115 return mod
115 return mod
116
116
117
117
118 def _importext(name, path=None, reportfunc=None):
118 def _importext(name, path=None, reportfunc=None):
119 if path:
119 if path:
120 # the module will be loaded in sys.modules
120 # the module will be loaded in sys.modules
121 # choose an unique name so that it doesn't
121 # choose an unique name so that it doesn't
122 # conflicts with other modules
122 # conflicts with other modules
123 mod = loadpath(path, b'hgext.%s' % name)
123 mod = loadpath(path, b'hgext.%s' % name)
124 else:
124 else:
125 try:
125 try:
126 mod = _importh(b"hgext.%s" % name)
126 mod = _importh(b"hgext.%s" % name)
127 except ImportError as err:
127 except ImportError as err:
128 if reportfunc:
128 if reportfunc:
129 reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
129 reportfunc(err, b"hgext.%s" % name, b"hgext3rd.%s" % name)
130 try:
130 try:
131 mod = _importh(b"hgext3rd.%s" % name)
131 mod = _importh(b"hgext3rd.%s" % name)
132 except ImportError as err:
132 except ImportError as err:
133 if reportfunc:
133 if reportfunc:
134 reportfunc(err, b"hgext3rd.%s" % name, name)
134 reportfunc(err, b"hgext3rd.%s" % name, name)
135 mod = _importh(name)
135 mod = _importh(name)
136 return mod
136 return mod
137
137
138
138
139 def _reportimporterror(ui, err, failed, next):
139 def _reportimporterror(ui, err, failed, next):
140 # note: this ui.log happens before --debug is processed,
140 # note: this ui.log happens before --debug is processed,
141 # Use --config ui.debug=1 to see them.
141 # Use --config ui.debug=1 to see them.
142 ui.log(
142 ui.log(
143 b'extension',
143 b'extension',
144 b' - could not import %s (%s): trying %s\n',
144 b' - could not import %s (%s): trying %s\n',
145 failed,
145 failed,
146 stringutil.forcebytestr(err),
146 stringutil.forcebytestr(err),
147 next,
147 next,
148 )
148 )
149 if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
149 if ui.debugflag and ui.configbool(b'devel', b'debug.extensions'):
150 ui.traceback()
150 ui.traceback()
151
151
152
152
153 def _rejectunicode(name, xs):
153 def _rejectunicode(name, xs):
154 if isinstance(xs, (list, set, tuple)):
154 if isinstance(xs, (list, set, tuple)):
155 for x in xs:
155 for x in xs:
156 _rejectunicode(name, x)
156 _rejectunicode(name, x)
157 elif isinstance(xs, dict):
157 elif isinstance(xs, dict):
158 for k, v in xs.items():
158 for k, v in xs.items():
159 _rejectunicode(name, k)
159 _rejectunicode(name, k)
160 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
160 _rejectunicode(b'%s.%s' % (name, stringutil.forcebytestr(k)), v)
161 elif isinstance(xs, type(u'')):
161 elif isinstance(xs, type(u'')):
162 raise error.ProgrammingError(
162 raise error.ProgrammingError(
163 b"unicode %r found in %s" % (xs, name),
163 b"unicode %r found in %s" % (xs, name),
164 hint=b"use b'' to make it byte string",
164 hint=b"use b'' to make it byte string",
165 )
165 )
166
166
167
167
168 # attributes set by registrar.command
168 # attributes set by registrar.command
169 _cmdfuncattrs = (b'norepo', b'optionalrepo', b'inferrepo')
169 _cmdfuncattrs = (b'norepo', b'optionalrepo', b'inferrepo')
170
170
171
171
172 def _validatecmdtable(ui, cmdtable):
172 def _validatecmdtable(ui, cmdtable):
173 """Check if extension commands have required attributes"""
173 """Check if extension commands have required attributes"""
174 for c, e in pycompat.iteritems(cmdtable):
174 for c, e in pycompat.iteritems(cmdtable):
175 f = e[0]
175 f = e[0]
176 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
176 missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
177 if not missing:
177 if not missing:
178 continue
178 continue
179 raise error.ProgrammingError(
179 raise error.ProgrammingError(
180 b'missing attributes: %s' % b', '.join(missing),
180 b'missing attributes: %s' % b', '.join(missing),
181 hint=b"use @command decorator to register '%s'" % c,
181 hint=b"use @command decorator to register '%s'" % c,
182 )
182 )
183
183
184
184
185 def _validatetables(ui, mod):
185 def _validatetables(ui, mod):
186 """Sanity check for loadable tables provided by extension module"""
186 """Sanity check for loadable tables provided by extension module"""
187 for t in [b'cmdtable', b'colortable', b'configtable']:
187 for t in [b'cmdtable', b'colortable', b'configtable']:
188 _rejectunicode(t, getattr(mod, t, {}))
188 _rejectunicode(t, getattr(mod, t, {}))
189 for t in [
189 for t in [
190 b'filesetpredicate',
190 b'filesetpredicate',
191 b'internalmerge',
191 b'internalmerge',
192 b'revsetpredicate',
192 b'revsetpredicate',
193 b'templatefilter',
193 b'templatefilter',
194 b'templatefunc',
194 b'templatefunc',
195 b'templatekeyword',
195 b'templatekeyword',
196 ]:
196 ]:
197 o = getattr(mod, t, None)
197 o = getattr(mod, t, None)
198 if o:
198 if o:
199 _rejectunicode(t, o._table)
199 _rejectunicode(t, o._table)
200 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
200 _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
201
201
202
202
203 def load(ui, name, path, loadingtime=None):
203 def load(ui, name, path, loadingtime=None):
204 if name.startswith(b'hgext.') or name.startswith(b'hgext/'):
204 if name.startswith(b'hgext.') or name.startswith(b'hgext/'):
205 shortname = name[6:]
205 shortname = name[6:]
206 else:
206 else:
207 shortname = name
207 shortname = name
208 if shortname in _builtin:
208 if shortname in _builtin:
209 return None
209 return None
210 if shortname in _extensions:
210 if shortname in _extensions:
211 return _extensions[shortname]
211 return _extensions[shortname]
212 ui.log(b'extension', b' - loading extension: %s\n', shortname)
212 ui.log(b'extension', b' - loading extension: %s\n', shortname)
213 _extensions[shortname] = None
213 _extensions[shortname] = None
214 with util.timedcm('load extension %s', shortname) as stats:
214 with util.timedcm('load extension %s', shortname) as stats:
215 mod = _importext(name, path, bind(_reportimporterror, ui))
215 mod = _importext(name, path, bind(_reportimporterror, ui))
216 ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats)
216 ui.log(b'extension', b' > %s extension loaded in %s\n', shortname, stats)
217 if loadingtime is not None:
217 if loadingtime is not None:
218 loadingtime[shortname] += stats.elapsed
218 loadingtime[shortname] += stats.elapsed
219
219
220 # Before we do anything with the extension, check against minimum stated
220 # Before we do anything with the extension, check against minimum stated
221 # compatibility. This gives extension authors a mechanism to have their
221 # compatibility. This gives extension authors a mechanism to have their
222 # extensions short circuit when loaded with a known incompatible version
222 # extensions short circuit when loaded with a known incompatible version
223 # of Mercurial.
223 # of Mercurial.
224 minver = getattr(mod, 'minimumhgversion', None)
224 minver = getattr(mod, 'minimumhgversion', None)
225 if minver:
225 if minver:
226 curver = util.versiontuple(n=2)
226 curver = util.versiontuple(n=2)
227 extmin = util.versiontuple(minver, 2)
227
228
228 if None in curver or util.versiontuple(minver, 2) > curver:
229 if None in extmin:
230 extmin = (extmin[0] or 0, extmin[1] or 0)
231
232 if None in curver or extmin > curver:
229 msg = _(
233 msg = _(
230 b'(third party extension %s requires version %s or newer '
234 b'(third party extension %s requires version %s or newer '
231 b'of Mercurial (current: %s); disabling)\n'
235 b'of Mercurial (current: %s); disabling)\n'
232 )
236 )
233 ui.warn(msg % (shortname, minver, util.version()))
237 ui.warn(msg % (shortname, minver, util.version()))
234 return
238 return
235 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
239 ui.log(b'extension', b' - validating extension tables: %s\n', shortname)
236 _validatetables(ui, mod)
240 _validatetables(ui, mod)
237
241
238 _extensions[shortname] = mod
242 _extensions[shortname] = mod
239 _order.append(shortname)
243 _order.append(shortname)
240 ui.log(
244 ui.log(
241 b'extension', b' - invoking registered callbacks: %s\n', shortname
245 b'extension', b' - invoking registered callbacks: %s\n', shortname
242 )
246 )
243 with util.timedcm('callbacks extension %s', shortname) as stats:
247 with util.timedcm('callbacks extension %s', shortname) as stats:
244 for fn in _aftercallbacks.get(shortname, []):
248 for fn in _aftercallbacks.get(shortname, []):
245 fn(loaded=True)
249 fn(loaded=True)
246 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
250 ui.log(b'extension', b' > callbacks completed in %s\n', stats)
247 return mod
251 return mod
248
252
249
253
250 def _runuisetup(name, ui):
254 def _runuisetup(name, ui):
251 uisetup = getattr(_extensions[name], 'uisetup', None)
255 uisetup = getattr(_extensions[name], 'uisetup', None)
252 if uisetup:
256 if uisetup:
253 try:
257 try:
254 uisetup(ui)
258 uisetup(ui)
255 except Exception as inst:
259 except Exception as inst:
256 ui.traceback(force=True)
260 ui.traceback(force=True)
257 msg = stringutil.forcebytestr(inst)
261 msg = stringutil.forcebytestr(inst)
258 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
262 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
259 return False
263 return False
260 return True
264 return True
261
265
262
266
263 def _runextsetup(name, ui):
267 def _runextsetup(name, ui):
264 extsetup = getattr(_extensions[name], 'extsetup', None)
268 extsetup = getattr(_extensions[name], 'extsetup', None)
265 if extsetup:
269 if extsetup:
266 try:
270 try:
267 extsetup(ui)
271 extsetup(ui)
268 except Exception as inst:
272 except Exception as inst:
269 ui.traceback(force=True)
273 ui.traceback(force=True)
270 msg = stringutil.forcebytestr(inst)
274 msg = stringutil.forcebytestr(inst)
271 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
275 ui.warn(_(b"*** failed to set up extension %s: %s\n") % (name, msg))
272 return False
276 return False
273 return True
277 return True
274
278
275
279
276 def loadall(ui, whitelist=None):
280 def loadall(ui, whitelist=None):
277 loadingtime = collections.defaultdict(int)
281 loadingtime = collections.defaultdict(int)
278 result = ui.configitems(b"extensions")
282 result = ui.configitems(b"extensions")
279 if whitelist is not None:
283 if whitelist is not None:
280 result = [(k, v) for (k, v) in result if k in whitelist]
284 result = [(k, v) for (k, v) in result if k in whitelist]
281 newindex = len(_order)
285 newindex = len(_order)
282 ui.log(
286 ui.log(
283 b'extension',
287 b'extension',
284 b'loading %sextensions\n',
288 b'loading %sextensions\n',
285 b'additional ' if newindex else b'',
289 b'additional ' if newindex else b'',
286 )
290 )
287 ui.log(b'extension', b'- processing %d entries\n', len(result))
291 ui.log(b'extension', b'- processing %d entries\n', len(result))
288 with util.timedcm('load all extensions') as stats:
292 with util.timedcm('load all extensions') as stats:
289 for (name, path) in result:
293 for (name, path) in result:
290 if path:
294 if path:
291 if path[0:1] == b'!':
295 if path[0:1] == b'!':
292 if name not in _disabledextensions:
296 if name not in _disabledextensions:
293 ui.log(
297 ui.log(
294 b'extension',
298 b'extension',
295 b' - skipping disabled extension: %s\n',
299 b' - skipping disabled extension: %s\n',
296 name,
300 name,
297 )
301 )
298 _disabledextensions[name] = path[1:]
302 _disabledextensions[name] = path[1:]
299 continue
303 continue
300 try:
304 try:
301 load(ui, name, path, loadingtime)
305 load(ui, name, path, loadingtime)
302 except Exception as inst:
306 except Exception as inst:
303 msg = stringutil.forcebytestr(inst)
307 msg = stringutil.forcebytestr(inst)
304 if path:
308 if path:
305 ui.warn(
309 ui.warn(
306 _(b"*** failed to import extension %s from %s: %s\n")
310 _(b"*** failed to import extension %s from %s: %s\n")
307 % (name, path, msg)
311 % (name, path, msg)
308 )
312 )
309 else:
313 else:
310 ui.warn(
314 ui.warn(
311 _(b"*** failed to import extension %s: %s\n")
315 _(b"*** failed to import extension %s: %s\n")
312 % (name, msg)
316 % (name, msg)
313 )
317 )
314 if isinstance(inst, error.Hint) and inst.hint:
318 if isinstance(inst, error.Hint) and inst.hint:
315 ui.warn(_(b"*** (%s)\n") % inst.hint)
319 ui.warn(_(b"*** (%s)\n") % inst.hint)
316 ui.traceback()
320 ui.traceback()
317
321
318 ui.log(
322 ui.log(
319 b'extension',
323 b'extension',
320 b'> loaded %d extensions, total time %s\n',
324 b'> loaded %d extensions, total time %s\n',
321 len(_order) - newindex,
325 len(_order) - newindex,
322 stats,
326 stats,
323 )
327 )
324 # list of (objname, loadermod, loadername) tuple:
328 # list of (objname, loadermod, loadername) tuple:
325 # - objname is the name of an object in extension module,
329 # - objname is the name of an object in extension module,
326 # from which extra information is loaded
330 # from which extra information is loaded
327 # - loadermod is the module where loader is placed
331 # - loadermod is the module where loader is placed
328 # - loadername is the name of the function,
332 # - loadername is the name of the function,
329 # which takes (ui, extensionname, extraobj) arguments
333 # which takes (ui, extensionname, extraobj) arguments
330 #
334 #
331 # This one is for the list of item that must be run before running any setup
335 # This one is for the list of item that must be run before running any setup
332 earlyextraloaders = [
336 earlyextraloaders = [
333 (b'configtable', configitems, b'loadconfigtable'),
337 (b'configtable', configitems, b'loadconfigtable'),
334 ]
338 ]
335
339
336 ui.log(b'extension', b'- loading configtable attributes\n')
340 ui.log(b'extension', b'- loading configtable attributes\n')
337 _loadextra(ui, newindex, earlyextraloaders)
341 _loadextra(ui, newindex, earlyextraloaders)
338
342
339 broken = set()
343 broken = set()
340 ui.log(b'extension', b'- executing uisetup hooks\n')
344 ui.log(b'extension', b'- executing uisetup hooks\n')
341 with util.timedcm('all uisetup') as alluisetupstats:
345 with util.timedcm('all uisetup') as alluisetupstats:
342 for name in _order[newindex:]:
346 for name in _order[newindex:]:
343 ui.log(b'extension', b' - running uisetup for %s\n', name)
347 ui.log(b'extension', b' - running uisetup for %s\n', name)
344 with util.timedcm('uisetup %s', name) as stats:
348 with util.timedcm('uisetup %s', name) as stats:
345 if not _runuisetup(name, ui):
349 if not _runuisetup(name, ui):
346 ui.log(
350 ui.log(
347 b'extension',
351 b'extension',
348 b' - the %s extension uisetup failed\n',
352 b' - the %s extension uisetup failed\n',
349 name,
353 name,
350 )
354 )
351 broken.add(name)
355 broken.add(name)
352 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
356 ui.log(b'extension', b' > uisetup for %s took %s\n', name, stats)
353 loadingtime[name] += stats.elapsed
357 loadingtime[name] += stats.elapsed
354 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
358 ui.log(b'extension', b'> all uisetup took %s\n', alluisetupstats)
355
359
356 ui.log(b'extension', b'- executing extsetup hooks\n')
360 ui.log(b'extension', b'- executing extsetup hooks\n')
357 with util.timedcm('all extsetup') as allextetupstats:
361 with util.timedcm('all extsetup') as allextetupstats:
358 for name in _order[newindex:]:
362 for name in _order[newindex:]:
359 if name in broken:
363 if name in broken:
360 continue
364 continue
361 ui.log(b'extension', b' - running extsetup for %s\n', name)
365 ui.log(b'extension', b' - running extsetup for %s\n', name)
362 with util.timedcm('extsetup %s', name) as stats:
366 with util.timedcm('extsetup %s', name) as stats:
363 if not _runextsetup(name, ui):
367 if not _runextsetup(name, ui):
364 ui.log(
368 ui.log(
365 b'extension',
369 b'extension',
366 b' - the %s extension extsetup failed\n',
370 b' - the %s extension extsetup failed\n',
367 name,
371 name,
368 )
372 )
369 broken.add(name)
373 broken.add(name)
370 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
374 ui.log(b'extension', b' > extsetup for %s took %s\n', name, stats)
371 loadingtime[name] += stats.elapsed
375 loadingtime[name] += stats.elapsed
372 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
376 ui.log(b'extension', b'> all extsetup took %s\n', allextetupstats)
373
377
374 for name in broken:
378 for name in broken:
375 ui.log(b'extension', b' - disabling broken %s extension\n', name)
379 ui.log(b'extension', b' - disabling broken %s extension\n', name)
376 _extensions[name] = None
380 _extensions[name] = None
377
381
378 # Call aftercallbacks that were never met.
382 # Call aftercallbacks that were never met.
379 ui.log(b'extension', b'- executing remaining aftercallbacks\n')
383 ui.log(b'extension', b'- executing remaining aftercallbacks\n')
380 with util.timedcm('aftercallbacks') as stats:
384 with util.timedcm('aftercallbacks') as stats:
381 for shortname in _aftercallbacks:
385 for shortname in _aftercallbacks:
382 if shortname in _extensions:
386 if shortname in _extensions:
383 continue
387 continue
384
388
385 for fn in _aftercallbacks[shortname]:
389 for fn in _aftercallbacks[shortname]:
386 ui.log(
390 ui.log(
387 b'extension',
391 b'extension',
388 b' - extension %s not loaded, notify callbacks\n',
392 b' - extension %s not loaded, notify callbacks\n',
389 shortname,
393 shortname,
390 )
394 )
391 fn(loaded=False)
395 fn(loaded=False)
392 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
396 ui.log(b'extension', b'> remaining aftercallbacks completed in %s\n', stats)
393
397
394 # loadall() is called multiple times and lingering _aftercallbacks
398 # loadall() is called multiple times and lingering _aftercallbacks
395 # entries could result in double execution. See issue4646.
399 # entries could result in double execution. See issue4646.
396 _aftercallbacks.clear()
400 _aftercallbacks.clear()
397
401
398 # delay importing avoids cyclic dependency (especially commands)
402 # delay importing avoids cyclic dependency (especially commands)
399 from . import (
403 from . import (
400 color,
404 color,
401 commands,
405 commands,
402 filemerge,
406 filemerge,
403 fileset,
407 fileset,
404 revset,
408 revset,
405 templatefilters,
409 templatefilters,
406 templatefuncs,
410 templatefuncs,
407 templatekw,
411 templatekw,
408 )
412 )
409
413
410 # list of (objname, loadermod, loadername) tuple:
414 # list of (objname, loadermod, loadername) tuple:
411 # - objname is the name of an object in extension module,
415 # - objname is the name of an object in extension module,
412 # from which extra information is loaded
416 # from which extra information is loaded
413 # - loadermod is the module where loader is placed
417 # - loadermod is the module where loader is placed
414 # - loadername is the name of the function,
418 # - loadername is the name of the function,
415 # which takes (ui, extensionname, extraobj) arguments
419 # which takes (ui, extensionname, extraobj) arguments
416 ui.log(b'extension', b'- loading extension registration objects\n')
420 ui.log(b'extension', b'- loading extension registration objects\n')
417 extraloaders = [
421 extraloaders = [
418 (b'cmdtable', commands, b'loadcmdtable'),
422 (b'cmdtable', commands, b'loadcmdtable'),
419 (b'colortable', color, b'loadcolortable'),
423 (b'colortable', color, b'loadcolortable'),
420 (b'filesetpredicate', fileset, b'loadpredicate'),
424 (b'filesetpredicate', fileset, b'loadpredicate'),
421 (b'internalmerge', filemerge, b'loadinternalmerge'),
425 (b'internalmerge', filemerge, b'loadinternalmerge'),
422 (b'revsetpredicate', revset, b'loadpredicate'),
426 (b'revsetpredicate', revset, b'loadpredicate'),
423 (b'templatefilter', templatefilters, b'loadfilter'),
427 (b'templatefilter', templatefilters, b'loadfilter'),
424 (b'templatefunc', templatefuncs, b'loadfunction'),
428 (b'templatefunc', templatefuncs, b'loadfunction'),
425 (b'templatekeyword', templatekw, b'loadkeyword'),
429 (b'templatekeyword', templatekw, b'loadkeyword'),
426 ]
430 ]
427 with util.timedcm('load registration objects') as stats:
431 with util.timedcm('load registration objects') as stats:
428 _loadextra(ui, newindex, extraloaders)
432 _loadextra(ui, newindex, extraloaders)
429 ui.log(
433 ui.log(
430 b'extension',
434 b'extension',
431 b'> extension registration object loading took %s\n',
435 b'> extension registration object loading took %s\n',
432 stats,
436 stats,
433 )
437 )
434
438
435 # Report per extension loading time (except reposetup)
439 # Report per extension loading time (except reposetup)
436 for name in sorted(loadingtime):
440 for name in sorted(loadingtime):
437 ui.log(
441 ui.log(
438 b'extension',
442 b'extension',
439 b'> extension %s take a total of %s to load\n',
443 b'> extension %s take a total of %s to load\n',
440 name,
444 name,
441 util.timecount(loadingtime[name]),
445 util.timecount(loadingtime[name]),
442 )
446 )
443
447
444 ui.log(b'extension', b'extension loading complete\n')
448 ui.log(b'extension', b'extension loading complete\n')
445
449
446
450
447 def _loadextra(ui, newindex, extraloaders):
451 def _loadextra(ui, newindex, extraloaders):
448 for name in _order[newindex:]:
452 for name in _order[newindex:]:
449 module = _extensions[name]
453 module = _extensions[name]
450 if not module:
454 if not module:
451 continue # loading this module failed
455 continue # loading this module failed
452
456
453 for objname, loadermod, loadername in extraloaders:
457 for objname, loadermod, loadername in extraloaders:
454 extraobj = getattr(module, objname, None)
458 extraobj = getattr(module, objname, None)
455 if extraobj is not None:
459 if extraobj is not None:
456 getattr(loadermod, loadername)(ui, name, extraobj)
460 getattr(loadermod, loadername)(ui, name, extraobj)
457
461
458
462
459 def afterloaded(extension, callback):
463 def afterloaded(extension, callback):
460 """Run the specified function after a named extension is loaded.
464 """Run the specified function after a named extension is loaded.
461
465
462 If the named extension is already loaded, the callback will be called
466 If the named extension is already loaded, the callback will be called
463 immediately.
467 immediately.
464
468
465 If the named extension never loads, the callback will be called after
469 If the named extension never loads, the callback will be called after
466 all extensions have been loaded.
470 all extensions have been loaded.
467
471
468 The callback receives the named argument ``loaded``, which is a boolean
472 The callback receives the named argument ``loaded``, which is a boolean
469 indicating whether the dependent extension actually loaded.
473 indicating whether the dependent extension actually loaded.
470 """
474 """
471
475
472 if extension in _extensions:
476 if extension in _extensions:
473 # Report loaded as False if the extension is disabled
477 # Report loaded as False if the extension is disabled
474 loaded = _extensions[extension] is not None
478 loaded = _extensions[extension] is not None
475 callback(loaded=loaded)
479 callback(loaded=loaded)
476 else:
480 else:
477 _aftercallbacks.setdefault(extension, []).append(callback)
481 _aftercallbacks.setdefault(extension, []).append(callback)
478
482
479
483
480 def populateui(ui):
484 def populateui(ui):
481 """Run extension hooks on the given ui to populate additional members,
485 """Run extension hooks on the given ui to populate additional members,
482 extend the class dynamically, etc.
486 extend the class dynamically, etc.
483
487
484 This will be called after the configuration is loaded, and/or extensions
488 This will be called after the configuration is loaded, and/or extensions
485 are loaded. In general, it's once per ui instance, but in command-server
489 are loaded. In general, it's once per ui instance, but in command-server
486 and hgweb, this may be called more than once with the same ui.
490 and hgweb, this may be called more than once with the same ui.
487 """
491 """
488 for name, mod in extensions(ui):
492 for name, mod in extensions(ui):
489 hook = getattr(mod, 'uipopulate', None)
493 hook = getattr(mod, 'uipopulate', None)
490 if not hook:
494 if not hook:
491 continue
495 continue
492 try:
496 try:
493 hook(ui)
497 hook(ui)
494 except Exception as inst:
498 except Exception as inst:
495 ui.traceback(force=True)
499 ui.traceback(force=True)
496 ui.warn(
500 ui.warn(
497 _(b'*** failed to populate ui by extension %s: %s\n')
501 _(b'*** failed to populate ui by extension %s: %s\n')
498 % (name, stringutil.forcebytestr(inst))
502 % (name, stringutil.forcebytestr(inst))
499 )
503 )
500
504
501
505
502 def bind(func, *args):
506 def bind(func, *args):
503 """Partial function application
507 """Partial function application
504
508
505 Returns a new function that is the partial application of args and kwargs
509 Returns a new function that is the partial application of args and kwargs
506 to func. For example,
510 to func. For example,
507
511
508 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)"""
512 f(1, 2, bar=3) === bind(f, 1)(2, bar=3)"""
509 assert callable(func)
513 assert callable(func)
510
514
511 def closure(*a, **kw):
515 def closure(*a, **kw):
512 return func(*(args + a), **kw)
516 return func(*(args + a), **kw)
513
517
514 return closure
518 return closure
515
519
516
520
517 def _updatewrapper(wrap, origfn, unboundwrapper):
521 def _updatewrapper(wrap, origfn, unboundwrapper):
518 '''Copy and add some useful attributes to wrapper'''
522 '''Copy and add some useful attributes to wrapper'''
519 try:
523 try:
520 wrap.__name__ = origfn.__name__
524 wrap.__name__ = origfn.__name__
521 except AttributeError:
525 except AttributeError:
522 pass
526 pass
523 wrap.__module__ = getattr(origfn, '__module__')
527 wrap.__module__ = getattr(origfn, '__module__')
524 wrap.__doc__ = getattr(origfn, '__doc__')
528 wrap.__doc__ = getattr(origfn, '__doc__')
525 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
529 wrap.__dict__.update(getattr(origfn, '__dict__', {}))
526 wrap._origfunc = origfn
530 wrap._origfunc = origfn
527 wrap._unboundwrapper = unboundwrapper
531 wrap._unboundwrapper = unboundwrapper
528
532
529
533
530 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
534 def wrapcommand(table, command, wrapper, synopsis=None, docstring=None):
531 '''Wrap the command named `command' in table
535 '''Wrap the command named `command' in table
532
536
533 Replace command in the command table with wrapper. The wrapped command will
537 Replace command in the command table with wrapper. The wrapped command will
534 be inserted into the command table specified by the table argument.
538 be inserted into the command table specified by the table argument.
535
539
536 The wrapper will be called like
540 The wrapper will be called like
537
541
538 wrapper(orig, *args, **kwargs)
542 wrapper(orig, *args, **kwargs)
539
543
540 where orig is the original (wrapped) function, and *args, **kwargs
544 where orig is the original (wrapped) function, and *args, **kwargs
541 are the arguments passed to it.
545 are the arguments passed to it.
542
546
543 Optionally append to the command synopsis and docstring, used for help.
547 Optionally append to the command synopsis and docstring, used for help.
544 For example, if your extension wraps the ``bookmarks`` command to add the
548 For example, if your extension wraps the ``bookmarks`` command to add the
545 flags ``--remote`` and ``--all`` you might call this function like so:
549 flags ``--remote`` and ``--all`` you might call this function like so:
546
550
547 synopsis = ' [-a] [--remote]'
551 synopsis = ' [-a] [--remote]'
548 docstring = """
552 docstring = """
549
553
550 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
554 The ``remotenames`` extension adds the ``--remote`` and ``--all`` (``-a``)
551 flags to the bookmarks command. Either flag will show the remote bookmarks
555 flags to the bookmarks command. Either flag will show the remote bookmarks
552 known to the repository; ``--remote`` will also suppress the output of the
556 known to the repository; ``--remote`` will also suppress the output of the
553 local bookmarks.
557 local bookmarks.
554 """
558 """
555
559
556 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
560 extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks,
557 synopsis, docstring)
561 synopsis, docstring)
558 '''
562 '''
559 assert callable(wrapper)
563 assert callable(wrapper)
560 aliases, entry = cmdutil.findcmd(command, table)
564 aliases, entry = cmdutil.findcmd(command, table)
561 for alias, e in pycompat.iteritems(table):
565 for alias, e in pycompat.iteritems(table):
562 if e is entry:
566 if e is entry:
563 key = alias
567 key = alias
564 break
568 break
565
569
566 origfn = entry[0]
570 origfn = entry[0]
567 wrap = functools.partial(
571 wrap = functools.partial(
568 util.checksignature(wrapper), util.checksignature(origfn)
572 util.checksignature(wrapper), util.checksignature(origfn)
569 )
573 )
570 _updatewrapper(wrap, origfn, wrapper)
574 _updatewrapper(wrap, origfn, wrapper)
571 if docstring is not None:
575 if docstring is not None:
572 wrap.__doc__ += docstring
576 wrap.__doc__ += docstring
573
577
574 newentry = list(entry)
578 newentry = list(entry)
575 newentry[0] = wrap
579 newentry[0] = wrap
576 if synopsis is not None:
580 if synopsis is not None:
577 newentry[2] += synopsis
581 newentry[2] += synopsis
578 table[key] = tuple(newentry)
582 table[key] = tuple(newentry)
579 return entry
583 return entry
580
584
581
585
582 def wrapfilecache(cls, propname, wrapper):
586 def wrapfilecache(cls, propname, wrapper):
583 """Wraps a filecache property.
587 """Wraps a filecache property.
584
588
585 These can't be wrapped using the normal wrapfunction.
589 These can't be wrapped using the normal wrapfunction.
586 """
590 """
587 propname = pycompat.sysstr(propname)
591 propname = pycompat.sysstr(propname)
588 assert callable(wrapper)
592 assert callable(wrapper)
589 for currcls in cls.__mro__:
593 for currcls in cls.__mro__:
590 if propname in currcls.__dict__:
594 if propname in currcls.__dict__:
591 origfn = currcls.__dict__[propname].func
595 origfn = currcls.__dict__[propname].func
592 assert callable(origfn)
596 assert callable(origfn)
593
597
594 def wrap(*args, **kwargs):
598 def wrap(*args, **kwargs):
595 return wrapper(origfn, *args, **kwargs)
599 return wrapper(origfn, *args, **kwargs)
596
600
597 currcls.__dict__[propname].func = wrap
601 currcls.__dict__[propname].func = wrap
598 break
602 break
599
603
600 if currcls is object:
604 if currcls is object:
601 raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
605 raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
602
606
603
607
604 class wrappedfunction(object):
608 class wrappedfunction(object):
605 '''context manager for temporarily wrapping a function'''
609 '''context manager for temporarily wrapping a function'''
606
610
607 def __init__(self, container, funcname, wrapper):
611 def __init__(self, container, funcname, wrapper):
608 assert callable(wrapper)
612 assert callable(wrapper)
609 self._container = container
613 self._container = container
610 self._funcname = funcname
614 self._funcname = funcname
611 self._wrapper = wrapper
615 self._wrapper = wrapper
612
616
613 def __enter__(self):
617 def __enter__(self):
614 wrapfunction(self._container, self._funcname, self._wrapper)
618 wrapfunction(self._container, self._funcname, self._wrapper)
615
619
616 def __exit__(self, exctype, excvalue, traceback):
620 def __exit__(self, exctype, excvalue, traceback):
617 unwrapfunction(self._container, self._funcname, self._wrapper)
621 unwrapfunction(self._container, self._funcname, self._wrapper)
618
622
619
623
620 def wrapfunction(container, funcname, wrapper):
624 def wrapfunction(container, funcname, wrapper):
621 """Wrap the function named funcname in container
625 """Wrap the function named funcname in container
622
626
623 Replace the funcname member in the given container with the specified
627 Replace the funcname member in the given container with the specified
624 wrapper. The container is typically a module, class, or instance.
628 wrapper. The container is typically a module, class, or instance.
625
629
626 The wrapper will be called like
630 The wrapper will be called like
627
631
628 wrapper(orig, *args, **kwargs)
632 wrapper(orig, *args, **kwargs)
629
633
630 where orig is the original (wrapped) function, and *args, **kwargs
634 where orig is the original (wrapped) function, and *args, **kwargs
631 are the arguments passed to it.
635 are the arguments passed to it.
632
636
633 Wrapping methods of the repository object is not recommended since
637 Wrapping methods of the repository object is not recommended since
634 it conflicts with extensions that extend the repository by
638 it conflicts with extensions that extend the repository by
635 subclassing. All extensions that need to extend methods of
639 subclassing. All extensions that need to extend methods of
636 localrepository should use this subclassing trick: namely,
640 localrepository should use this subclassing trick: namely,
637 reposetup() should look like
641 reposetup() should look like
638
642
639 def reposetup(ui, repo):
643 def reposetup(ui, repo):
640 class myrepo(repo.__class__):
644 class myrepo(repo.__class__):
641 def whatever(self, *args, **kwargs):
645 def whatever(self, *args, **kwargs):
642 [...extension stuff...]
646 [...extension stuff...]
643 super(myrepo, self).whatever(*args, **kwargs)
647 super(myrepo, self).whatever(*args, **kwargs)
644 [...extension stuff...]
648 [...extension stuff...]
645
649
646 repo.__class__ = myrepo
650 repo.__class__ = myrepo
647
651
648 In general, combining wrapfunction() with subclassing does not
652 In general, combining wrapfunction() with subclassing does not
649 work. Since you cannot control what other extensions are loaded by
653 work. Since you cannot control what other extensions are loaded by
650 your end users, you should play nicely with others by using the
654 your end users, you should play nicely with others by using the
651 subclass trick.
655 subclass trick.
652 """
656 """
653 assert callable(wrapper)
657 assert callable(wrapper)
654
658
655 origfn = getattr(container, funcname)
659 origfn = getattr(container, funcname)
656 assert callable(origfn)
660 assert callable(origfn)
657 if inspect.ismodule(container):
661 if inspect.ismodule(container):
658 # origfn is not an instance or class method. "partial" can be used.
662 # origfn is not an instance or class method. "partial" can be used.
659 # "partial" won't insert a frame in traceback.
663 # "partial" won't insert a frame in traceback.
660 wrap = functools.partial(wrapper, origfn)
664 wrap = functools.partial(wrapper, origfn)
661 else:
665 else:
662 # "partial" cannot be safely used. Emulate its effect by using "bind".
666 # "partial" cannot be safely used. Emulate its effect by using "bind".
663 # The downside is one more frame in traceback.
667 # The downside is one more frame in traceback.
664 wrap = bind(wrapper, origfn)
668 wrap = bind(wrapper, origfn)
665 _updatewrapper(wrap, origfn, wrapper)
669 _updatewrapper(wrap, origfn, wrapper)
666 setattr(container, funcname, wrap)
670 setattr(container, funcname, wrap)
667 return origfn
671 return origfn
668
672
669
673
670 def unwrapfunction(container, funcname, wrapper=None):
674 def unwrapfunction(container, funcname, wrapper=None):
671 """undo wrapfunction
675 """undo wrapfunction
672
676
673 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
677 If wrappers is None, undo the last wrap. Otherwise removes the wrapper
674 from the chain of wrappers.
678 from the chain of wrappers.
675
679
676 Return the removed wrapper.
680 Return the removed wrapper.
677 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
681 Raise IndexError if wrapper is None and nothing to unwrap; ValueError if
678 wrapper is not None but is not found in the wrapper chain.
682 wrapper is not None but is not found in the wrapper chain.
679 """
683 """
680 chain = getwrapperchain(container, funcname)
684 chain = getwrapperchain(container, funcname)
681 origfn = chain.pop()
685 origfn = chain.pop()
682 if wrapper is None:
686 if wrapper is None:
683 wrapper = chain[0]
687 wrapper = chain[0]
684 chain.remove(wrapper)
688 chain.remove(wrapper)
685 setattr(container, funcname, origfn)
689 setattr(container, funcname, origfn)
686 for w in reversed(chain):
690 for w in reversed(chain):
687 wrapfunction(container, funcname, w)
691 wrapfunction(container, funcname, w)
688 return wrapper
692 return wrapper
689
693
690
694
691 def getwrapperchain(container, funcname):
695 def getwrapperchain(container, funcname):
692 """get a chain of wrappers of a function
696 """get a chain of wrappers of a function
693
697
694 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
698 Return a list of functions: [newest wrapper, ..., oldest wrapper, origfunc]
695
699
696 The wrapper functions are the ones passed to wrapfunction, whose first
700 The wrapper functions are the ones passed to wrapfunction, whose first
697 argument is origfunc.
701 argument is origfunc.
698 """
702 """
699 result = []
703 result = []
700 fn = getattr(container, funcname)
704 fn = getattr(container, funcname)
701 while fn:
705 while fn:
702 assert callable(fn)
706 assert callable(fn)
703 result.append(getattr(fn, '_unboundwrapper', fn))
707 result.append(getattr(fn, '_unboundwrapper', fn))
704 fn = getattr(fn, '_origfunc', None)
708 fn = getattr(fn, '_origfunc', None)
705 return result
709 return result
706
710
707
711
708 def _disabledpaths():
712 def _disabledpaths():
709 '''find paths of disabled extensions. returns a dict of {name: path}'''
713 '''find paths of disabled extensions. returns a dict of {name: path}'''
710 import hgext
714 import hgext
711
715
712 # The hgext might not have a __file__ attribute (e.g. in PyOxidizer) and
716 # The hgext might not have a __file__ attribute (e.g. in PyOxidizer) and
713 # it might not be on a filesystem even if it does.
717 # it might not be on a filesystem even if it does.
714 if util.safehasattr(hgext, '__file__'):
718 if util.safehasattr(hgext, '__file__'):
715 extpath = os.path.dirname(
719 extpath = os.path.dirname(
716 util.abspath(pycompat.fsencode(hgext.__file__))
720 util.abspath(pycompat.fsencode(hgext.__file__))
717 )
721 )
718 try:
722 try:
719 files = os.listdir(extpath)
723 files = os.listdir(extpath)
720 except OSError:
724 except OSError:
721 return {}
725 return {}
722 else:
726 else:
723 return {}
727 return {}
724
728
725 exts = {}
729 exts = {}
726 for e in files:
730 for e in files:
727 if e.endswith(b'.py'):
731 if e.endswith(b'.py'):
728 name = e.rsplit(b'.', 1)[0]
732 name = e.rsplit(b'.', 1)[0]
729 path = os.path.join(extpath, e)
733 path = os.path.join(extpath, e)
730 else:
734 else:
731 name = e
735 name = e
732 path = os.path.join(extpath, e, b'__init__.py')
736 path = os.path.join(extpath, e, b'__init__.py')
733 if not os.path.exists(path):
737 if not os.path.exists(path):
734 continue
738 continue
735 if name in exts or name in _order or name == b'__init__':
739 if name in exts or name in _order or name == b'__init__':
736 continue
740 continue
737 exts[name] = path
741 exts[name] = path
738 for name, path in pycompat.iteritems(_disabledextensions):
742 for name, path in pycompat.iteritems(_disabledextensions):
739 # If no path was provided for a disabled extension (e.g. "color=!"),
743 # If no path was provided for a disabled extension (e.g. "color=!"),
740 # don't replace the path we already found by the scan above.
744 # don't replace the path we already found by the scan above.
741 if path:
745 if path:
742 exts[name] = path
746 exts[name] = path
743 return exts
747 return exts
744
748
745
749
746 def _moduledoc(file):
750 def _moduledoc(file):
747 """return the top-level python documentation for the given file
751 """return the top-level python documentation for the given file
748
752
749 Loosely inspired by pydoc.source_synopsis(), but rewritten to
753 Loosely inspired by pydoc.source_synopsis(), but rewritten to
750 handle triple quotes and to return the whole text instead of just
754 handle triple quotes and to return the whole text instead of just
751 the synopsis"""
755 the synopsis"""
752 result = []
756 result = []
753
757
754 line = file.readline()
758 line = file.readline()
755 while line[:1] == b'#' or not line.strip():
759 while line[:1] == b'#' or not line.strip():
756 line = file.readline()
760 line = file.readline()
757 if not line:
761 if not line:
758 break
762 break
759
763
760 start = line[:3]
764 start = line[:3]
761 if start == b'"""' or start == b"'''":
765 if start == b'"""' or start == b"'''":
762 line = line[3:]
766 line = line[3:]
763 while line:
767 while line:
764 if line.rstrip().endswith(start):
768 if line.rstrip().endswith(start):
765 line = line.split(start)[0]
769 line = line.split(start)[0]
766 if line:
770 if line:
767 result.append(line)
771 result.append(line)
768 break
772 break
769 elif not line:
773 elif not line:
770 return None # unmatched delimiter
774 return None # unmatched delimiter
771 result.append(line)
775 result.append(line)
772 line = file.readline()
776 line = file.readline()
773 else:
777 else:
774 return None
778 return None
775
779
776 return b''.join(result)
780 return b''.join(result)
777
781
778
782
779 def _disabledhelp(path):
783 def _disabledhelp(path):
780 '''retrieve help synopsis of a disabled extension (without importing)'''
784 '''retrieve help synopsis of a disabled extension (without importing)'''
781 try:
785 try:
782 with open(path, b'rb') as src:
786 with open(path, b'rb') as src:
783 doc = _moduledoc(src)
787 doc = _moduledoc(src)
784 except IOError:
788 except IOError:
785 return
789 return
786
790
787 if doc: # extracting localized synopsis
791 if doc: # extracting localized synopsis
788 return gettext(doc)
792 return gettext(doc)
789 else:
793 else:
790 return _(b'(no help text available)')
794 return _(b'(no help text available)')
791
795
792
796
793 def disabled():
797 def disabled():
794 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
798 '''find disabled extensions from hgext. returns a dict of {name: desc}'''
795 try:
799 try:
796 from hgext import __index__ # pytype: disable=import-error
800 from hgext import __index__ # pytype: disable=import-error
797
801
798 return {
802 return {
799 name: gettext(desc)
803 name: gettext(desc)
800 for name, desc in pycompat.iteritems(__index__.docs)
804 for name, desc in pycompat.iteritems(__index__.docs)
801 if name not in _order
805 if name not in _order
802 }
806 }
803 except (ImportError, AttributeError):
807 except (ImportError, AttributeError):
804 pass
808 pass
805
809
806 paths = _disabledpaths()
810 paths = _disabledpaths()
807 if not paths:
811 if not paths:
808 return {}
812 return {}
809
813
810 exts = {}
814 exts = {}
811 for name, path in pycompat.iteritems(paths):
815 for name, path in pycompat.iteritems(paths):
812 doc = _disabledhelp(path)
816 doc = _disabledhelp(path)
813 if doc and name != b'__index__':
817 if doc and name != b'__index__':
814 exts[name] = doc.splitlines()[0]
818 exts[name] = doc.splitlines()[0]
815
819
816 return exts
820 return exts
817
821
818
822
819 def disabled_help(name):
823 def disabled_help(name):
820 """Obtain the full help text for a disabled extension, or None."""
824 """Obtain the full help text for a disabled extension, or None."""
821 paths = _disabledpaths()
825 paths = _disabledpaths()
822 if name in paths:
826 if name in paths:
823 return _disabledhelp(paths[name])
827 return _disabledhelp(paths[name])
824
828
825
829
826 def _walkcommand(node):
830 def _walkcommand(node):
827 """Scan @command() decorators in the tree starting at node"""
831 """Scan @command() decorators in the tree starting at node"""
828 todo = collections.deque([node])
832 todo = collections.deque([node])
829 while todo:
833 while todo:
830 node = todo.popleft()
834 node = todo.popleft()
831 if not isinstance(node, ast.FunctionDef):
835 if not isinstance(node, ast.FunctionDef):
832 todo.extend(ast.iter_child_nodes(node))
836 todo.extend(ast.iter_child_nodes(node))
833 continue
837 continue
834 for d in node.decorator_list:
838 for d in node.decorator_list:
835 if not isinstance(d, ast.Call):
839 if not isinstance(d, ast.Call):
836 continue
840 continue
837 if not isinstance(d.func, ast.Name):
841 if not isinstance(d.func, ast.Name):
838 continue
842 continue
839 if d.func.id != 'command':
843 if d.func.id != 'command':
840 continue
844 continue
841 yield d
845 yield d
842
846
843
847
844 def _disabledcmdtable(path):
848 def _disabledcmdtable(path):
845 """Construct a dummy command table without loading the extension module
849 """Construct a dummy command table without loading the extension module
846
850
847 This may raise IOError or SyntaxError.
851 This may raise IOError or SyntaxError.
848 """
852 """
849 with open(path, b'rb') as src:
853 with open(path, b'rb') as src:
850 root = ast.parse(src.read(), path)
854 root = ast.parse(src.read(), path)
851 cmdtable = {}
855 cmdtable = {}
852 for node in _walkcommand(root):
856 for node in _walkcommand(root):
853 if not node.args:
857 if not node.args:
854 continue
858 continue
855 a = node.args[0]
859 a = node.args[0]
856 if isinstance(a, ast.Str):
860 if isinstance(a, ast.Str):
857 name = pycompat.sysbytes(a.s)
861 name = pycompat.sysbytes(a.s)
858 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
862 elif pycompat.ispy3 and isinstance(a, ast.Bytes):
859 name = a.s
863 name = a.s
860 else:
864 else:
861 continue
865 continue
862 cmdtable[name] = (None, [], b'')
866 cmdtable[name] = (None, [], b'')
863 return cmdtable
867 return cmdtable
864
868
865
869
866 def _finddisabledcmd(ui, cmd, name, path, strict):
870 def _finddisabledcmd(ui, cmd, name, path, strict):
867 try:
871 try:
868 cmdtable = _disabledcmdtable(path)
872 cmdtable = _disabledcmdtable(path)
869 except (IOError, SyntaxError):
873 except (IOError, SyntaxError):
870 return
874 return
871 try:
875 try:
872 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
876 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
873 except (error.AmbiguousCommand, error.UnknownCommand):
877 except (error.AmbiguousCommand, error.UnknownCommand):
874 return
878 return
875 for c in aliases:
879 for c in aliases:
876 if c.startswith(cmd):
880 if c.startswith(cmd):
877 cmd = c
881 cmd = c
878 break
882 break
879 else:
883 else:
880 cmd = aliases[0]
884 cmd = aliases[0]
881 doc = _disabledhelp(path)
885 doc = _disabledhelp(path)
882 return (cmd, name, doc)
886 return (cmd, name, doc)
883
887
884
888
885 def disabledcmd(ui, cmd, strict=False):
889 def disabledcmd(ui, cmd, strict=False):
886 """find cmd from disabled extensions without importing.
890 """find cmd from disabled extensions without importing.
887 returns (cmdname, extname, doc)"""
891 returns (cmdname, extname, doc)"""
888
892
889 paths = _disabledpaths()
893 paths = _disabledpaths()
890 if not paths:
894 if not paths:
891 raise error.UnknownCommand(cmd)
895 raise error.UnknownCommand(cmd)
892
896
893 ext = None
897 ext = None
894 # first, search for an extension with the same name as the command
898 # first, search for an extension with the same name as the command
895 path = paths.pop(cmd, None)
899 path = paths.pop(cmd, None)
896 if path:
900 if path:
897 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
901 ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
898 if not ext:
902 if not ext:
899 # otherwise, interrogate each extension until there's a match
903 # otherwise, interrogate each extension until there's a match
900 for name, path in pycompat.iteritems(paths):
904 for name, path in pycompat.iteritems(paths):
901 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
905 ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
902 if ext:
906 if ext:
903 break
907 break
904 if ext:
908 if ext:
905 return ext
909 return ext
906
910
907 raise error.UnknownCommand(cmd)
911 raise error.UnknownCommand(cmd)
908
912
909
913
910 def enabled(shortname=True):
914 def enabled(shortname=True):
911 '''return a dict of {name: desc} of extensions'''
915 '''return a dict of {name: desc} of extensions'''
912 exts = {}
916 exts = {}
913 for ename, ext in extensions():
917 for ename, ext in extensions():
914 doc = gettext(ext.__doc__) or _(b'(no help text available)')
918 doc = gettext(ext.__doc__) or _(b'(no help text available)')
915 assert doc is not None # help pytype
919 assert doc is not None # help pytype
916 if shortname:
920 if shortname:
917 ename = ename.split(b'.')[-1]
921 ename = ename.split(b'.')[-1]
918 exts[ename] = doc.splitlines()[0].strip()
922 exts[ename] = doc.splitlines()[0].strip()
919
923
920 return exts
924 return exts
921
925
922
926
923 def notloaded():
927 def notloaded():
924 '''return short names of extensions that failed to load'''
928 '''return short names of extensions that failed to load'''
925 return [
929 return [
926 name for name, mod in pycompat.iteritems(_extensions) if mod is None
930 name for name, mod in pycompat.iteritems(_extensions) if mod is None
927 ]
931 ]
928
932
929
933
930 def moduleversion(module):
934 def moduleversion(module):
931 '''return version information from given module as a string'''
935 '''return version information from given module as a string'''
932 if util.safehasattr(module, b'getversion') and callable(module.getversion):
936 if util.safehasattr(module, b'getversion') and callable(module.getversion):
933 try:
937 try:
934 version = module.getversion()
938 version = module.getversion()
935 except Exception:
939 except Exception:
936 version = b'unknown'
940 version = b'unknown'
937
941
938 elif util.safehasattr(module, b'__version__'):
942 elif util.safehasattr(module, b'__version__'):
939 version = module.__version__
943 version = module.__version__
940 else:
944 else:
941 version = b''
945 version = b''
942 if isinstance(version, (list, tuple)):
946 if isinstance(version, (list, tuple)):
943 version = b'.'.join(pycompat.bytestr(o) for o in version)
947 version = b'.'.join(pycompat.bytestr(o) for o in version)
944 else:
948 else:
945 # version data should be bytes, but not all extensions are ported
949 # version data should be bytes, but not all extensions are ported
946 # to py3.
950 # to py3.
947 version = stringutil.forcebytestr(version)
951 version = stringutil.forcebytestr(version)
948 return version
952 return version
949
953
950
954
951 def ismoduleinternal(module):
955 def ismoduleinternal(module):
952 exttestedwith = getattr(module, 'testedwith', None)
956 exttestedwith = getattr(module, 'testedwith', None)
953 return exttestedwith == b"ships-with-hg-core"
957 return exttestedwith == b"ships-with-hg-core"
@@ -1,1926 +1,1945 b''
1 Test basic extension support
1 Test basic extension support
2 $ cat > unflush.py <<EOF
2 $ cat > unflush.py <<EOF
3 > import sys
3 > import sys
4 > from mercurial import pycompat
4 > from mercurial import pycompat
5 > if pycompat.ispy3:
5 > if pycompat.ispy3:
6 > # no changes required
6 > # no changes required
7 > sys.exit(0)
7 > sys.exit(0)
8 > with open(sys.argv[1], 'rb') as f:
8 > with open(sys.argv[1], 'rb') as f:
9 > data = f.read()
9 > data = f.read()
10 > with open(sys.argv[1], 'wb') as f:
10 > with open(sys.argv[1], 'wb') as f:
11 > f.write(data.replace(b', flush=True', b''))
11 > f.write(data.replace(b', flush=True', b''))
12 > EOF
12 > EOF
13
13
14 $ cat > foobar.py <<EOF
14 $ cat > foobar.py <<EOF
15 > import os
15 > import os
16 > from mercurial import commands, exthelper, registrar
16 > from mercurial import commands, exthelper, registrar
17 >
17 >
18 > eh = exthelper.exthelper()
18 > eh = exthelper.exthelper()
19 > eh.configitem(b'tests', b'foo', default=b"Foo")
19 > eh.configitem(b'tests', b'foo', default=b"Foo")
20 >
20 >
21 > uisetup = eh.finaluisetup
21 > uisetup = eh.finaluisetup
22 > uipopulate = eh.finaluipopulate
22 > uipopulate = eh.finaluipopulate
23 > reposetup = eh.finalreposetup
23 > reposetup = eh.finalreposetup
24 > cmdtable = eh.cmdtable
24 > cmdtable = eh.cmdtable
25 > configtable = eh.configtable
25 > configtable = eh.configtable
26 >
26 >
27 > @eh.uisetup
27 > @eh.uisetup
28 > def _uisetup(ui):
28 > def _uisetup(ui):
29 > ui.debug(b"uisetup called [debug]\\n")
29 > ui.debug(b"uisetup called [debug]\\n")
30 > ui.write(b"uisetup called\\n")
30 > ui.write(b"uisetup called\\n")
31 > ui.status(b"uisetup called [status]\\n")
31 > ui.status(b"uisetup called [status]\\n")
32 > ui.flush()
32 > ui.flush()
33 > @eh.uipopulate
33 > @eh.uipopulate
34 > def _uipopulate(ui):
34 > def _uipopulate(ui):
35 > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
35 > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
36 > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
36 > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
37 > @eh.reposetup
37 > @eh.reposetup
38 > def _reposetup(ui, repo):
38 > def _reposetup(ui, repo):
39 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
39 > ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
40 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
40 > ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
41 > ui.flush()
41 > ui.flush()
42 > @eh.command(b'foo', [], b'hg foo')
42 > @eh.command(b'foo', [], b'hg foo')
43 > def foo(ui, *args, **kwargs):
43 > def foo(ui, *args, **kwargs):
44 > foo = ui.config(b'tests', b'foo')
44 > foo = ui.config(b'tests', b'foo')
45 > ui.write(foo)
45 > ui.write(foo)
46 > ui.write(b"\\n")
46 > ui.write(b"\\n")
47 > @eh.command(b'bar', [], b'hg bar', norepo=True)
47 > @eh.command(b'bar', [], b'hg bar', norepo=True)
48 > def bar(ui, *args, **kwargs):
48 > def bar(ui, *args, **kwargs):
49 > ui.write(b"Bar\\n")
49 > ui.write(b"Bar\\n")
50 > EOF
50 > EOF
51 $ abspath=`pwd`/foobar.py
51 $ abspath=`pwd`/foobar.py
52
52
53 $ mkdir barfoo
53 $ mkdir barfoo
54 $ cp foobar.py barfoo/__init__.py
54 $ cp foobar.py barfoo/__init__.py
55 $ barfoopath=`pwd`/barfoo
55 $ barfoopath=`pwd`/barfoo
56
56
57 $ hg init a
57 $ hg init a
58 $ cd a
58 $ cd a
59 $ echo foo > file
59 $ echo foo > file
60 $ hg add file
60 $ hg add file
61 $ hg commit -m 'add file'
61 $ hg commit -m 'add file'
62
62
63 $ echo '[extensions]' >> $HGRCPATH
63 $ echo '[extensions]' >> $HGRCPATH
64 $ echo "foobar = $abspath" >> $HGRCPATH
64 $ echo "foobar = $abspath" >> $HGRCPATH
65 $ hg foo
65 $ hg foo
66 uisetup called
66 uisetup called
67 uisetup called [status]
67 uisetup called [status]
68 uipopulate called (1 times)
68 uipopulate called (1 times)
69 uipopulate called (1 times)
69 uipopulate called (1 times)
70 uipopulate called (1 times)
70 uipopulate called (1 times)
71 reposetup called for a
71 reposetup called for a
72 ui == repo.ui
72 ui == repo.ui
73 uipopulate called (1 times) (chg !)
73 uipopulate called (1 times) (chg !)
74 uipopulate called (1 times) (chg !)
74 uipopulate called (1 times) (chg !)
75 uipopulate called (1 times) (chg !)
75 uipopulate called (1 times) (chg !)
76 uipopulate called (1 times) (chg !)
76 uipopulate called (1 times) (chg !)
77 uipopulate called (1 times) (chg !)
77 uipopulate called (1 times) (chg !)
78 reposetup called for a (chg !)
78 reposetup called for a (chg !)
79 ui == repo.ui (chg !)
79 ui == repo.ui (chg !)
80 Foo
80 Foo
81 $ hg foo --quiet
81 $ hg foo --quiet
82 uisetup called (no-chg !)
82 uisetup called (no-chg !)
83 uipopulate called (1 times)
83 uipopulate called (1 times)
84 uipopulate called (1 times)
84 uipopulate called (1 times)
85 uipopulate called (1 times) (chg !)
85 uipopulate called (1 times) (chg !)
86 uipopulate called (1 times) (chg !)
86 uipopulate called (1 times) (chg !)
87 uipopulate called (1 times)
87 uipopulate called (1 times)
88 reposetup called for a
88 reposetup called for a
89 ui == repo.ui
89 ui == repo.ui
90 Foo
90 Foo
91 $ hg foo --debug
91 $ hg foo --debug
92 uisetup called [debug] (no-chg !)
92 uisetup called [debug] (no-chg !)
93 uisetup called (no-chg !)
93 uisetup called (no-chg !)
94 uisetup called [status] (no-chg !)
94 uisetup called [status] (no-chg !)
95 uipopulate called (1 times)
95 uipopulate called (1 times)
96 uipopulate called (1 times)
96 uipopulate called (1 times)
97 uipopulate called (1 times) (chg !)
97 uipopulate called (1 times) (chg !)
98 uipopulate called (1 times) (chg !)
98 uipopulate called (1 times) (chg !)
99 uipopulate called (1 times)
99 uipopulate called (1 times)
100 reposetup called for a
100 reposetup called for a
101 ui == repo.ui
101 ui == repo.ui
102 Foo
102 Foo
103
103
104 $ cd ..
104 $ cd ..
105 $ hg clone a b
105 $ hg clone a b
106 uisetup called (no-chg !)
106 uisetup called (no-chg !)
107 uisetup called [status] (no-chg !)
107 uisetup called [status] (no-chg !)
108 uipopulate called (1 times)
108 uipopulate called (1 times)
109 uipopulate called (1 times) (chg !)
109 uipopulate called (1 times) (chg !)
110 uipopulate called (1 times)
110 uipopulate called (1 times)
111 reposetup called for a
111 reposetup called for a
112 ui == repo.ui
112 ui == repo.ui
113 uipopulate called (1 times)
113 uipopulate called (1 times)
114 uipopulate called (1 times)
114 uipopulate called (1 times)
115 reposetup called for b
115 reposetup called for b
116 ui == repo.ui
116 ui == repo.ui
117 updating to branch default
117 updating to branch default
118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
119
119
120 $ hg bar
120 $ hg bar
121 uisetup called (no-chg !)
121 uisetup called (no-chg !)
122 uisetup called [status] (no-chg !)
122 uisetup called [status] (no-chg !)
123 uipopulate called (1 times)
123 uipopulate called (1 times)
124 uipopulate called (1 times) (chg !)
124 uipopulate called (1 times) (chg !)
125 Bar
125 Bar
126 $ echo 'foobar = !' >> $HGRCPATH
126 $ echo 'foobar = !' >> $HGRCPATH
127
127
128 module/__init__.py-style
128 module/__init__.py-style
129
129
130 $ echo "barfoo = $barfoopath" >> $HGRCPATH
130 $ echo "barfoo = $barfoopath" >> $HGRCPATH
131 $ cd a
131 $ cd a
132 $ hg foo
132 $ hg foo
133 uisetup called
133 uisetup called
134 uisetup called [status]
134 uisetup called [status]
135 uipopulate called (1 times)
135 uipopulate called (1 times)
136 uipopulate called (1 times)
136 uipopulate called (1 times)
137 uipopulate called (1 times)
137 uipopulate called (1 times)
138 reposetup called for a
138 reposetup called for a
139 ui == repo.ui
139 ui == repo.ui
140 uipopulate called (1 times) (chg !)
140 uipopulate called (1 times) (chg !)
141 uipopulate called (1 times) (chg !)
141 uipopulate called (1 times) (chg !)
142 uipopulate called (1 times) (chg !)
142 uipopulate called (1 times) (chg !)
143 uipopulate called (1 times) (chg !)
143 uipopulate called (1 times) (chg !)
144 uipopulate called (1 times) (chg !)
144 uipopulate called (1 times) (chg !)
145 reposetup called for a (chg !)
145 reposetup called for a (chg !)
146 ui == repo.ui (chg !)
146 ui == repo.ui (chg !)
147 Foo
147 Foo
148 $ echo 'barfoo = !' >> $HGRCPATH
148 $ echo 'barfoo = !' >> $HGRCPATH
149
149
150 Check that extensions are loaded in phases:
150 Check that extensions are loaded in phases:
151
151
152 $ cat > foo.py <<EOF
152 $ cat > foo.py <<EOF
153 > from __future__ import print_function
153 > from __future__ import print_function
154 > import os
154 > import os
155 > from mercurial import exthelper
155 > from mercurial import exthelper
156 > from mercurial.utils import procutil
156 > from mercurial.utils import procutil
157 >
157 >
158 > def write(msg):
158 > def write(msg):
159 > procutil.stdout.write(msg)
159 > procutil.stdout.write(msg)
160 > procutil.stdout.flush()
160 > procutil.stdout.flush()
161 >
161 >
162 > name = os.path.basename(__file__).rsplit('.', 1)[0]
162 > name = os.path.basename(__file__).rsplit('.', 1)[0]
163 > bytesname = name.encode('utf-8')
163 > bytesname = name.encode('utf-8')
164 > write(b"1) %s imported\n" % bytesname)
164 > write(b"1) %s imported\n" % bytesname)
165 > eh = exthelper.exthelper()
165 > eh = exthelper.exthelper()
166 > @eh.uisetup
166 > @eh.uisetup
167 > def _uisetup(ui):
167 > def _uisetup(ui):
168 > write(b"2) %s uisetup\n" % bytesname)
168 > write(b"2) %s uisetup\n" % bytesname)
169 > @eh.extsetup
169 > @eh.extsetup
170 > def _extsetup(ui):
170 > def _extsetup(ui):
171 > write(b"3) %s extsetup\n" % bytesname)
171 > write(b"3) %s extsetup\n" % bytesname)
172 > @eh.uipopulate
172 > @eh.uipopulate
173 > def _uipopulate(ui):
173 > def _uipopulate(ui):
174 > write(b"4) %s uipopulate\n" % bytesname)
174 > write(b"4) %s uipopulate\n" % bytesname)
175 > @eh.reposetup
175 > @eh.reposetup
176 > def _reposetup(ui, repo):
176 > def _reposetup(ui, repo):
177 > write(b"5) %s reposetup\n" % bytesname)
177 > write(b"5) %s reposetup\n" % bytesname)
178 >
178 >
179 > extsetup = eh.finalextsetup
179 > extsetup = eh.finalextsetup
180 > reposetup = eh.finalreposetup
180 > reposetup = eh.finalreposetup
181 > uipopulate = eh.finaluipopulate
181 > uipopulate = eh.finaluipopulate
182 > uisetup = eh.finaluisetup
182 > uisetup = eh.finaluisetup
183 > revsetpredicate = eh.revsetpredicate
183 > revsetpredicate = eh.revsetpredicate
184 >
184 >
185 > # custom predicate to check registration of functions at loading
185 > # custom predicate to check registration of functions at loading
186 > from mercurial import (
186 > from mercurial import (
187 > smartset,
187 > smartset,
188 > )
188 > )
189 > @eh.revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
189 > @eh.revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
190 > def custompredicate(repo, subset, x):
190 > def custompredicate(repo, subset, x):
191 > return smartset.baseset([r for r in subset if r in {0}])
191 > return smartset.baseset([r for r in subset if r in {0}])
192 > EOF
192 > EOF
193 $ "$PYTHON" $TESTTMP/unflush.py foo.py
193 $ "$PYTHON" $TESTTMP/unflush.py foo.py
194
194
195 $ cp foo.py bar.py
195 $ cp foo.py bar.py
196 $ echo 'foo = foo.py' >> $HGRCPATH
196 $ echo 'foo = foo.py' >> $HGRCPATH
197 $ echo 'bar = bar.py' >> $HGRCPATH
197 $ echo 'bar = bar.py' >> $HGRCPATH
198
198
199 Check normal command's load order of extensions and registration of functions
199 Check normal command's load order of extensions and registration of functions
200
200
201 On chg server, extension should be first set up by the server. Then
201 On chg server, extension should be first set up by the server. Then
202 object-level setup should follow in the worker process.
202 object-level setup should follow in the worker process.
203
203
204 $ hg log -r "foo() and bar()" -q
204 $ hg log -r "foo() and bar()" -q
205 1) foo imported
205 1) foo imported
206 1) bar imported
206 1) bar imported
207 2) foo uisetup
207 2) foo uisetup
208 2) bar uisetup
208 2) bar uisetup
209 3) foo extsetup
209 3) foo extsetup
210 3) bar extsetup
210 3) bar extsetup
211 4) foo uipopulate
211 4) foo uipopulate
212 4) bar uipopulate
212 4) bar uipopulate
213 4) foo uipopulate
213 4) foo uipopulate
214 4) bar uipopulate
214 4) bar uipopulate
215 4) foo uipopulate
215 4) foo uipopulate
216 4) bar uipopulate
216 4) bar uipopulate
217 5) foo reposetup
217 5) foo reposetup
218 5) bar reposetup
218 5) bar reposetup
219 4) foo uipopulate (chg !)
219 4) foo uipopulate (chg !)
220 4) bar uipopulate (chg !)
220 4) bar uipopulate (chg !)
221 4) foo uipopulate (chg !)
221 4) foo uipopulate (chg !)
222 4) bar uipopulate (chg !)
222 4) bar uipopulate (chg !)
223 4) foo uipopulate (chg !)
223 4) foo uipopulate (chg !)
224 4) bar uipopulate (chg !)
224 4) bar uipopulate (chg !)
225 4) foo uipopulate (chg !)
225 4) foo uipopulate (chg !)
226 4) bar uipopulate (chg !)
226 4) bar uipopulate (chg !)
227 4) foo uipopulate (chg !)
227 4) foo uipopulate (chg !)
228 4) bar uipopulate (chg !)
228 4) bar uipopulate (chg !)
229 5) foo reposetup (chg !)
229 5) foo reposetup (chg !)
230 5) bar reposetup (chg !)
230 5) bar reposetup (chg !)
231 0:c24b9ac61126
231 0:c24b9ac61126
232
232
233 Check hgweb's load order of extensions and registration of functions
233 Check hgweb's load order of extensions and registration of functions
234
234
235 $ cat > hgweb.cgi <<EOF
235 $ cat > hgweb.cgi <<EOF
236 > #!$PYTHON
236 > #!$PYTHON
237 > from mercurial import demandimport; demandimport.enable()
237 > from mercurial import demandimport; demandimport.enable()
238 > from mercurial.hgweb import hgweb
238 > from mercurial.hgweb import hgweb
239 > from mercurial.hgweb import wsgicgi
239 > from mercurial.hgweb import wsgicgi
240 > application = hgweb(b'.', b'test repo')
240 > application = hgweb(b'.', b'test repo')
241 > wsgicgi.launch(application)
241 > wsgicgi.launch(application)
242 > EOF
242 > EOF
243 $ . "$TESTDIR/cgienv"
243 $ . "$TESTDIR/cgienv"
244
244
245 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
245 $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
246 > | grep '^[0-9]) ' # ignores HTML output
246 > | grep '^[0-9]) ' # ignores HTML output
247 1) foo imported
247 1) foo imported
248 1) bar imported
248 1) bar imported
249 2) foo uisetup
249 2) foo uisetup
250 2) bar uisetup
250 2) bar uisetup
251 3) foo extsetup
251 3) foo extsetup
252 3) bar extsetup
252 3) bar extsetup
253 4) foo uipopulate
253 4) foo uipopulate
254 4) bar uipopulate
254 4) bar uipopulate
255 4) foo uipopulate
255 4) foo uipopulate
256 4) bar uipopulate
256 4) bar uipopulate
257 5) foo reposetup
257 5) foo reposetup
258 5) bar reposetup
258 5) bar reposetup
259
259
260 (check that revset predicate foo() and bar() are available)
260 (check that revset predicate foo() and bar() are available)
261
261
262 #if msys
262 #if msys
263 $ PATH_INFO='//shortlog'
263 $ PATH_INFO='//shortlog'
264 #else
264 #else
265 $ PATH_INFO='/shortlog'
265 $ PATH_INFO='/shortlog'
266 #endif
266 #endif
267 $ export PATH_INFO
267 $ export PATH_INFO
268 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
268 $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
269 > | grep '<a href="/rev/[0-9a-z]*">'
269 > | grep '<a href="/rev/[0-9a-z]*">'
270 <a href="/rev/c24b9ac61126">add file</a>
270 <a href="/rev/c24b9ac61126">add file</a>
271
271
272 $ echo 'foo = !' >> $HGRCPATH
272 $ echo 'foo = !' >> $HGRCPATH
273 $ echo 'bar = !' >> $HGRCPATH
273 $ echo 'bar = !' >> $HGRCPATH
274
274
275 Check "from __future__ import absolute_import" support for external libraries
275 Check "from __future__ import absolute_import" support for external libraries
276
276
277 (import-checker.py reports issues for some of heredoc python code
277 (import-checker.py reports issues for some of heredoc python code
278 fragments below, because import-checker.py does not know test specific
278 fragments below, because import-checker.py does not know test specific
279 package hierarchy. NO_CHECK_* should be used as a limit mark of
279 package hierarchy. NO_CHECK_* should be used as a limit mark of
280 heredoc, in order to make import-checker.py ignore them. For
280 heredoc, in order to make import-checker.py ignore them. For
281 simplicity, all python code fragments below are generated with such
281 simplicity, all python code fragments below are generated with such
282 limit mark, regardless of importing module or not.)
282 limit mark, regardless of importing module or not.)
283
283
284 #if windows
284 #if windows
285 $ PATHSEP=";"
285 $ PATHSEP=";"
286 #else
286 #else
287 $ PATHSEP=":"
287 $ PATHSEP=":"
288 #endif
288 #endif
289 $ export PATHSEP
289 $ export PATHSEP
290
290
291 $ mkdir $TESTTMP/libroot
291 $ mkdir $TESTTMP/libroot
292 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
292 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
293 $ mkdir $TESTTMP/libroot/mod
293 $ mkdir $TESTTMP/libroot/mod
294 $ touch $TESTTMP/libroot/mod/__init__.py
294 $ touch $TESTTMP/libroot/mod/__init__.py
295 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
295 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
296
296
297 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
297 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
298 > from __future__ import absolute_import, print_function
298 > from __future__ import absolute_import, print_function
299 > import ambig # should load "libroot/ambig.py"
299 > import ambig # should load "libroot/ambig.py"
300 > s = ambig.s
300 > s = ambig.s
301 > NO_CHECK_EOF
301 > NO_CHECK_EOF
302 $ cat > loadabs.py <<NO_CHECK_EOF
302 $ cat > loadabs.py <<NO_CHECK_EOF
303 > import mod.ambigabs as ambigabs
303 > import mod.ambigabs as ambigabs
304 > def extsetup(ui):
304 > def extsetup(ui):
305 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
305 > print('ambigabs.s=%s' % ambigabs.s, flush=True)
306 > NO_CHECK_EOF
306 > NO_CHECK_EOF
307 $ "$PYTHON" $TESTTMP/unflush.py loadabs.py
307 $ "$PYTHON" $TESTTMP/unflush.py loadabs.py
308 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
308 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
309 ambigabs.s=libroot/ambig.py
309 ambigabs.s=libroot/ambig.py
310 $TESTTMP/a
310 $TESTTMP/a
311
311
312 #if no-py3
312 #if no-py3
313 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
313 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
314 > from __future__ import print_function
314 > from __future__ import print_function
315 > import ambig # should load "libroot/mod/ambig.py"
315 > import ambig # should load "libroot/mod/ambig.py"
316 > s = ambig.s
316 > s = ambig.s
317 > NO_CHECK_EOF
317 > NO_CHECK_EOF
318 $ cat > loadrel.py <<NO_CHECK_EOF
318 $ cat > loadrel.py <<NO_CHECK_EOF
319 > import mod.ambigrel as ambigrel
319 > import mod.ambigrel as ambigrel
320 > def extsetup(ui):
320 > def extsetup(ui):
321 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
321 > print('ambigrel.s=%s' % ambigrel.s, flush=True)
322 > NO_CHECK_EOF
322 > NO_CHECK_EOF
323 $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
323 $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
324 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
324 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
325 ambigrel.s=libroot/mod/ambig.py
325 ambigrel.s=libroot/mod/ambig.py
326 $TESTTMP/a
326 $TESTTMP/a
327 #endif
327 #endif
328
328
329 Check absolute/relative import of extension specific modules
329 Check absolute/relative import of extension specific modules
330
330
331 $ mkdir $TESTTMP/extroot
331 $ mkdir $TESTTMP/extroot
332 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
332 $ cat > $TESTTMP/extroot/bar.py <<NO_CHECK_EOF
333 > s = b'this is extroot.bar'
333 > s = b'this is extroot.bar'
334 > NO_CHECK_EOF
334 > NO_CHECK_EOF
335 $ mkdir $TESTTMP/extroot/sub1
335 $ mkdir $TESTTMP/extroot/sub1
336 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
336 $ cat > $TESTTMP/extroot/sub1/__init__.py <<NO_CHECK_EOF
337 > s = b'this is extroot.sub1.__init__'
337 > s = b'this is extroot.sub1.__init__'
338 > NO_CHECK_EOF
338 > NO_CHECK_EOF
339 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
339 $ cat > $TESTTMP/extroot/sub1/baz.py <<NO_CHECK_EOF
340 > s = b'this is extroot.sub1.baz'
340 > s = b'this is extroot.sub1.baz'
341 > NO_CHECK_EOF
341 > NO_CHECK_EOF
342 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
342 $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
343 > from __future__ import absolute_import
343 > from __future__ import absolute_import
344 > s = b'this is extroot.__init__'
344 > s = b'this is extroot.__init__'
345 > from . import foo
345 > from . import foo
346 > def extsetup(ui):
346 > def extsetup(ui):
347 > ui.write(b'(extroot) ', foo.func(), b'\n')
347 > ui.write(b'(extroot) ', foo.func(), b'\n')
348 > ui.flush()
348 > ui.flush()
349 > NO_CHECK_EOF
349 > NO_CHECK_EOF
350
350
351 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
351 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
352 > # test absolute import
352 > # test absolute import
353 > buf = []
353 > buf = []
354 > def func():
354 > def func():
355 > # "not locals" case
355 > # "not locals" case
356 > import extroot.bar
356 > import extroot.bar
357 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
357 > buf.append(b'import extroot.bar in func(): %s' % extroot.bar.s)
358 > return b'\n(extroot) '.join(buf)
358 > return b'\n(extroot) '.join(buf)
359 > # b"fromlist == ('*',)" case
359 > # b"fromlist == ('*',)" case
360 > from extroot.bar import *
360 > from extroot.bar import *
361 > buf.append(b'from extroot.bar import *: %s' % s)
361 > buf.append(b'from extroot.bar import *: %s' % s)
362 > # "not fromlist" and "if '.' in name" case
362 > # "not fromlist" and "if '.' in name" case
363 > import extroot.sub1.baz
363 > import extroot.sub1.baz
364 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
364 > buf.append(b'import extroot.sub1.baz: %s' % extroot.sub1.baz.s)
365 > # "not fromlist" and NOT "if '.' in name" case
365 > # "not fromlist" and NOT "if '.' in name" case
366 > import extroot
366 > import extroot
367 > buf.append(b'import extroot: %s' % extroot.s)
367 > buf.append(b'import extroot: %s' % extroot.s)
368 > # NOT "not fromlist" and NOT "level != -1" case
368 > # NOT "not fromlist" and NOT "level != -1" case
369 > from extroot.bar import s
369 > from extroot.bar import s
370 > buf.append(b'from extroot.bar import s: %s' % s)
370 > buf.append(b'from extroot.bar import s: %s' % s)
371 > NO_CHECK_EOF
371 > NO_CHECK_EOF
372 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
372 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.extroot=$TESTTMP/extroot root)
373 (extroot) from extroot.bar import *: this is extroot.bar
373 (extroot) from extroot.bar import *: this is extroot.bar
374 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
374 (extroot) import extroot.sub1.baz: this is extroot.sub1.baz
375 (extroot) import extroot: this is extroot.__init__
375 (extroot) import extroot: this is extroot.__init__
376 (extroot) from extroot.bar import s: this is extroot.bar
376 (extroot) from extroot.bar import s: this is extroot.bar
377 (extroot) import extroot.bar in func(): this is extroot.bar
377 (extroot) import extroot.bar in func(): this is extroot.bar
378 $TESTTMP/a
378 $TESTTMP/a
379
379
380 #if no-py3
380 #if no-py3
381 $ rm "$TESTTMP"/extroot/foo.*
381 $ rm "$TESTTMP"/extroot/foo.*
382 $ rm -Rf "$TESTTMP/extroot/__pycache__"
382 $ rm -Rf "$TESTTMP/extroot/__pycache__"
383 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
383 $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
384 > # test relative import
384 > # test relative import
385 > buf = []
385 > buf = []
386 > def func():
386 > def func():
387 > # "not locals" case
387 > # "not locals" case
388 > import bar
388 > import bar
389 > buf.append('import bar in func(): %s' % bar.s)
389 > buf.append('import bar in func(): %s' % bar.s)
390 > return '\n(extroot) '.join(buf)
390 > return '\n(extroot) '.join(buf)
391 > # "fromlist == ('*',)" case
391 > # "fromlist == ('*',)" case
392 > from bar import *
392 > from bar import *
393 > buf.append('from bar import *: %s' % s)
393 > buf.append('from bar import *: %s' % s)
394 > # "not fromlist" and "if '.' in name" case
394 > # "not fromlist" and "if '.' in name" case
395 > import sub1.baz
395 > import sub1.baz
396 > buf.append('import sub1.baz: %s' % sub1.baz.s)
396 > buf.append('import sub1.baz: %s' % sub1.baz.s)
397 > # "not fromlist" and NOT "if '.' in name" case
397 > # "not fromlist" and NOT "if '.' in name" case
398 > import sub1
398 > import sub1
399 > buf.append('import sub1: %s' % sub1.s)
399 > buf.append('import sub1: %s' % sub1.s)
400 > # NOT "not fromlist" and NOT "level != -1" case
400 > # NOT "not fromlist" and NOT "level != -1" case
401 > from bar import s
401 > from bar import s
402 > buf.append('from bar import s: %s' % s)
402 > buf.append('from bar import s: %s' % s)
403 > NO_CHECK_EOF
403 > NO_CHECK_EOF
404 $ hg --config extensions.extroot=$TESTTMP/extroot root
404 $ hg --config extensions.extroot=$TESTTMP/extroot root
405 (extroot) from bar import *: this is extroot.bar
405 (extroot) from bar import *: this is extroot.bar
406 (extroot) import sub1.baz: this is extroot.sub1.baz
406 (extroot) import sub1.baz: this is extroot.sub1.baz
407 (extroot) import sub1: this is extroot.sub1.__init__
407 (extroot) import sub1: this is extroot.sub1.__init__
408 (extroot) from bar import s: this is extroot.bar
408 (extroot) from bar import s: this is extroot.bar
409 (extroot) import bar in func(): this is extroot.bar
409 (extroot) import bar in func(): this is extroot.bar
410 $TESTTMP/a
410 $TESTTMP/a
411 #endif
411 #endif
412
412
413 #if demandimport
413 #if demandimport
414
414
415 Examine whether module loading is delayed until actual referring, even
415 Examine whether module loading is delayed until actual referring, even
416 though module is imported with "absolute_import" feature.
416 though module is imported with "absolute_import" feature.
417
417
418 Files below in each packages are used for described purpose:
418 Files below in each packages are used for described purpose:
419
419
420 - "called": examine whether "from MODULE import ATTR" works correctly
420 - "called": examine whether "from MODULE import ATTR" works correctly
421 - "unused": examine whether loading is delayed correctly
421 - "unused": examine whether loading is delayed correctly
422 - "used": examine whether "from PACKAGE import MODULE" works correctly
422 - "used": examine whether "from PACKAGE import MODULE" works correctly
423
423
424 Package hierarchy is needed to examine whether demand importing works
424 Package hierarchy is needed to examine whether demand importing works
425 as expected for "from SUB.PACK.AGE import MODULE".
425 as expected for "from SUB.PACK.AGE import MODULE".
426
426
427 Setup "external library" to be imported with "absolute_import"
427 Setup "external library" to be imported with "absolute_import"
428 feature.
428 feature.
429
429
430 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
430 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
431 $ touch $TESTTMP/extlibroot/__init__.py
431 $ touch $TESTTMP/extlibroot/__init__.py
432 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
432 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
433 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
433 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
434
434
435 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
435 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<NO_CHECK_EOF
436 > def func():
436 > def func():
437 > return b"this is extlibroot.lsub1.lsub2.called.func()"
437 > return b"this is extlibroot.lsub1.lsub2.called.func()"
438 > NO_CHECK_EOF
438 > NO_CHECK_EOF
439 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
439 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<NO_CHECK_EOF
440 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
440 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
441 > NO_CHECK_EOF
441 > NO_CHECK_EOF
442 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
442 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<NO_CHECK_EOF
443 > detail = b"this is extlibroot.lsub1.lsub2.used"
443 > detail = b"this is extlibroot.lsub1.lsub2.used"
444 > NO_CHECK_EOF
444 > NO_CHECK_EOF
445
445
446 Setup sub-package of "external library", which causes instantiation of
446 Setup sub-package of "external library", which causes instantiation of
447 demandmod in "recurse down the module chain" code path. Relative
447 demandmod in "recurse down the module chain" code path. Relative
448 importing with "absolute_import" feature isn't tested, because "level
448 importing with "absolute_import" feature isn't tested, because "level
449 >=1 " doesn't cause instantiation of demandmod.
449 >=1 " doesn't cause instantiation of demandmod.
450
450
451 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
451 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
452 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
452 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<NO_CHECK_EOF
453 > detail = b"this is extlibroot.recursedown.abs.used"
453 > detail = b"this is extlibroot.recursedown.abs.used"
454 > NO_CHECK_EOF
454 > NO_CHECK_EOF
455 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
455 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
456 > from __future__ import absolute_import
456 > from __future__ import absolute_import
457 > from extlibroot.recursedown.abs.used import detail
457 > from extlibroot.recursedown.abs.used import detail
458 > NO_CHECK_EOF
458 > NO_CHECK_EOF
459
459
460 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
460 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
461 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
461 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<NO_CHECK_EOF
462 > detail = b"this is extlibroot.recursedown.legacy.used"
462 > detail = b"this is extlibroot.recursedown.legacy.used"
463 > NO_CHECK_EOF
463 > NO_CHECK_EOF
464 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
464 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<NO_CHECK_EOF
465 > # legacy style (level == -1) import
465 > # legacy style (level == -1) import
466 > from extlibroot.recursedown.legacy.used import detail
466 > from extlibroot.recursedown.legacy.used import detail
467 > NO_CHECK_EOF
467 > NO_CHECK_EOF
468
468
469 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
469 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
470 > from __future__ import absolute_import
470 > from __future__ import absolute_import
471 > from extlibroot.recursedown.abs import detail as absdetail
471 > from extlibroot.recursedown.abs import detail as absdetail
472 > from .legacy import detail as legacydetail
472 > from .legacy import detail as legacydetail
473 > NO_CHECK_EOF
473 > NO_CHECK_EOF
474
474
475 Setup package that re-exports an attribute of its submodule as the same
475 Setup package that re-exports an attribute of its submodule as the same
476 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
476 name. This leaves 'shadowing.used' pointing to 'used.detail', but still
477 the submodule 'used' should be somehow accessible. (issue5617)
477 the submodule 'used' should be somehow accessible. (issue5617)
478
478
479 $ mkdir -p $TESTTMP/extlibroot/shadowing
479 $ mkdir -p $TESTTMP/extlibroot/shadowing
480 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
480 $ cat > $TESTTMP/extlibroot/shadowing/used.py <<NO_CHECK_EOF
481 > detail = b"this is extlibroot.shadowing.used"
481 > detail = b"this is extlibroot.shadowing.used"
482 > NO_CHECK_EOF
482 > NO_CHECK_EOF
483 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
483 $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
484 > from __future__ import absolute_import
484 > from __future__ import absolute_import
485 > from extlibroot.shadowing.used import detail
485 > from extlibroot.shadowing.used import detail
486 > NO_CHECK_EOF
486 > NO_CHECK_EOF
487 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
487 $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
488 > from __future__ import absolute_import
488 > from __future__ import absolute_import
489 > from .used import detail as used
489 > from .used import detail as used
490 > NO_CHECK_EOF
490 > NO_CHECK_EOF
491
491
492 Setup extension local modules to be imported with "absolute_import"
492 Setup extension local modules to be imported with "absolute_import"
493 feature.
493 feature.
494
494
495 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
495 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
496 $ touch $TESTTMP/absextroot/xsub1/__init__.py
496 $ touch $TESTTMP/absextroot/xsub1/__init__.py
497 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
497 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
498
498
499 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
499 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<NO_CHECK_EOF
500 > def func():
500 > def func():
501 > return b"this is absextroot.xsub1.xsub2.called.func()"
501 > return b"this is absextroot.xsub1.xsub2.called.func()"
502 > NO_CHECK_EOF
502 > NO_CHECK_EOF
503 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
503 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<NO_CHECK_EOF
504 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
504 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
505 > NO_CHECK_EOF
505 > NO_CHECK_EOF
506 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
506 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<NO_CHECK_EOF
507 > detail = b"this is absextroot.xsub1.xsub2.used"
507 > detail = b"this is absextroot.xsub1.xsub2.used"
508 > NO_CHECK_EOF
508 > NO_CHECK_EOF
509
509
510 Setup extension local modules to examine whether demand importing
510 Setup extension local modules to examine whether demand importing
511 works as expected in "level > 1" case.
511 works as expected in "level > 1" case.
512
512
513 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
513 $ cat > $TESTTMP/absextroot/relimportee.py <<NO_CHECK_EOF
514 > detail = b"this is absextroot.relimportee"
514 > detail = b"this is absextroot.relimportee"
515 > NO_CHECK_EOF
515 > NO_CHECK_EOF
516 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
516 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
517 > from __future__ import absolute_import
517 > from __future__ import absolute_import
518 > from mercurial import pycompat
518 > from mercurial import pycompat
519 > from ... import relimportee
519 > from ... import relimportee
520 > detail = b"this relimporter imports %r" % (
520 > detail = b"this relimporter imports %r" % (
521 > pycompat.bytestr(relimportee.detail))
521 > pycompat.bytestr(relimportee.detail))
522 > NO_CHECK_EOF
522 > NO_CHECK_EOF
523
523
524 Setup modules, which actually import extension local modules at
524 Setup modules, which actually import extension local modules at
525 runtime.
525 runtime.
526
526
527 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
527 $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
528 > from __future__ import absolute_import
528 > from __future__ import absolute_import
529 >
529 >
530 > # import extension local modules absolutely (level = 0)
530 > # import extension local modules absolutely (level = 0)
531 > from absextroot.xsub1.xsub2 import used, unused
531 > from absextroot.xsub1.xsub2 import used, unused
532 > from absextroot.xsub1.xsub2.called import func
532 > from absextroot.xsub1.xsub2.called import func
533 >
533 >
534 > def getresult():
534 > def getresult():
535 > result = []
535 > result = []
536 > result.append(used.detail)
536 > result.append(used.detail)
537 > result.append(func())
537 > result.append(func())
538 > return result
538 > return result
539 > NO_CHECK_EOF
539 > NO_CHECK_EOF
540
540
541 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
541 $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
542 > from __future__ import absolute_import
542 > from __future__ import absolute_import
543 >
543 >
544 > # import extension local modules relatively (level == 1)
544 > # import extension local modules relatively (level == 1)
545 > from .xsub1.xsub2 import used, unused
545 > from .xsub1.xsub2 import used, unused
546 > from .xsub1.xsub2.called import func
546 > from .xsub1.xsub2.called import func
547 >
547 >
548 > # import a module, which implies "importing with level > 1"
548 > # import a module, which implies "importing with level > 1"
549 > from .xsub1.xsub2 import relimporter
549 > from .xsub1.xsub2 import relimporter
550 >
550 >
551 > def getresult():
551 > def getresult():
552 > result = []
552 > result = []
553 > result.append(used.detail)
553 > result.append(used.detail)
554 > result.append(func())
554 > result.append(func())
555 > result.append(relimporter.detail)
555 > result.append(relimporter.detail)
556 > return result
556 > return result
557 > NO_CHECK_EOF
557 > NO_CHECK_EOF
558
558
559 Setup main procedure of extension.
559 Setup main procedure of extension.
560
560
561 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
561 $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
562 > from __future__ import absolute_import
562 > from __future__ import absolute_import
563 > from mercurial import registrar
563 > from mercurial import registrar
564 > cmdtable = {}
564 > cmdtable = {}
565 > command = registrar.command(cmdtable)
565 > command = registrar.command(cmdtable)
566 >
566 >
567 > # "absolute" and "relative" shouldn't be imported before actual
567 > # "absolute" and "relative" shouldn't be imported before actual
568 > # command execution, because (1) they import same modules, and (2)
568 > # command execution, because (1) they import same modules, and (2)
569 > # preceding import (= instantiate "demandmod" object instead of
569 > # preceding import (= instantiate "demandmod" object instead of
570 > # real "module" object) might hide problem of succeeding import.
570 > # real "module" object) might hide problem of succeeding import.
571 >
571 >
572 > @command(b'showabsolute', [], norepo=True)
572 > @command(b'showabsolute', [], norepo=True)
573 > def showabsolute(ui, *args, **opts):
573 > def showabsolute(ui, *args, **opts):
574 > from absextroot import absolute
574 > from absextroot import absolute
575 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
575 > ui.write(b'ABS: %s\n' % b'\nABS: '.join(absolute.getresult()))
576 >
576 >
577 > @command(b'showrelative', [], norepo=True)
577 > @command(b'showrelative', [], norepo=True)
578 > def showrelative(ui, *args, **opts):
578 > def showrelative(ui, *args, **opts):
579 > from . import relative
579 > from . import relative
580 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
580 > ui.write(b'REL: %s\n' % b'\nREL: '.join(relative.getresult()))
581 >
581 >
582 > # import modules from external library
582 > # import modules from external library
583 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
583 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
584 > from extlibroot.lsub1.lsub2.called import func as lfunc
584 > from extlibroot.lsub1.lsub2.called import func as lfunc
585 > from extlibroot.recursedown import absdetail, legacydetail
585 > from extlibroot.recursedown import absdetail, legacydetail
586 > from extlibroot.shadowing import proxied
586 > from extlibroot.shadowing import proxied
587 >
587 >
588 > def uisetup(ui):
588 > def uisetup(ui):
589 > result = []
589 > result = []
590 > result.append(lused.detail)
590 > result.append(lused.detail)
591 > result.append(lfunc())
591 > result.append(lfunc())
592 > result.append(absdetail)
592 > result.append(absdetail)
593 > result.append(legacydetail)
593 > result.append(legacydetail)
594 > result.append(proxied.detail)
594 > result.append(proxied.detail)
595 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
595 > ui.write(b'LIB: %s\n' % b'\nLIB: '.join(result))
596 > NO_CHECK_EOF
596 > NO_CHECK_EOF
597
597
598 Examine module importing.
598 Examine module importing.
599
599
600 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
600 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
601 LIB: this is extlibroot.lsub1.lsub2.used
601 LIB: this is extlibroot.lsub1.lsub2.used
602 LIB: this is extlibroot.lsub1.lsub2.called.func()
602 LIB: this is extlibroot.lsub1.lsub2.called.func()
603 LIB: this is extlibroot.recursedown.abs.used
603 LIB: this is extlibroot.recursedown.abs.used
604 LIB: this is extlibroot.recursedown.legacy.used
604 LIB: this is extlibroot.recursedown.legacy.used
605 LIB: this is extlibroot.shadowing.used
605 LIB: this is extlibroot.shadowing.used
606 ABS: this is absextroot.xsub1.xsub2.used
606 ABS: this is absextroot.xsub1.xsub2.used
607 ABS: this is absextroot.xsub1.xsub2.called.func()
607 ABS: this is absextroot.xsub1.xsub2.called.func()
608
608
609 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
609 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
610 LIB: this is extlibroot.lsub1.lsub2.used
610 LIB: this is extlibroot.lsub1.lsub2.used
611 LIB: this is extlibroot.lsub1.lsub2.called.func()
611 LIB: this is extlibroot.lsub1.lsub2.called.func()
612 LIB: this is extlibroot.recursedown.abs.used
612 LIB: this is extlibroot.recursedown.abs.used
613 LIB: this is extlibroot.recursedown.legacy.used
613 LIB: this is extlibroot.recursedown.legacy.used
614 LIB: this is extlibroot.shadowing.used
614 LIB: this is extlibroot.shadowing.used
615 REL: this is absextroot.xsub1.xsub2.used
615 REL: this is absextroot.xsub1.xsub2.used
616 REL: this is absextroot.xsub1.xsub2.called.func()
616 REL: this is absextroot.xsub1.xsub2.called.func()
617 REL: this relimporter imports 'this is absextroot.relimportee'
617 REL: this relimporter imports 'this is absextroot.relimportee'
618
618
619 Examine whether sub-module is imported relatively as expected.
619 Examine whether sub-module is imported relatively as expected.
620
620
621 See also issue5208 for detail about example case on Python 3.x.
621 See also issue5208 for detail about example case on Python 3.x.
622
622
623 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
623 $ f -q $TESTTMP/extlibroot/lsub1/lsub2/notexist.py
624 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
624 $TESTTMP/extlibroot/lsub1/lsub2/notexist.py: file not found
625
625
626 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
626 $ cat > $TESTTMP/notexist.py <<NO_CHECK_EOF
627 > text = 'notexist.py at root is loaded unintentionally\n'
627 > text = 'notexist.py at root is loaded unintentionally\n'
628 > NO_CHECK_EOF
628 > NO_CHECK_EOF
629
629
630 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
630 $ cat > $TESTTMP/checkrelativity.py <<NO_CHECK_EOF
631 > from mercurial import registrar
631 > from mercurial import registrar
632 > cmdtable = {}
632 > cmdtable = {}
633 > command = registrar.command(cmdtable)
633 > command = registrar.command(cmdtable)
634 >
634 >
635 > # demand import avoids failure of importing notexist here, but only on
635 > # demand import avoids failure of importing notexist here, but only on
636 > # Python 2.
636 > # Python 2.
637 > import extlibroot.lsub1.lsub2.notexist
637 > import extlibroot.lsub1.lsub2.notexist
638 >
638 >
639 > @command(b'checkrelativity', [], norepo=True)
639 > @command(b'checkrelativity', [], norepo=True)
640 > def checkrelativity(ui, *args, **opts):
640 > def checkrelativity(ui, *args, **opts):
641 > try:
641 > try:
642 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
642 > ui.write(extlibroot.lsub1.lsub2.notexist.text)
643 > return 1 # unintentional success
643 > return 1 # unintentional success
644 > except ImportError:
644 > except ImportError:
645 > pass # intentional failure
645 > pass # intentional failure
646 > NO_CHECK_EOF
646 > NO_CHECK_EOF
647
647
648 Python 3's lazy importer verifies modules exist before returning the lazy
648 Python 3's lazy importer verifies modules exist before returning the lazy
649 module stub. Our custom lazy importer for Python 2 always returns a stub.
649 module stub. Our custom lazy importer for Python 2 always returns a stub.
650
650
651 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true
651 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity) || true
652 *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !)
652 *** failed to import extension checkrelativity from $TESTTMP/checkrelativity.py: No module named 'extlibroot.lsub1.lsub2.notexist' (py3 !)
653 hg: unknown command 'checkrelativity' (py3 !)
653 hg: unknown command 'checkrelativity' (py3 !)
654 (use 'hg help' for a list of commands) (py3 !)
654 (use 'hg help' for a list of commands) (py3 !)
655
655
656 #endif
656 #endif
657
657
658 (Here, module importing tests are finished. Therefore, use other than
658 (Here, module importing tests are finished. Therefore, use other than
659 NO_CHECK_* limit mark for heredoc python files, in order to apply
659 NO_CHECK_* limit mark for heredoc python files, in order to apply
660 import-checker.py or so on their contents)
660 import-checker.py or so on their contents)
661
661
662 Make sure a broken uisetup doesn't globally break hg:
662 Make sure a broken uisetup doesn't globally break hg:
663 $ cat > $TESTTMP/baduisetup.py <<EOF
663 $ cat > $TESTTMP/baduisetup.py <<EOF
664 > def uisetup(ui):
664 > def uisetup(ui):
665 > 1 / 0
665 > 1 / 0
666 > EOF
666 > EOF
667
667
668 Even though the extension fails during uisetup, hg is still basically usable:
668 Even though the extension fails during uisetup, hg is still basically usable:
669 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
669 $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
670 Traceback (most recent call last):
670 Traceback (most recent call last):
671 File "*/mercurial/extensions.py", line *, in _runuisetup (glob) (no-pyoxidizer !)
671 File "*/mercurial/extensions.py", line *, in _runuisetup (glob) (no-pyoxidizer !)
672 File "mercurial.extensions", line *, in _runuisetup (glob) (pyoxidizer !)
672 File "mercurial.extensions", line *, in _runuisetup (glob) (pyoxidizer !)
673 uisetup(ui)
673 uisetup(ui)
674 File "$TESTTMP/baduisetup.py", line 2, in uisetup
674 File "$TESTTMP/baduisetup.py", line 2, in uisetup
675 1 / 0
675 1 / 0
676 ZeroDivisionError: * by zero (glob)
676 ZeroDivisionError: * by zero (glob)
677 *** failed to set up extension baduisetup: * by zero (glob)
677 *** failed to set up extension baduisetup: * by zero (glob)
678 Mercurial Distributed SCM (version *) (glob)
678 Mercurial Distributed SCM (version *) (glob)
679 (see https://mercurial-scm.org for more information)
679 (see https://mercurial-scm.org for more information)
680
680
681 Copyright (C) 2005-* Olivia Mackall and others (glob)
681 Copyright (C) 2005-* Olivia Mackall and others (glob)
682 This is free software; see the source for copying conditions. There is NO
682 This is free software; see the source for copying conditions. There is NO
683 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
683 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
684
684
685 $ cd ..
685 $ cd ..
686
686
687 hide outer repo
687 hide outer repo
688 $ hg init
688 $ hg init
689
689
690 $ cat > empty.py <<EOF
690 $ cat > empty.py <<EOF
691 > '''empty cmdtable
691 > '''empty cmdtable
692 > '''
692 > '''
693 > cmdtable = {}
693 > cmdtable = {}
694 > EOF
694 > EOF
695 $ emptypath=`pwd`/empty.py
695 $ emptypath=`pwd`/empty.py
696 $ echo "empty = $emptypath" >> $HGRCPATH
696 $ echo "empty = $emptypath" >> $HGRCPATH
697 $ hg help empty
697 $ hg help empty
698 empty extension - empty cmdtable
698 empty extension - empty cmdtable
699
699
700 no commands defined
700 no commands defined
701
701
702
702
703 $ echo 'empty = !' >> $HGRCPATH
703 $ echo 'empty = !' >> $HGRCPATH
704
704
705 $ cat > debugextension.py <<EOF
705 $ cat > debugextension.py <<EOF
706 > '''only debugcommands
706 > '''only debugcommands
707 > '''
707 > '''
708 > from mercurial import registrar
708 > from mercurial import registrar
709 > cmdtable = {}
709 > cmdtable = {}
710 > command = registrar.command(cmdtable)
710 > command = registrar.command(cmdtable)
711 > @command(b'debugfoobar', [], b'hg debugfoobar')
711 > @command(b'debugfoobar', [], b'hg debugfoobar')
712 > def debugfoobar(ui, repo, *args, **opts):
712 > def debugfoobar(ui, repo, *args, **opts):
713 > "yet another debug command"
713 > "yet another debug command"
714 > @command(b'foo', [], b'hg foo')
714 > @command(b'foo', [], b'hg foo')
715 > def foo(ui, repo, *args, **opts):
715 > def foo(ui, repo, *args, **opts):
716 > """yet another foo command
716 > """yet another foo command
717 > This command has been DEPRECATED since forever.
717 > This command has been DEPRECATED since forever.
718 > """
718 > """
719 > EOF
719 > EOF
720 $ debugpath=`pwd`/debugextension.py
720 $ debugpath=`pwd`/debugextension.py
721 $ echo "debugextension = $debugpath" >> $HGRCPATH
721 $ echo "debugextension = $debugpath" >> $HGRCPATH
722
722
723 $ hg help debugextension
723 $ hg help debugextension
724 hg debugextensions
724 hg debugextensions
725
725
726 show information about active extensions
726 show information about active extensions
727
727
728 options:
728 options:
729
729
730 -T --template TEMPLATE display with template
730 -T --template TEMPLATE display with template
731
731
732 (some details hidden, use --verbose to show complete help)
732 (some details hidden, use --verbose to show complete help)
733
733
734
734
735 $ hg --verbose help debugextension
735 $ hg --verbose help debugextension
736 hg debugextensions
736 hg debugextensions
737
737
738 show information about active extensions
738 show information about active extensions
739
739
740 options:
740 options:
741
741
742 -T --template TEMPLATE display with template
742 -T --template TEMPLATE display with template
743
743
744 global options ([+] can be repeated):
744 global options ([+] can be repeated):
745
745
746 -R --repository REPO repository root directory or name of overlay bundle
746 -R --repository REPO repository root directory or name of overlay bundle
747 file
747 file
748 --cwd DIR change working directory
748 --cwd DIR change working directory
749 -y --noninteractive do not prompt, automatically pick the first choice for
749 -y --noninteractive do not prompt, automatically pick the first choice for
750 all prompts
750 all prompts
751 -q --quiet suppress output
751 -q --quiet suppress output
752 -v --verbose enable additional output
752 -v --verbose enable additional output
753 --color TYPE when to colorize (boolean, always, auto, never, or
753 --color TYPE when to colorize (boolean, always, auto, never, or
754 debug)
754 debug)
755 --config CONFIG [+] set/override config option (use 'section.name=value')
755 --config CONFIG [+] set/override config option (use 'section.name=value')
756 --debug enable debugging output
756 --debug enable debugging output
757 --debugger start debugger
757 --debugger start debugger
758 --encoding ENCODE set the charset encoding (default: ascii)
758 --encoding ENCODE set the charset encoding (default: ascii)
759 --encodingmode MODE set the charset encoding mode (default: strict)
759 --encodingmode MODE set the charset encoding mode (default: strict)
760 --traceback always print a traceback on exception
760 --traceback always print a traceback on exception
761 --time time how long the command takes
761 --time time how long the command takes
762 --profile print command execution profile
762 --profile print command execution profile
763 --version output version information and exit
763 --version output version information and exit
764 -h --help display help and exit
764 -h --help display help and exit
765 --hidden consider hidden changesets
765 --hidden consider hidden changesets
766 --pager TYPE when to paginate (boolean, always, auto, or never)
766 --pager TYPE when to paginate (boolean, always, auto, or never)
767 (default: auto)
767 (default: auto)
768
768
769
769
770
770
771
771
772
772
773
773
774 $ hg --debug help debugextension
774 $ hg --debug help debugextension
775 hg debugextensions
775 hg debugextensions
776
776
777 show information about active extensions
777 show information about active extensions
778
778
779 options:
779 options:
780
780
781 -T --template TEMPLATE display with template
781 -T --template TEMPLATE display with template
782
782
783 global options ([+] can be repeated):
783 global options ([+] can be repeated):
784
784
785 -R --repository REPO repository root directory or name of overlay bundle
785 -R --repository REPO repository root directory or name of overlay bundle
786 file
786 file
787 --cwd DIR change working directory
787 --cwd DIR change working directory
788 -y --noninteractive do not prompt, automatically pick the first choice for
788 -y --noninteractive do not prompt, automatically pick the first choice for
789 all prompts
789 all prompts
790 -q --quiet suppress output
790 -q --quiet suppress output
791 -v --verbose enable additional output
791 -v --verbose enable additional output
792 --color TYPE when to colorize (boolean, always, auto, never, or
792 --color TYPE when to colorize (boolean, always, auto, never, or
793 debug)
793 debug)
794 --config CONFIG [+] set/override config option (use 'section.name=value')
794 --config CONFIG [+] set/override config option (use 'section.name=value')
795 --debug enable debugging output
795 --debug enable debugging output
796 --debugger start debugger
796 --debugger start debugger
797 --encoding ENCODE set the charset encoding (default: ascii)
797 --encoding ENCODE set the charset encoding (default: ascii)
798 --encodingmode MODE set the charset encoding mode (default: strict)
798 --encodingmode MODE set the charset encoding mode (default: strict)
799 --traceback always print a traceback on exception
799 --traceback always print a traceback on exception
800 --time time how long the command takes
800 --time time how long the command takes
801 --profile print command execution profile
801 --profile print command execution profile
802 --version output version information and exit
802 --version output version information and exit
803 -h --help display help and exit
803 -h --help display help and exit
804 --hidden consider hidden changesets
804 --hidden consider hidden changesets
805 --pager TYPE when to paginate (boolean, always, auto, or never)
805 --pager TYPE when to paginate (boolean, always, auto, or never)
806 (default: auto)
806 (default: auto)
807
807
808
808
809
809
810
810
811
811
812 $ echo 'debugextension = !' >> $HGRCPATH
812 $ echo 'debugextension = !' >> $HGRCPATH
813
813
814 Asking for help about a deprecated extension should do something useful:
814 Asking for help about a deprecated extension should do something useful:
815
815
816 $ hg help glog
816 $ hg help glog
817 'glog' is provided by the following extension:
817 'glog' is provided by the following extension:
818
818
819 graphlog command to view revision graphs from a shell (DEPRECATED)
819 graphlog command to view revision graphs from a shell (DEPRECATED)
820
820
821 (use 'hg help extensions' for information on enabling extensions)
821 (use 'hg help extensions' for information on enabling extensions)
822
822
823 Extension module help vs command help:
823 Extension module help vs command help:
824
824
825 $ echo 'extdiff =' >> $HGRCPATH
825 $ echo 'extdiff =' >> $HGRCPATH
826 $ hg help extdiff
826 $ hg help extdiff
827 hg extdiff [OPT]... [FILE]...
827 hg extdiff [OPT]... [FILE]...
828
828
829 use external program to diff repository (or selected files)
829 use external program to diff repository (or selected files)
830
830
831 Show differences between revisions for the specified files, using an
831 Show differences between revisions for the specified files, using an
832 external program. The default program used is diff, with default options
832 external program. The default program used is diff, with default options
833 "-Npru".
833 "-Npru".
834
834
835 To select a different program, use the -p/--program option. The program
835 To select a different program, use the -p/--program option. The program
836 will be passed the names of two directories to compare, unless the --per-
836 will be passed the names of two directories to compare, unless the --per-
837 file option is specified (see below). To pass additional options to the
837 file option is specified (see below). To pass additional options to the
838 program, use -o/--option. These will be passed before the names of the
838 program, use -o/--option. These will be passed before the names of the
839 directories or files to compare.
839 directories or files to compare.
840
840
841 The --from, --to, and --change options work the same way they do for 'hg
841 The --from, --to, and --change options work the same way they do for 'hg
842 diff'.
842 diff'.
843
843
844 The --per-file option runs the external program repeatedly on each file to
844 The --per-file option runs the external program repeatedly on each file to
845 diff, instead of once on two directories. By default, this happens one by
845 diff, instead of once on two directories. By default, this happens one by
846 one, where the next file diff is open in the external program only once
846 one, where the next file diff is open in the external program only once
847 the previous external program (for the previous file diff) has exited. If
847 the previous external program (for the previous file diff) has exited. If
848 the external program has a graphical interface, it can open all the file
848 the external program has a graphical interface, it can open all the file
849 diffs at once instead of one by one. See 'hg help -e extdiff' for
849 diffs at once instead of one by one. See 'hg help -e extdiff' for
850 information about how to tell Mercurial that a given program has a
850 information about how to tell Mercurial that a given program has a
851 graphical interface.
851 graphical interface.
852
852
853 The --confirm option will prompt the user before each invocation of the
853 The --confirm option will prompt the user before each invocation of the
854 external program. It is ignored if --per-file isn't specified.
854 external program. It is ignored if --per-file isn't specified.
855
855
856 (use 'hg help -e extdiff' to show help for the extdiff extension)
856 (use 'hg help -e extdiff' to show help for the extdiff extension)
857
857
858 options ([+] can be repeated):
858 options ([+] can be repeated):
859
859
860 -p --program CMD comparison program to run
860 -p --program CMD comparison program to run
861 -o --option OPT [+] pass option to comparison program
861 -o --option OPT [+] pass option to comparison program
862 --from REV1 revision to diff from
862 --from REV1 revision to diff from
863 --to REV2 revision to diff to
863 --to REV2 revision to diff to
864 -c --change REV change made by revision
864 -c --change REV change made by revision
865 --per-file compare each file instead of revision snapshots
865 --per-file compare each file instead of revision snapshots
866 --confirm prompt user before each external program invocation
866 --confirm prompt user before each external program invocation
867 --patch compare patches for two revisions
867 --patch compare patches for two revisions
868 -I --include PATTERN [+] include names matching the given patterns
868 -I --include PATTERN [+] include names matching the given patterns
869 -X --exclude PATTERN [+] exclude names matching the given patterns
869 -X --exclude PATTERN [+] exclude names matching the given patterns
870 -S --subrepos recurse into subrepositories
870 -S --subrepos recurse into subrepositories
871
871
872 (some details hidden, use --verbose to show complete help)
872 (some details hidden, use --verbose to show complete help)
873
873
874
874
875
875
876
876
877
877
878
878
879
879
880
880
881
881
882
882
883 $ hg help --extension extdiff
883 $ hg help --extension extdiff
884 extdiff extension - command to allow external programs to compare revisions
884 extdiff extension - command to allow external programs to compare revisions
885
885
886 The extdiff Mercurial extension allows you to use external programs to compare
886 The extdiff Mercurial extension allows you to use external programs to compare
887 revisions, or revision with working directory. The external diff programs are
887 revisions, or revision with working directory. The external diff programs are
888 called with a configurable set of options and two non-option arguments: paths
888 called with a configurable set of options and two non-option arguments: paths
889 to directories containing snapshots of files to compare.
889 to directories containing snapshots of files to compare.
890
890
891 If there is more than one file being compared and the "child" revision is the
891 If there is more than one file being compared and the "child" revision is the
892 working directory, any modifications made in the external diff program will be
892 working directory, any modifications made in the external diff program will be
893 copied back to the working directory from the temporary directory.
893 copied back to the working directory from the temporary directory.
894
894
895 The extdiff extension also allows you to configure new diff commands, so you
895 The extdiff extension also allows you to configure new diff commands, so you
896 do not need to type 'hg extdiff -p kdiff3' always.
896 do not need to type 'hg extdiff -p kdiff3' always.
897
897
898 [extdiff]
898 [extdiff]
899 # add new command that runs GNU diff(1) in 'context diff' mode
899 # add new command that runs GNU diff(1) in 'context diff' mode
900 cdiff = gdiff -Nprc5
900 cdiff = gdiff -Nprc5
901 ## or the old way:
901 ## or the old way:
902 #cmd.cdiff = gdiff
902 #cmd.cdiff = gdiff
903 #opts.cdiff = -Nprc5
903 #opts.cdiff = -Nprc5
904
904
905 # add new command called meld, runs meld (no need to name twice). If
905 # add new command called meld, runs meld (no need to name twice). If
906 # the meld executable is not available, the meld tool in [merge-tools]
906 # the meld executable is not available, the meld tool in [merge-tools]
907 # will be used, if available
907 # will be used, if available
908 meld =
908 meld =
909
909
910 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
910 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
911 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
911 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
912 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
912 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
913 # your .vimrc
913 # your .vimrc
914 vimdiff = gvim -f "+next" \
914 vimdiff = gvim -f "+next" \
915 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
915 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
916
916
917 Tool arguments can include variables that are expanded at runtime:
917 Tool arguments can include variables that are expanded at runtime:
918
918
919 $parent1, $plabel1 - filename, descriptive label of first parent
919 $parent1, $plabel1 - filename, descriptive label of first parent
920 $child, $clabel - filename, descriptive label of child revision
920 $child, $clabel - filename, descriptive label of child revision
921 $parent2, $plabel2 - filename, descriptive label of second parent
921 $parent2, $plabel2 - filename, descriptive label of second parent
922 $root - repository root
922 $root - repository root
923 $parent is an alias for $parent1.
923 $parent is an alias for $parent1.
924
924
925 The extdiff extension will look in your [diff-tools] and [merge-tools]
925 The extdiff extension will look in your [diff-tools] and [merge-tools]
926 sections for diff tool arguments, when none are specified in [extdiff].
926 sections for diff tool arguments, when none are specified in [extdiff].
927
927
928 [extdiff]
928 [extdiff]
929 kdiff3 =
929 kdiff3 =
930
930
931 [diff-tools]
931 [diff-tools]
932 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
932 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
933
933
934 If a program has a graphical interface, it might be interesting to tell
934 If a program has a graphical interface, it might be interesting to tell
935 Mercurial about it. It will prevent the program from being mistakenly used in
935 Mercurial about it. It will prevent the program from being mistakenly used in
936 a terminal-only environment (such as an SSH terminal session), and will make
936 a terminal-only environment (such as an SSH terminal session), and will make
937 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
937 'hg extdiff --per-file' open multiple file diffs at once instead of one by one
938 (if you still want to open file diffs one by one, you can use the --confirm
938 (if you still want to open file diffs one by one, you can use the --confirm
939 option).
939 option).
940
940
941 Declaring that a tool has a graphical interface can be done with the "gui"
941 Declaring that a tool has a graphical interface can be done with the "gui"
942 flag next to where "diffargs" are specified:
942 flag next to where "diffargs" are specified:
943
943
944 [diff-tools]
944 [diff-tools]
945 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
945 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
946 kdiff3.gui = true
946 kdiff3.gui = true
947
947
948 You can use -I/-X and list of file or directory names like normal 'hg diff'
948 You can use -I/-X and list of file or directory names like normal 'hg diff'
949 command. The extdiff extension makes snapshots of only needed files, so
949 command. The extdiff extension makes snapshots of only needed files, so
950 running the external diff program will actually be pretty fast (at least
950 running the external diff program will actually be pretty fast (at least
951 faster than having to compare the entire tree).
951 faster than having to compare the entire tree).
952
952
953 list of commands:
953 list of commands:
954
954
955 extdiff use external program to diff repository (or selected files)
955 extdiff use external program to diff repository (or selected files)
956
956
957 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
957 (use 'hg help -v -e extdiff' to show built-in aliases and global options)
958
958
959
959
960
960
961
961
962
962
963
963
964
964
965
965
966
966
967
967
968
968
969
969
970
970
971
971
972
972
973
973
974 $ echo 'extdiff = !' >> $HGRCPATH
974 $ echo 'extdiff = !' >> $HGRCPATH
975
975
976 Test help topic with same name as extension
976 Test help topic with same name as extension
977
977
978 $ cat > multirevs.py <<EOF
978 $ cat > multirevs.py <<EOF
979 > from mercurial import commands, registrar
979 > from mercurial import commands, registrar
980 > cmdtable = {}
980 > cmdtable = {}
981 > command = registrar.command(cmdtable)
981 > command = registrar.command(cmdtable)
982 > """multirevs extension
982 > """multirevs extension
983 > Big multi-line module docstring."""
983 > Big multi-line module docstring."""
984 > @command(b'multirevs', [], b'ARG', norepo=True)
984 > @command(b'multirevs', [], b'ARG', norepo=True)
985 > def multirevs(ui, repo, arg, *args, **opts):
985 > def multirevs(ui, repo, arg, *args, **opts):
986 > """multirevs command"""
986 > """multirevs command"""
987 > EOF
987 > EOF
988 $ echo "multirevs = multirevs.py" >> $HGRCPATH
988 $ echo "multirevs = multirevs.py" >> $HGRCPATH
989
989
990 $ hg help multirevs | tail
990 $ hg help multirevs | tail
991 used):
991 used):
992
992
993 hg update :@
993 hg update :@
994
994
995 - Show diff between tags 1.3 and 1.5 (this works because the first and the
995 - Show diff between tags 1.3 and 1.5 (this works because the first and the
996 last revisions of the revset are used):
996 last revisions of the revset are used):
997
997
998 hg diff -r 1.3::1.5
998 hg diff -r 1.3::1.5
999
999
1000 use 'hg help -c multirevs' to see help for the multirevs command
1000 use 'hg help -c multirevs' to see help for the multirevs command
1001
1001
1002
1002
1003
1003
1004
1004
1005
1005
1006
1006
1007 $ hg help -c multirevs
1007 $ hg help -c multirevs
1008 hg multirevs ARG
1008 hg multirevs ARG
1009
1009
1010 multirevs command
1010 multirevs command
1011
1011
1012 (some details hidden, use --verbose to show complete help)
1012 (some details hidden, use --verbose to show complete help)
1013
1013
1014
1014
1015
1015
1016 $ hg multirevs
1016 $ hg multirevs
1017 hg multirevs: invalid arguments
1017 hg multirevs: invalid arguments
1018 hg multirevs ARG
1018 hg multirevs ARG
1019
1019
1020 multirevs command
1020 multirevs command
1021
1021
1022 (use 'hg multirevs -h' to show more help)
1022 (use 'hg multirevs -h' to show more help)
1023 [10]
1023 [10]
1024
1024
1025
1025
1026
1026
1027 $ echo "multirevs = !" >> $HGRCPATH
1027 $ echo "multirevs = !" >> $HGRCPATH
1028
1028
1029 Issue811: Problem loading extensions twice (by site and by user)
1029 Issue811: Problem loading extensions twice (by site and by user)
1030
1030
1031 $ cat <<EOF >> $HGRCPATH
1031 $ cat <<EOF >> $HGRCPATH
1032 > mq =
1032 > mq =
1033 > strip =
1033 > strip =
1034 > hgext.mq =
1034 > hgext.mq =
1035 > hgext/mq =
1035 > hgext/mq =
1036 > EOF
1036 > EOF
1037
1037
1038 Show extensions:
1038 Show extensions:
1039 (note that mq force load strip, also checking it's not loaded twice)
1039 (note that mq force load strip, also checking it's not loaded twice)
1040
1040
1041 #if no-extraextensions
1041 #if no-extraextensions
1042 $ hg debugextensions
1042 $ hg debugextensions
1043 mq
1043 mq
1044 strip
1044 strip
1045 #endif
1045 #endif
1046
1046
1047 For extensions, which name matches one of its commands, help
1047 For extensions, which name matches one of its commands, help
1048 message should ask '-v -e' to get list of built-in aliases
1048 message should ask '-v -e' to get list of built-in aliases
1049 along with extension help itself
1049 along with extension help itself
1050
1050
1051 $ mkdir $TESTTMP/d
1051 $ mkdir $TESTTMP/d
1052 $ cat > $TESTTMP/d/dodo.py <<EOF
1052 $ cat > $TESTTMP/d/dodo.py <<EOF
1053 > """
1053 > """
1054 > This is an awesome 'dodo' extension. It does nothing and
1054 > This is an awesome 'dodo' extension. It does nothing and
1055 > writes 'Foo foo'
1055 > writes 'Foo foo'
1056 > """
1056 > """
1057 > from mercurial import commands, registrar
1057 > from mercurial import commands, registrar
1058 > cmdtable = {}
1058 > cmdtable = {}
1059 > command = registrar.command(cmdtable)
1059 > command = registrar.command(cmdtable)
1060 > @command(b'dodo', [], b'hg dodo')
1060 > @command(b'dodo', [], b'hg dodo')
1061 > def dodo(ui, *args, **kwargs):
1061 > def dodo(ui, *args, **kwargs):
1062 > """Does nothing"""
1062 > """Does nothing"""
1063 > ui.write(b"I do nothing. Yay\\n")
1063 > ui.write(b"I do nothing. Yay\\n")
1064 > @command(b'foofoo', [], b'hg foofoo')
1064 > @command(b'foofoo', [], b'hg foofoo')
1065 > def foofoo(ui, *args, **kwargs):
1065 > def foofoo(ui, *args, **kwargs):
1066 > """Writes 'Foo foo'"""
1066 > """Writes 'Foo foo'"""
1067 > ui.write(b"Foo foo\\n")
1067 > ui.write(b"Foo foo\\n")
1068 > EOF
1068 > EOF
1069 $ dodopath=$TESTTMP/d/dodo.py
1069 $ dodopath=$TESTTMP/d/dodo.py
1070
1070
1071 $ echo "dodo = $dodopath" >> $HGRCPATH
1071 $ echo "dodo = $dodopath" >> $HGRCPATH
1072
1072
1073 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
1073 Make sure that user is asked to enter '-v -e' to get list of built-in aliases
1074 $ hg help -e dodo
1074 $ hg help -e dodo
1075 dodo extension -
1075 dodo extension -
1076
1076
1077 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1077 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1078
1078
1079 list of commands:
1079 list of commands:
1080
1080
1081 dodo Does nothing
1081 dodo Does nothing
1082 foofoo Writes 'Foo foo'
1082 foofoo Writes 'Foo foo'
1083
1083
1084 (use 'hg help -v -e dodo' to show built-in aliases and global options)
1084 (use 'hg help -v -e dodo' to show built-in aliases and global options)
1085
1085
1086 Make sure that '-v -e' prints list of built-in aliases along with
1086 Make sure that '-v -e' prints list of built-in aliases along with
1087 extension help itself
1087 extension help itself
1088 $ hg help -v -e dodo
1088 $ hg help -v -e dodo
1089 dodo extension -
1089 dodo extension -
1090
1090
1091 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1091 This is an awesome 'dodo' extension. It does nothing and writes 'Foo foo'
1092
1092
1093 list of commands:
1093 list of commands:
1094
1094
1095 dodo Does nothing
1095 dodo Does nothing
1096 foofoo Writes 'Foo foo'
1096 foofoo Writes 'Foo foo'
1097
1097
1098 global options ([+] can be repeated):
1098 global options ([+] can be repeated):
1099
1099
1100 -R --repository REPO repository root directory or name of overlay bundle
1100 -R --repository REPO repository root directory or name of overlay bundle
1101 file
1101 file
1102 --cwd DIR change working directory
1102 --cwd DIR change working directory
1103 -y --noninteractive do not prompt, automatically pick the first choice for
1103 -y --noninteractive do not prompt, automatically pick the first choice for
1104 all prompts
1104 all prompts
1105 -q --quiet suppress output
1105 -q --quiet suppress output
1106 -v --verbose enable additional output
1106 -v --verbose enable additional output
1107 --color TYPE when to colorize (boolean, always, auto, never, or
1107 --color TYPE when to colorize (boolean, always, auto, never, or
1108 debug)
1108 debug)
1109 --config CONFIG [+] set/override config option (use 'section.name=value')
1109 --config CONFIG [+] set/override config option (use 'section.name=value')
1110 --debug enable debugging output
1110 --debug enable debugging output
1111 --debugger start debugger
1111 --debugger start debugger
1112 --encoding ENCODE set the charset encoding (default: ascii)
1112 --encoding ENCODE set the charset encoding (default: ascii)
1113 --encodingmode MODE set the charset encoding mode (default: strict)
1113 --encodingmode MODE set the charset encoding mode (default: strict)
1114 --traceback always print a traceback on exception
1114 --traceback always print a traceback on exception
1115 --time time how long the command takes
1115 --time time how long the command takes
1116 --profile print command execution profile
1116 --profile print command execution profile
1117 --version output version information and exit
1117 --version output version information and exit
1118 -h --help display help and exit
1118 -h --help display help and exit
1119 --hidden consider hidden changesets
1119 --hidden consider hidden changesets
1120 --pager TYPE when to paginate (boolean, always, auto, or never)
1120 --pager TYPE when to paginate (boolean, always, auto, or never)
1121 (default: auto)
1121 (default: auto)
1122
1122
1123 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1123 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
1124 $ hg help -v dodo
1124 $ hg help -v dodo
1125 hg dodo
1125 hg dodo
1126
1126
1127 Does nothing
1127 Does nothing
1128
1128
1129 (use 'hg help -e dodo' to show help for the dodo extension)
1129 (use 'hg help -e dodo' to show help for the dodo extension)
1130
1130
1131 options:
1131 options:
1132
1132
1133 --mq operate on patch repository
1133 --mq operate on patch repository
1134
1134
1135 global options ([+] can be repeated):
1135 global options ([+] can be repeated):
1136
1136
1137 -R --repository REPO repository root directory or name of overlay bundle
1137 -R --repository REPO repository root directory or name of overlay bundle
1138 file
1138 file
1139 --cwd DIR change working directory
1139 --cwd DIR change working directory
1140 -y --noninteractive do not prompt, automatically pick the first choice for
1140 -y --noninteractive do not prompt, automatically pick the first choice for
1141 all prompts
1141 all prompts
1142 -q --quiet suppress output
1142 -q --quiet suppress output
1143 -v --verbose enable additional output
1143 -v --verbose enable additional output
1144 --color TYPE when to colorize (boolean, always, auto, never, or
1144 --color TYPE when to colorize (boolean, always, auto, never, or
1145 debug)
1145 debug)
1146 --config CONFIG [+] set/override config option (use 'section.name=value')
1146 --config CONFIG [+] set/override config option (use 'section.name=value')
1147 --debug enable debugging output
1147 --debug enable debugging output
1148 --debugger start debugger
1148 --debugger start debugger
1149 --encoding ENCODE set the charset encoding (default: ascii)
1149 --encoding ENCODE set the charset encoding (default: ascii)
1150 --encodingmode MODE set the charset encoding mode (default: strict)
1150 --encodingmode MODE set the charset encoding mode (default: strict)
1151 --traceback always print a traceback on exception
1151 --traceback always print a traceback on exception
1152 --time time how long the command takes
1152 --time time how long the command takes
1153 --profile print command execution profile
1153 --profile print command execution profile
1154 --version output version information and exit
1154 --version output version information and exit
1155 -h --help display help and exit
1155 -h --help display help and exit
1156 --hidden consider hidden changesets
1156 --hidden consider hidden changesets
1157 --pager TYPE when to paginate (boolean, always, auto, or never)
1157 --pager TYPE when to paginate (boolean, always, auto, or never)
1158 (default: auto)
1158 (default: auto)
1159
1159
1160 In case when extension name doesn't match any of its commands,
1160 In case when extension name doesn't match any of its commands,
1161 help message should ask for '-v' to get list of built-in aliases
1161 help message should ask for '-v' to get list of built-in aliases
1162 along with extension help
1162 along with extension help
1163 $ cat > $TESTTMP/d/dudu.py <<EOF
1163 $ cat > $TESTTMP/d/dudu.py <<EOF
1164 > """
1164 > """
1165 > This is an awesome 'dudu' extension. It does something and
1165 > This is an awesome 'dudu' extension. It does something and
1166 > also writes 'Beep beep'
1166 > also writes 'Beep beep'
1167 > """
1167 > """
1168 > from mercurial import commands, registrar
1168 > from mercurial import commands, registrar
1169 > cmdtable = {}
1169 > cmdtable = {}
1170 > command = registrar.command(cmdtable)
1170 > command = registrar.command(cmdtable)
1171 > @command(b'something', [], b'hg something')
1171 > @command(b'something', [], b'hg something')
1172 > def something(ui, *args, **kwargs):
1172 > def something(ui, *args, **kwargs):
1173 > """Does something"""
1173 > """Does something"""
1174 > ui.write(b"I do something. Yaaay\\n")
1174 > ui.write(b"I do something. Yaaay\\n")
1175 > @command(b'beep', [], b'hg beep')
1175 > @command(b'beep', [], b'hg beep')
1176 > def beep(ui, *args, **kwargs):
1176 > def beep(ui, *args, **kwargs):
1177 > """Writes 'Beep beep'"""
1177 > """Writes 'Beep beep'"""
1178 > ui.write(b"Beep beep\\n")
1178 > ui.write(b"Beep beep\\n")
1179 > EOF
1179 > EOF
1180 $ dudupath=$TESTTMP/d/dudu.py
1180 $ dudupath=$TESTTMP/d/dudu.py
1181
1181
1182 $ echo "dudu = $dudupath" >> $HGRCPATH
1182 $ echo "dudu = $dudupath" >> $HGRCPATH
1183
1183
1184 $ hg help -e dudu
1184 $ hg help -e dudu
1185 dudu extension -
1185 dudu extension -
1186
1186
1187 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1187 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1188 beep'
1188 beep'
1189
1189
1190 list of commands:
1190 list of commands:
1191
1191
1192 beep Writes 'Beep beep'
1192 beep Writes 'Beep beep'
1193 something Does something
1193 something Does something
1194
1194
1195 (use 'hg help -v dudu' to show built-in aliases and global options)
1195 (use 'hg help -v dudu' to show built-in aliases and global options)
1196
1196
1197 In case when extension name doesn't match any of its commands,
1197 In case when extension name doesn't match any of its commands,
1198 help options '-v' and '-v -e' should be equivalent
1198 help options '-v' and '-v -e' should be equivalent
1199 $ hg help -v dudu
1199 $ hg help -v dudu
1200 dudu extension -
1200 dudu extension -
1201
1201
1202 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1202 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1203 beep'
1203 beep'
1204
1204
1205 list of commands:
1205 list of commands:
1206
1206
1207 beep Writes 'Beep beep'
1207 beep Writes 'Beep beep'
1208 something Does something
1208 something Does something
1209
1209
1210 global options ([+] can be repeated):
1210 global options ([+] can be repeated):
1211
1211
1212 -R --repository REPO repository root directory or name of overlay bundle
1212 -R --repository REPO repository root directory or name of overlay bundle
1213 file
1213 file
1214 --cwd DIR change working directory
1214 --cwd DIR change working directory
1215 -y --noninteractive do not prompt, automatically pick the first choice for
1215 -y --noninteractive do not prompt, automatically pick the first choice for
1216 all prompts
1216 all prompts
1217 -q --quiet suppress output
1217 -q --quiet suppress output
1218 -v --verbose enable additional output
1218 -v --verbose enable additional output
1219 --color TYPE when to colorize (boolean, always, auto, never, or
1219 --color TYPE when to colorize (boolean, always, auto, never, or
1220 debug)
1220 debug)
1221 --config CONFIG [+] set/override config option (use 'section.name=value')
1221 --config CONFIG [+] set/override config option (use 'section.name=value')
1222 --debug enable debugging output
1222 --debug enable debugging output
1223 --debugger start debugger
1223 --debugger start debugger
1224 --encoding ENCODE set the charset encoding (default: ascii)
1224 --encoding ENCODE set the charset encoding (default: ascii)
1225 --encodingmode MODE set the charset encoding mode (default: strict)
1225 --encodingmode MODE set the charset encoding mode (default: strict)
1226 --traceback always print a traceback on exception
1226 --traceback always print a traceback on exception
1227 --time time how long the command takes
1227 --time time how long the command takes
1228 --profile print command execution profile
1228 --profile print command execution profile
1229 --version output version information and exit
1229 --version output version information and exit
1230 -h --help display help and exit
1230 -h --help display help and exit
1231 --hidden consider hidden changesets
1231 --hidden consider hidden changesets
1232 --pager TYPE when to paginate (boolean, always, auto, or never)
1232 --pager TYPE when to paginate (boolean, always, auto, or never)
1233 (default: auto)
1233 (default: auto)
1234
1234
1235 $ hg help -v -e dudu
1235 $ hg help -v -e dudu
1236 dudu extension -
1236 dudu extension -
1237
1237
1238 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1238 This is an awesome 'dudu' extension. It does something and also writes 'Beep
1239 beep'
1239 beep'
1240
1240
1241 list of commands:
1241 list of commands:
1242
1242
1243 beep Writes 'Beep beep'
1243 beep Writes 'Beep beep'
1244 something Does something
1244 something Does something
1245
1245
1246 global options ([+] can be repeated):
1246 global options ([+] can be repeated):
1247
1247
1248 -R --repository REPO repository root directory or name of overlay bundle
1248 -R --repository REPO repository root directory or name of overlay bundle
1249 file
1249 file
1250 --cwd DIR change working directory
1250 --cwd DIR change working directory
1251 -y --noninteractive do not prompt, automatically pick the first choice for
1251 -y --noninteractive do not prompt, automatically pick the first choice for
1252 all prompts
1252 all prompts
1253 -q --quiet suppress output
1253 -q --quiet suppress output
1254 -v --verbose enable additional output
1254 -v --verbose enable additional output
1255 --color TYPE when to colorize (boolean, always, auto, never, or
1255 --color TYPE when to colorize (boolean, always, auto, never, or
1256 debug)
1256 debug)
1257 --config CONFIG [+] set/override config option (use 'section.name=value')
1257 --config CONFIG [+] set/override config option (use 'section.name=value')
1258 --debug enable debugging output
1258 --debug enable debugging output
1259 --debugger start debugger
1259 --debugger start debugger
1260 --encoding ENCODE set the charset encoding (default: ascii)
1260 --encoding ENCODE set the charset encoding (default: ascii)
1261 --encodingmode MODE set the charset encoding mode (default: strict)
1261 --encodingmode MODE set the charset encoding mode (default: strict)
1262 --traceback always print a traceback on exception
1262 --traceback always print a traceback on exception
1263 --time time how long the command takes
1263 --time time how long the command takes
1264 --profile print command execution profile
1264 --profile print command execution profile
1265 --version output version information and exit
1265 --version output version information and exit
1266 -h --help display help and exit
1266 -h --help display help and exit
1267 --hidden consider hidden changesets
1267 --hidden consider hidden changesets
1268 --pager TYPE when to paginate (boolean, always, auto, or never)
1268 --pager TYPE when to paginate (boolean, always, auto, or never)
1269 (default: auto)
1269 (default: auto)
1270
1270
1271 Disabled extension commands:
1271 Disabled extension commands:
1272
1272
1273 $ ORGHGRCPATH=$HGRCPATH
1273 $ ORGHGRCPATH=$HGRCPATH
1274 $ HGRCPATH=
1274 $ HGRCPATH=
1275 $ export HGRCPATH
1275 $ export HGRCPATH
1276 $ hg help email
1276 $ hg help email
1277 'email' is provided by the following extension:
1277 'email' is provided by the following extension:
1278
1278
1279 patchbomb command to send changesets as (a series of) patch emails
1279 patchbomb command to send changesets as (a series of) patch emails
1280
1280
1281 (use 'hg help extensions' for information on enabling extensions)
1281 (use 'hg help extensions' for information on enabling extensions)
1282
1282
1283
1283
1284 $ hg qdel
1284 $ hg qdel
1285 hg: unknown command 'qdel'
1285 hg: unknown command 'qdel'
1286 'qdelete' is provided by the following extension:
1286 'qdelete' is provided by the following extension:
1287
1287
1288 mq manage a stack of patches
1288 mq manage a stack of patches
1289
1289
1290 (use 'hg help extensions' for information on enabling extensions)
1290 (use 'hg help extensions' for information on enabling extensions)
1291 [255]
1291 [255]
1292
1292
1293
1293
1294 $ hg churn
1294 $ hg churn
1295 hg: unknown command 'churn'
1295 hg: unknown command 'churn'
1296 'churn' is provided by the following extension:
1296 'churn' is provided by the following extension:
1297
1297
1298 churn command to display statistics about repository history
1298 churn command to display statistics about repository history
1299
1299
1300 (use 'hg help extensions' for information on enabling extensions)
1300 (use 'hg help extensions' for information on enabling extensions)
1301 [255]
1301 [255]
1302
1302
1303
1303
1304
1304
1305 Disabled extensions:
1305 Disabled extensions:
1306
1306
1307 $ hg help churn
1307 $ hg help churn
1308 churn extension - command to display statistics about repository history
1308 churn extension - command to display statistics about repository history
1309
1309
1310 (use 'hg help extensions' for information on enabling extensions)
1310 (use 'hg help extensions' for information on enabling extensions)
1311
1311
1312 $ hg help patchbomb
1312 $ hg help patchbomb
1313 patchbomb extension - command to send changesets as (a series of) patch emails
1313 patchbomb extension - command to send changesets as (a series of) patch emails
1314
1314
1315 The series is started off with a "[PATCH 0 of N]" introduction, which
1315 The series is started off with a "[PATCH 0 of N]" introduction, which
1316 describes the series as a whole.
1316 describes the series as a whole.
1317
1317
1318 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1318 Each patch email has a Subject line of "[PATCH M of N] ...", using the first
1319 line of the changeset description as the subject text. The message contains
1319 line of the changeset description as the subject text. The message contains
1320 two or three body parts:
1320 two or three body parts:
1321
1321
1322 - The changeset description.
1322 - The changeset description.
1323 - [Optional] The result of running diffstat on the patch.
1323 - [Optional] The result of running diffstat on the patch.
1324 - The patch itself, as generated by 'hg export'.
1324 - The patch itself, as generated by 'hg export'.
1325
1325
1326 Each message refers to the first in the series using the In-Reply-To and
1326 Each message refers to the first in the series using the In-Reply-To and
1327 References headers, so they will show up as a sequence in threaded mail and
1327 References headers, so they will show up as a sequence in threaded mail and
1328 news readers, and in mail archives.
1328 news readers, and in mail archives.
1329
1329
1330 To configure other defaults, add a section like this to your configuration
1330 To configure other defaults, add a section like this to your configuration
1331 file:
1331 file:
1332
1332
1333 [email]
1333 [email]
1334 from = My Name <my@email>
1334 from = My Name <my@email>
1335 to = recipient1, recipient2, ...
1335 to = recipient1, recipient2, ...
1336 cc = cc1, cc2, ...
1336 cc = cc1, cc2, ...
1337 bcc = bcc1, bcc2, ...
1337 bcc = bcc1, bcc2, ...
1338 reply-to = address1, address2, ...
1338 reply-to = address1, address2, ...
1339
1339
1340 Use "[patchbomb]" as configuration section name if you need to override global
1340 Use "[patchbomb]" as configuration section name if you need to override global
1341 "[email]" address settings.
1341 "[email]" address settings.
1342
1342
1343 Then you can use the 'hg email' command to mail a series of changesets as a
1343 Then you can use the 'hg email' command to mail a series of changesets as a
1344 patchbomb.
1344 patchbomb.
1345
1345
1346 You can also either configure the method option in the email section to be a
1346 You can also either configure the method option in the email section to be a
1347 sendmail compatible mailer or fill out the [smtp] section so that the
1347 sendmail compatible mailer or fill out the [smtp] section so that the
1348 patchbomb extension can automatically send patchbombs directly from the
1348 patchbomb extension can automatically send patchbombs directly from the
1349 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1349 commandline. See the [email] and [smtp] sections in hgrc(5) for details.
1350
1350
1351 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1351 By default, 'hg email' will prompt for a "To" or "CC" header if you do not
1352 supply one via configuration or the command line. You can override this to
1352 supply one via configuration or the command line. You can override this to
1353 never prompt by configuring an empty value:
1353 never prompt by configuring an empty value:
1354
1354
1355 [email]
1355 [email]
1356 cc =
1356 cc =
1357
1357
1358 You can control the default inclusion of an introduction message with the
1358 You can control the default inclusion of an introduction message with the
1359 "patchbomb.intro" configuration option. The configuration is always
1359 "patchbomb.intro" configuration option. The configuration is always
1360 overwritten by command line flags like --intro and --desc:
1360 overwritten by command line flags like --intro and --desc:
1361
1361
1362 [patchbomb]
1362 [patchbomb]
1363 intro=auto # include introduction message if more than 1 patch (default)
1363 intro=auto # include introduction message if more than 1 patch (default)
1364 intro=never # never include an introduction message
1364 intro=never # never include an introduction message
1365 intro=always # always include an introduction message
1365 intro=always # always include an introduction message
1366
1366
1367 You can specify a template for flags to be added in subject prefixes. Flags
1367 You can specify a template for flags to be added in subject prefixes. Flags
1368 specified by --flag option are exported as "{flags}" keyword:
1368 specified by --flag option are exported as "{flags}" keyword:
1369
1369
1370 [patchbomb]
1370 [patchbomb]
1371 flagtemplate = "{separate(' ',
1371 flagtemplate = "{separate(' ',
1372 ifeq(branch, 'default', '', branch|upper),
1372 ifeq(branch, 'default', '', branch|upper),
1373 flags)}"
1373 flags)}"
1374
1374
1375 You can set patchbomb to always ask for confirmation by setting
1375 You can set patchbomb to always ask for confirmation by setting
1376 "patchbomb.confirm" to true.
1376 "patchbomb.confirm" to true.
1377
1377
1378 (use 'hg help extensions' for information on enabling extensions)
1378 (use 'hg help extensions' for information on enabling extensions)
1379
1379
1380
1380
1381 Help can find unimported extensions
1381 Help can find unimported extensions
1382 -----------------------------------
1382 -----------------------------------
1383
1383
1384 XXX-PYOXIDIZER since the frozen binary does not have source directory tree,
1384 XXX-PYOXIDIZER since the frozen binary does not have source directory tree,
1385 this make the checking for actual file under `hgext` a bit complicated. In
1385 this make the checking for actual file under `hgext` a bit complicated. In
1386 addition these tests do some strange dance to ensure some other module are the
1386 addition these tests do some strange dance to ensure some other module are the
1387 first in `sys.path` (since the current install path is always in front
1387 first in `sys.path` (since the current install path is always in front
1388 otherwise) that are fragile and that does not match reality in the field. So
1388 otherwise) that are fragile and that does not match reality in the field. So
1389 for now we disable this test untill a deeper rework of that logic is done.
1389 for now we disable this test untill a deeper rework of that logic is done.
1390
1390
1391 #if no-pyoxidizer
1391 #if no-pyoxidizer
1392
1392
1393 Broken disabled extension and command:
1393 Broken disabled extension and command:
1394
1394
1395 $ mkdir hgext
1395 $ mkdir hgext
1396 $ echo > hgext/__init__.py
1396 $ echo > hgext/__init__.py
1397 $ cat > hgext/broken.py <<NO_CHECK_EOF
1397 $ cat > hgext/broken.py <<NO_CHECK_EOF
1398 > "broken extension'
1398 > "broken extension'
1399 > NO_CHECK_EOF
1399 > NO_CHECK_EOF
1400 $ cat > path.py <<EOF
1400 $ cat > path.py <<EOF
1401 > import os
1401 > import os
1402 > import sys
1402 > import sys
1403 > sys.path.insert(0, os.environ['HGEXTPATH'])
1403 > sys.path.insert(0, os.environ['HGEXTPATH'])
1404 > EOF
1404 > EOF
1405 $ HGEXTPATH=`pwd`
1405 $ HGEXTPATH=`pwd`
1406 $ export HGEXTPATH
1406 $ export HGEXTPATH
1407
1407
1408 $ hg --config extensions.path=./path.py help broken
1408 $ hg --config extensions.path=./path.py help broken
1409 broken extension - (no help text available)
1409 broken extension - (no help text available)
1410
1410
1411 (use 'hg help extensions' for information on enabling extensions)
1411 (use 'hg help extensions' for information on enabling extensions)
1412
1412
1413
1413
1414 $ cat > hgext/forest.py <<EOF
1414 $ cat > hgext/forest.py <<EOF
1415 > cmdtable = None
1415 > cmdtable = None
1416 > @command()
1416 > @command()
1417 > def f():
1417 > def f():
1418 > pass
1418 > pass
1419 > @command(123)
1419 > @command(123)
1420 > def g():
1420 > def g():
1421 > pass
1421 > pass
1422 > EOF
1422 > EOF
1423 $ hg --config extensions.path=./path.py help foo
1423 $ hg --config extensions.path=./path.py help foo
1424 abort: no such help topic: foo
1424 abort: no such help topic: foo
1425 (try 'hg help --keyword foo')
1425 (try 'hg help --keyword foo')
1426 [255]
1426 [255]
1427
1427
1428 #endif
1428 #endif
1429
1429
1430 ---
1430 ---
1431
1431
1432 $ cat > throw.py <<EOF
1432 $ cat > throw.py <<EOF
1433 > from mercurial import commands, registrar, util
1433 > from mercurial import commands, registrar, util
1434 > cmdtable = {}
1434 > cmdtable = {}
1435 > command = registrar.command(cmdtable)
1435 > command = registrar.command(cmdtable)
1436 > class Bogon(Exception): pass
1436 > class Bogon(Exception): pass
1437 > # NB: version should be bytes; simulating extension not ported to py3
1437 > # NB: version should be bytes; simulating extension not ported to py3
1438 > __version__ = '1.0.0'
1438 > __version__ = '1.0.0'
1439 > @command(b'throw', [], b'hg throw', norepo=True)
1439 > @command(b'throw', [], b'hg throw', norepo=True)
1440 > def throw(ui, **opts):
1440 > def throw(ui, **opts):
1441 > """throws an exception"""
1441 > """throws an exception"""
1442 > raise Bogon()
1442 > raise Bogon()
1443 > EOF
1443 > EOF
1444
1444
1445 Test extension without proper byteification of key attributes doesn't crash when
1445 Test extension without proper byteification of key attributes doesn't crash when
1446 accessed.
1446 accessed.
1447
1447
1448 $ hg version -v --config extensions.throw=throw.py | grep '^ '
1448 $ hg version -v --config extensions.throw=throw.py | grep '^ '
1449 throw external 1.0.0
1449 throw external 1.0.0
1450
1450
1451 No declared supported version, extension complains:
1451 No declared supported version, extension complains:
1452 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1452 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1453 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1453 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1454 ** which supports versions unknown of Mercurial.
1454 ** which supports versions unknown of Mercurial.
1455 ** Please disable "throw" and try your action again.
1455 ** Please disable "throw" and try your action again.
1456 ** If that fixes the bug please report it to the extension author.
1456 ** If that fixes the bug please report it to the extension author.
1457 ** Python * (glob)
1457 ** Python * (glob)
1458 ** Mercurial Distributed SCM * (glob)
1458 ** Mercurial Distributed SCM * (glob)
1459 ** Extensions loaded: throw 1.0.0
1459 ** Extensions loaded: throw 1.0.0
1460
1460
1461 empty declaration of supported version, extension complains (but doesn't choke if
1461 empty declaration of supported version, extension complains (but doesn't choke if
1462 the value is improperly a str instead of bytes):
1462 the value is improperly a str instead of bytes):
1463 $ echo "testedwith = ''" >> throw.py
1463 $ echo "testedwith = ''" >> throw.py
1464 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1464 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1465 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1465 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1466 ** which supports versions unknown of Mercurial.
1466 ** which supports versions unknown of Mercurial.
1467 ** Please disable "throw" and try your action again.
1467 ** Please disable "throw" and try your action again.
1468 ** If that fixes the bug please report it to the extension author.
1468 ** If that fixes the bug please report it to the extension author.
1469 ** Python * (glob)
1469 ** Python * (glob)
1470 ** Mercurial Distributed SCM (*) (glob)
1470 ** Mercurial Distributed SCM (*) (glob)
1471 ** Extensions loaded: throw 1.0.0
1471 ** Extensions loaded: throw 1.0.0
1472
1472
1473 If the extension specifies a buglink, show that (but don't choke if the value is
1473 If the extension specifies a buglink, show that (but don't choke if the value is
1474 improperly a str instead of bytes):
1474 improperly a str instead of bytes):
1475 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1475 $ echo 'buglink = "http://example.com/bts"' >> throw.py
1476 $ rm -f throw.pyc throw.pyo
1476 $ rm -f throw.pyc throw.pyo
1477 $ rm -Rf __pycache__
1477 $ rm -Rf __pycache__
1478 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1478 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1479 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1479 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1480 ** which supports versions unknown of Mercurial.
1480 ** which supports versions unknown of Mercurial.
1481 ** Please disable "throw" and try your action again.
1481 ** Please disable "throw" and try your action again.
1482 ** If that fixes the bug please report it to http://example.com/bts
1482 ** If that fixes the bug please report it to http://example.com/bts
1483 ** Python * (glob)
1483 ** Python * (glob)
1484 ** Mercurial Distributed SCM (*) (glob)
1484 ** Mercurial Distributed SCM (*) (glob)
1485 ** Extensions loaded: throw 1.0.0
1485 ** Extensions loaded: throw 1.0.0
1486
1486
1487 If the extensions declare outdated versions, accuse the older extension first:
1487 If the extensions declare outdated versions, accuse the older extension first:
1488 $ echo "from mercurial import util" >> older.py
1488 $ echo "from mercurial import util" >> older.py
1489 $ echo "util.version = lambda:b'2.2'" >> older.py
1489 $ echo "util.version = lambda:b'2.2'" >> older.py
1490 $ echo "testedwith = b'1.9.3'" >> older.py
1490 $ echo "testedwith = b'1.9.3'" >> older.py
1491 $ echo "testedwith = b'2.1.1'" >> throw.py
1491 $ echo "testedwith = b'2.1.1'" >> throw.py
1492 $ rm -f throw.pyc throw.pyo
1492 $ rm -f throw.pyc throw.pyo
1493 $ rm -Rf __pycache__
1493 $ rm -Rf __pycache__
1494 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1494 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1495 > throw 2>&1 | egrep '^\*\*'
1495 > throw 2>&1 | egrep '^\*\*'
1496 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1496 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1497 ** which supports versions 1.9 of Mercurial.
1497 ** which supports versions 1.9 of Mercurial.
1498 ** Please disable "older" and try your action again.
1498 ** Please disable "older" and try your action again.
1499 ** If that fixes the bug please report it to the extension author.
1499 ** If that fixes the bug please report it to the extension author.
1500 ** Python * (glob)
1500 ** Python * (glob)
1501 ** Mercurial Distributed SCM (version 2.2)
1501 ** Mercurial Distributed SCM (version 2.2)
1502 ** Extensions loaded: older, throw 1.0.0
1502 ** Extensions loaded: older, throw 1.0.0
1503
1503
1504 One extension only tested with older, one only with newer versions:
1504 One extension only tested with older, one only with newer versions:
1505 $ echo "util.version = lambda:b'2.1'" >> older.py
1505 $ echo "util.version = lambda:b'2.1'" >> older.py
1506 $ rm -f older.pyc older.pyo
1506 $ rm -f older.pyc older.pyo
1507 $ rm -Rf __pycache__
1507 $ rm -Rf __pycache__
1508 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1508 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1509 > throw 2>&1 | egrep '^\*\*'
1509 > throw 2>&1 | egrep '^\*\*'
1510 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1510 ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
1511 ** which supports versions 1.9 of Mercurial.
1511 ** which supports versions 1.9 of Mercurial.
1512 ** Please disable "older" and try your action again.
1512 ** Please disable "older" and try your action again.
1513 ** If that fixes the bug please report it to the extension author.
1513 ** If that fixes the bug please report it to the extension author.
1514 ** Python * (glob)
1514 ** Python * (glob)
1515 ** Mercurial Distributed SCM (version 2.1)
1515 ** Mercurial Distributed SCM (version 2.1)
1516 ** Extensions loaded: older, throw 1.0.0
1516 ** Extensions loaded: older, throw 1.0.0
1517
1517
1518 Older extension is tested with current version, the other only with newer:
1518 Older extension is tested with current version, the other only with newer:
1519 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1519 $ echo "util.version = lambda:b'1.9.3'" >> older.py
1520 $ rm -f older.pyc older.pyo
1520 $ rm -f older.pyc older.pyo
1521 $ rm -Rf __pycache__
1521 $ rm -Rf __pycache__
1522 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1522 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1523 > throw 2>&1 | egrep '^\*\*'
1523 > throw 2>&1 | egrep '^\*\*'
1524 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1524 ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
1525 ** which supports versions 2.1 of Mercurial.
1525 ** which supports versions 2.1 of Mercurial.
1526 ** Please disable "throw" and try your action again.
1526 ** Please disable "throw" and try your action again.
1527 ** If that fixes the bug please report it to http://example.com/bts
1527 ** If that fixes the bug please report it to http://example.com/bts
1528 ** Python * (glob)
1528 ** Python * (glob)
1529 ** Mercurial Distributed SCM (version 1.9.3)
1529 ** Mercurial Distributed SCM (version 1.9.3)
1530 ** Extensions loaded: older, throw 1.0.0
1530 ** Extensions loaded: older, throw 1.0.0
1531
1531
1532 Ability to point to a different point
1532 Ability to point to a different point
1533 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1533 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
1534 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1534 > --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
1535 ** unknown exception encountered, please report by visiting
1535 ** unknown exception encountered, please report by visiting
1536 ** Your Local Goat Lenders
1536 ** Your Local Goat Lenders
1537 ** Python * (glob)
1537 ** Python * (glob)
1538 ** Mercurial Distributed SCM (*) (glob)
1538 ** Mercurial Distributed SCM (*) (glob)
1539 ** Extensions loaded: older, throw 1.0.0
1539 ** Extensions loaded: older, throw 1.0.0
1540
1540
1541 Declare the version as supporting this hg version, show regular bts link:
1541 Declare the version as supporting this hg version, show regular bts link:
1542 $ hgver=`hg debuginstall -T '{hgver}'`
1542 $ hgver=`hg debuginstall -T '{hgver}'`
1543 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1543 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
1544 $ if [ -z "$hgver" ]; then
1544 $ if [ -z "$hgver" ]; then
1545 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1545 > echo "unable to fetch a mercurial version. Make sure __version__ is correct";
1546 > fi
1546 > fi
1547 $ rm -f throw.pyc throw.pyo
1547 $ rm -f throw.pyc throw.pyo
1548 $ rm -Rf __pycache__
1548 $ rm -Rf __pycache__
1549 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1549 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1550 ** unknown exception encountered, please report by visiting
1550 ** unknown exception encountered, please report by visiting
1551 ** https://mercurial-scm.org/wiki/BugTracker
1551 ** https://mercurial-scm.org/wiki/BugTracker
1552 ** Python * (glob)
1552 ** Python * (glob)
1553 ** Mercurial Distributed SCM (*) (glob)
1553 ** Mercurial Distributed SCM (*) (glob)
1554 ** Extensions loaded: throw 1.0.0
1554 ** Extensions loaded: throw 1.0.0
1555
1555
1556 Patch version is ignored during compatibility check
1556 Patch version is ignored during compatibility check
1557 $ echo "testedwith = b'3.2'" >> throw.py
1557 $ echo "testedwith = b'3.2'" >> throw.py
1558 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1558 $ echo "util.version = lambda:b'3.2.2'" >> throw.py
1559 $ rm -f throw.pyc throw.pyo
1559 $ rm -f throw.pyc throw.pyo
1560 $ rm -Rf __pycache__
1560 $ rm -Rf __pycache__
1561 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1561 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
1562 ** unknown exception encountered, please report by visiting
1562 ** unknown exception encountered, please report by visiting
1563 ** https://mercurial-scm.org/wiki/BugTracker
1563 ** https://mercurial-scm.org/wiki/BugTracker
1564 ** Python * (glob)
1564 ** Python * (glob)
1565 ** Mercurial Distributed SCM (*) (glob)
1565 ** Mercurial Distributed SCM (*) (glob)
1566 ** Extensions loaded: throw 1.0.0
1566 ** Extensions loaded: throw 1.0.0
1567
1567
1568 Test version number support in 'hg version':
1568 Test version number support in 'hg version':
1569 $ echo '__version__ = (1, 2, 3)' >> throw.py
1569 $ echo '__version__ = (1, 2, 3)' >> throw.py
1570 $ rm -f throw.pyc throw.pyo
1570 $ rm -f throw.pyc throw.pyo
1571 $ rm -Rf __pycache__
1571 $ rm -Rf __pycache__
1572 $ hg version -v
1572 $ hg version -v
1573 Mercurial Distributed SCM (version *) (glob)
1573 Mercurial Distributed SCM (version *) (glob)
1574 (see https://mercurial-scm.org for more information)
1574 (see https://mercurial-scm.org for more information)
1575
1575
1576 Copyright (C) 2005-* Olivia Mackall and others (glob)
1576 Copyright (C) 2005-* Olivia Mackall and others (glob)
1577 This is free software; see the source for copying conditions. There is NO
1577 This is free software; see the source for copying conditions. There is NO
1578 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1578 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1579
1579
1580 Enabled extensions:
1580 Enabled extensions:
1581
1581
1582
1582
1583 $ hg version -v --config extensions.throw=throw.py
1583 $ hg version -v --config extensions.throw=throw.py
1584 Mercurial Distributed SCM (version *) (glob)
1584 Mercurial Distributed SCM (version *) (glob)
1585 (see https://mercurial-scm.org for more information)
1585 (see https://mercurial-scm.org for more information)
1586
1586
1587 Copyright (C) 2005-* Olivia Mackall and others (glob)
1587 Copyright (C) 2005-* Olivia Mackall and others (glob)
1588 This is free software; see the source for copying conditions. There is NO
1588 This is free software; see the source for copying conditions. There is NO
1589 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1589 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1590
1590
1591 Enabled extensions:
1591 Enabled extensions:
1592
1592
1593 throw external 1.2.3
1593 throw external 1.2.3
1594 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1594 $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
1595 $ rm -f throw.pyc throw.pyo
1595 $ rm -f throw.pyc throw.pyo
1596 $ rm -Rf __pycache__
1596 $ rm -Rf __pycache__
1597 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1597 $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
1598 Mercurial Distributed SCM (version *) (glob)
1598 Mercurial Distributed SCM (version *) (glob)
1599 (see https://mercurial-scm.org for more information)
1599 (see https://mercurial-scm.org for more information)
1600
1600
1601 Copyright (C) 2005-* Olivia Mackall and others (glob)
1601 Copyright (C) 2005-* Olivia Mackall and others (glob)
1602 This is free software; see the source for copying conditions. There is NO
1602 This is free software; see the source for copying conditions. There is NO
1603 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1603 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1604
1604
1605 Enabled extensions:
1605 Enabled extensions:
1606
1606
1607 strip internal
1607 strip internal
1608 throw external 1.twentythree
1608 throw external 1.twentythree
1609
1609
1610 $ hg version -q --config extensions.throw=throw.py
1610 $ hg version -q --config extensions.throw=throw.py
1611 Mercurial Distributed SCM (version *) (glob)
1611 Mercurial Distributed SCM (version *) (glob)
1612
1612
1613 Test template output:
1613 Test template output:
1614
1614
1615 $ hg version --config extensions.strip= -T'{extensions}'
1615 $ hg version --config extensions.strip= -T'{extensions}'
1616 strip
1616 strip
1617
1617
1618 Test JSON output of version:
1618 Test JSON output of version:
1619
1619
1620 $ hg version -Tjson
1620 $ hg version -Tjson
1621 [
1621 [
1622 {
1622 {
1623 "extensions": [],
1623 "extensions": [],
1624 "ver": "*" (glob)
1624 "ver": "*" (glob)
1625 }
1625 }
1626 ]
1626 ]
1627
1627
1628 $ hg version --config extensions.throw=throw.py -Tjson
1628 $ hg version --config extensions.throw=throw.py -Tjson
1629 [
1629 [
1630 {
1630 {
1631 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1631 "extensions": [{"bundled": false, "name": "throw", "ver": "1.twentythree"}],
1632 "ver": "3.2.2"
1632 "ver": "3.2.2"
1633 }
1633 }
1634 ]
1634 ]
1635
1635
1636 $ hg version --config extensions.strip= -Tjson
1636 $ hg version --config extensions.strip= -Tjson
1637 [
1637 [
1638 {
1638 {
1639 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1639 "extensions": [{"bundled": true, "name": "strip", "ver": null}],
1640 "ver": "*" (glob)
1640 "ver": "*" (glob)
1641 }
1641 }
1642 ]
1642 ]
1643
1643
1644 Test template output of version:
1644 Test template output of version:
1645
1645
1646 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1646 $ hg version --config extensions.throw=throw.py --config extensions.strip= \
1647 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1647 > -T'{extensions % "{name} {pad(ver, 16)} ({if(bundled, "internal", "external")})\n"}'
1648 strip (internal)
1648 strip (internal)
1649 throw 1.twentythree (external)
1649 throw 1.twentythree (external)
1650
1650
1651 Refuse to load extensions with minimum version requirements
1651 Refuse to load extensions with minimum version requirements
1652
1652
1653 $ cat > minversion1.py << EOF
1653 $ cat > minversion1.py << EOF
1654 > from mercurial import util
1654 > from mercurial import util
1655 > util.version = lambda: b'3.5.2'
1655 > util.version = lambda: b'3.5.2'
1656 > minimumhgversion = b'3.6'
1656 > minimumhgversion = b'3.6'
1657 > EOF
1657 > EOF
1658 $ hg --config extensions.minversion=minversion1.py version
1658 $ hg --config extensions.minversion=minversion1.py version
1659 (third party extension minversion requires version 3.6 or newer of Mercurial (current: 3.5.2); disabling)
1659 (third party extension minversion requires version 3.6 or newer of Mercurial (current: 3.5.2); disabling)
1660 Mercurial Distributed SCM (version 3.5.2)
1660 Mercurial Distributed SCM (version 3.5.2)
1661 (see https://mercurial-scm.org for more information)
1661 (see https://mercurial-scm.org for more information)
1662
1662
1663 Copyright (C) 2005-* Olivia Mackall and others (glob)
1663 Copyright (C) 2005-* Olivia Mackall and others (glob)
1664 This is free software; see the source for copying conditions. There is NO
1664 This is free software; see the source for copying conditions. There is NO
1665 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1665 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1666
1666
1667 $ cat > minversion2.py << EOF
1667 $ cat > minversion2.py << EOF
1668 > from mercurial import util
1668 > from mercurial import util
1669 > util.version = lambda: b'3.6'
1669 > util.version = lambda: b'3.6'
1670 > minimumhgversion = b'3.7'
1670 > minimumhgversion = b'3.7'
1671 > EOF
1671 > EOF
1672 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1672 $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
1673 (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
1673 (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
1674
1674
1675 Can load version that is only off by point release
1675 Can load version that is only off by point release
1676
1676
1677 $ cat > minversion2.py << EOF
1677 $ cat > minversion2.py << EOF
1678 > from mercurial import util
1678 > from mercurial import util
1679 > util.version = lambda: b'3.6.1'
1679 > util.version = lambda: b'3.6.1'
1680 > minimumhgversion = b'3.6'
1680 > minimumhgversion = b'3.6'
1681 > EOF
1681 > EOF
1682 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1682 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1683 [1]
1683 [1]
1684
1684
1685 Can load minimum version identical to current
1685 Can load minimum version identical to current
1686
1686
1687 $ cat > minversion3.py << EOF
1687 $ cat > minversion3.py << EOF
1688 > from mercurial import util
1688 > from mercurial import util
1689 > util.version = lambda: b'3.5'
1689 > util.version = lambda: b'3.5'
1690 > minimumhgversion = b'3.5'
1690 > minimumhgversion = b'3.5'
1691 > EOF
1691 > EOF
1692 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1692 $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
1693 [1]
1693 [1]
1694
1694
1695 Don't explode on py3 with a bad version number
1696
1697 $ cat > minversion4.py << EOF
1698 > from mercurial import util
1699 > util.version = lambda: b'3.5'
1700 > minimumhgversion = b'3'
1701 > EOF
1702 $ hg --config extensions.minversion=minversion4.py version -v
1703 Mercurial Distributed SCM (version 3.5)
1704 (see https://mercurial-scm.org for more information)
1705
1706 Copyright (C) 2005-* Olivia Mackall and others (glob)
1707 This is free software; see the source for copying conditions. There is NO
1708 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1709
1710 Enabled extensions:
1711
1712 minversion external
1713
1695 Restore HGRCPATH
1714 Restore HGRCPATH
1696
1715
1697 $ HGRCPATH=$ORGHGRCPATH
1716 $ HGRCPATH=$ORGHGRCPATH
1698 $ export HGRCPATH
1717 $ export HGRCPATH
1699
1718
1700 Commands handling multiple repositories at a time should invoke only
1719 Commands handling multiple repositories at a time should invoke only
1701 "reposetup()" of extensions enabling in the target repository.
1720 "reposetup()" of extensions enabling in the target repository.
1702
1721
1703 $ mkdir reposetup-test
1722 $ mkdir reposetup-test
1704 $ cd reposetup-test
1723 $ cd reposetup-test
1705
1724
1706 $ cat > $TESTTMP/reposetuptest.py <<EOF
1725 $ cat > $TESTTMP/reposetuptest.py <<EOF
1707 > from mercurial import extensions
1726 > from mercurial import extensions
1708 > def reposetup(ui, repo):
1727 > def reposetup(ui, repo):
1709 > ui.write(b'reposetup() for %s\n' % (repo.root))
1728 > ui.write(b'reposetup() for %s\n' % (repo.root))
1710 > ui.flush()
1729 > ui.flush()
1711 > EOF
1730 > EOF
1712 $ hg init src
1731 $ hg init src
1713 $ echo a > src/a
1732 $ echo a > src/a
1714 $ hg -R src commit -Am '#0 at src/a'
1733 $ hg -R src commit -Am '#0 at src/a'
1715 adding a
1734 adding a
1716 $ echo '[extensions]' >> src/.hg/hgrc
1735 $ echo '[extensions]' >> src/.hg/hgrc
1717 $ echo '# enable extension locally' >> src/.hg/hgrc
1736 $ echo '# enable extension locally' >> src/.hg/hgrc
1718 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1737 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
1719 $ hg -R src status
1738 $ hg -R src status
1720 reposetup() for $TESTTMP/reposetup-test/src
1739 reposetup() for $TESTTMP/reposetup-test/src
1721 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1740 reposetup() for $TESTTMP/reposetup-test/src (chg !)
1722
1741
1723 #if no-extraextensions
1742 #if no-extraextensions
1724 $ hg --cwd src debugextensions
1743 $ hg --cwd src debugextensions
1725 reposetup() for $TESTTMP/reposetup-test/src
1744 reposetup() for $TESTTMP/reposetup-test/src
1726 dodo (untested!)
1745 dodo (untested!)
1727 dudu (untested!)
1746 dudu (untested!)
1728 mq
1747 mq
1729 reposetuptest (untested!)
1748 reposetuptest (untested!)
1730 strip
1749 strip
1731 #endif
1750 #endif
1732
1751
1733 $ hg clone -U src clone-dst1
1752 $ hg clone -U src clone-dst1
1734 reposetup() for $TESTTMP/reposetup-test/src
1753 reposetup() for $TESTTMP/reposetup-test/src
1735 $ hg init push-dst1
1754 $ hg init push-dst1
1736 $ hg -q -R src push push-dst1
1755 $ hg -q -R src push push-dst1
1737 reposetup() for $TESTTMP/reposetup-test/src
1756 reposetup() for $TESTTMP/reposetup-test/src
1738 $ hg init pull-src1
1757 $ hg init pull-src1
1739 $ hg -q -R pull-src1 pull src
1758 $ hg -q -R pull-src1 pull src
1740 reposetup() for $TESTTMP/reposetup-test/src
1759 reposetup() for $TESTTMP/reposetup-test/src
1741
1760
1742 $ cat <<EOF >> $HGRCPATH
1761 $ cat <<EOF >> $HGRCPATH
1743 > [extensions]
1762 > [extensions]
1744 > # disable extension globally and explicitly
1763 > # disable extension globally and explicitly
1745 > reposetuptest = !
1764 > reposetuptest = !
1746 > EOF
1765 > EOF
1747 $ hg clone -U src clone-dst2
1766 $ hg clone -U src clone-dst2
1748 reposetup() for $TESTTMP/reposetup-test/src
1767 reposetup() for $TESTTMP/reposetup-test/src
1749 $ hg init push-dst2
1768 $ hg init push-dst2
1750 $ hg -q -R src push push-dst2
1769 $ hg -q -R src push push-dst2
1751 reposetup() for $TESTTMP/reposetup-test/src
1770 reposetup() for $TESTTMP/reposetup-test/src
1752 $ hg init pull-src2
1771 $ hg init pull-src2
1753 $ hg -q -R pull-src2 pull src
1772 $ hg -q -R pull-src2 pull src
1754 reposetup() for $TESTTMP/reposetup-test/src
1773 reposetup() for $TESTTMP/reposetup-test/src
1755
1774
1756 $ cat <<EOF >> $HGRCPATH
1775 $ cat <<EOF >> $HGRCPATH
1757 > [extensions]
1776 > [extensions]
1758 > # enable extension globally
1777 > # enable extension globally
1759 > reposetuptest = $TESTTMP/reposetuptest.py
1778 > reposetuptest = $TESTTMP/reposetuptest.py
1760 > EOF
1779 > EOF
1761 $ hg clone -U src clone-dst3
1780 $ hg clone -U src clone-dst3
1762 reposetup() for $TESTTMP/reposetup-test/src
1781 reposetup() for $TESTTMP/reposetup-test/src
1763 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1782 reposetup() for $TESTTMP/reposetup-test/clone-dst3
1764 $ hg init push-dst3
1783 $ hg init push-dst3
1765 reposetup() for $TESTTMP/reposetup-test/push-dst3
1784 reposetup() for $TESTTMP/reposetup-test/push-dst3
1766 $ hg -q -R src push push-dst3
1785 $ hg -q -R src push push-dst3
1767 reposetup() for $TESTTMP/reposetup-test/src
1786 reposetup() for $TESTTMP/reposetup-test/src
1768 reposetup() for $TESTTMP/reposetup-test/push-dst3
1787 reposetup() for $TESTTMP/reposetup-test/push-dst3
1769 $ hg init pull-src3
1788 $ hg init pull-src3
1770 reposetup() for $TESTTMP/reposetup-test/pull-src3
1789 reposetup() for $TESTTMP/reposetup-test/pull-src3
1771 $ hg -q -R pull-src3 pull src
1790 $ hg -q -R pull-src3 pull src
1772 reposetup() for $TESTTMP/reposetup-test/pull-src3
1791 reposetup() for $TESTTMP/reposetup-test/pull-src3
1773 reposetup() for $TESTTMP/reposetup-test/src
1792 reposetup() for $TESTTMP/reposetup-test/src
1774
1793
1775 $ echo '[extensions]' >> src/.hg/hgrc
1794 $ echo '[extensions]' >> src/.hg/hgrc
1776 $ echo '# disable extension locally' >> src/.hg/hgrc
1795 $ echo '# disable extension locally' >> src/.hg/hgrc
1777 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1796 $ echo 'reposetuptest = !' >> src/.hg/hgrc
1778 $ hg clone -U src clone-dst4
1797 $ hg clone -U src clone-dst4
1779 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1798 reposetup() for $TESTTMP/reposetup-test/clone-dst4
1780 $ hg init push-dst4
1799 $ hg init push-dst4
1781 reposetup() for $TESTTMP/reposetup-test/push-dst4
1800 reposetup() for $TESTTMP/reposetup-test/push-dst4
1782 $ hg -q -R src push push-dst4
1801 $ hg -q -R src push push-dst4
1783 reposetup() for $TESTTMP/reposetup-test/push-dst4
1802 reposetup() for $TESTTMP/reposetup-test/push-dst4
1784 $ hg init pull-src4
1803 $ hg init pull-src4
1785 reposetup() for $TESTTMP/reposetup-test/pull-src4
1804 reposetup() for $TESTTMP/reposetup-test/pull-src4
1786 $ hg -q -R pull-src4 pull src
1805 $ hg -q -R pull-src4 pull src
1787 reposetup() for $TESTTMP/reposetup-test/pull-src4
1806 reposetup() for $TESTTMP/reposetup-test/pull-src4
1788
1807
1789 disabling in command line overlays with all configuration
1808 disabling in command line overlays with all configuration
1790 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1809 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
1791 $ hg --config extensions.reposetuptest=! init push-dst5
1810 $ hg --config extensions.reposetuptest=! init push-dst5
1792 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1811 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
1793 $ hg --config extensions.reposetuptest=! init pull-src5
1812 $ hg --config extensions.reposetuptest=! init pull-src5
1794 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1813 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
1795
1814
1796 $ cat <<EOF >> $HGRCPATH
1815 $ cat <<EOF >> $HGRCPATH
1797 > [extensions]
1816 > [extensions]
1798 > # disable extension globally and explicitly
1817 > # disable extension globally and explicitly
1799 > reposetuptest = !
1818 > reposetuptest = !
1800 > EOF
1819 > EOF
1801 $ hg init parent
1820 $ hg init parent
1802 $ hg init parent/sub1
1821 $ hg init parent/sub1
1803 $ echo 1 > parent/sub1/1
1822 $ echo 1 > parent/sub1/1
1804 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1823 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
1805 adding 1
1824 adding 1
1806 $ hg init parent/sub2
1825 $ hg init parent/sub2
1807 $ hg init parent/sub2/sub21
1826 $ hg init parent/sub2/sub21
1808 $ echo 21 > parent/sub2/sub21/21
1827 $ echo 21 > parent/sub2/sub21/21
1809 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1828 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
1810 adding 21
1829 adding 21
1811 $ cat > parent/sub2/.hgsub <<EOF
1830 $ cat > parent/sub2/.hgsub <<EOF
1812 > sub21 = sub21
1831 > sub21 = sub21
1813 > EOF
1832 > EOF
1814 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1833 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
1815 adding .hgsub
1834 adding .hgsub
1816 $ hg init parent/sub3
1835 $ hg init parent/sub3
1817 $ echo 3 > parent/sub3/3
1836 $ echo 3 > parent/sub3/3
1818 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1837 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
1819 adding 3
1838 adding 3
1820 $ cat > parent/.hgsub <<EOF
1839 $ cat > parent/.hgsub <<EOF
1821 > sub1 = sub1
1840 > sub1 = sub1
1822 > sub2 = sub2
1841 > sub2 = sub2
1823 > sub3 = sub3
1842 > sub3 = sub3
1824 > EOF
1843 > EOF
1825 $ hg -R parent commit -Am '#0 at parent'
1844 $ hg -R parent commit -Am '#0 at parent'
1826 adding .hgsub
1845 adding .hgsub
1827 $ echo '[extensions]' >> parent/.hg/hgrc
1846 $ echo '[extensions]' >> parent/.hg/hgrc
1828 $ echo '# enable extension locally' >> parent/.hg/hgrc
1847 $ echo '# enable extension locally' >> parent/.hg/hgrc
1829 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1848 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
1830 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1849 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
1831 $ hg -R parent status -S -A
1850 $ hg -R parent status -S -A
1832 reposetup() for $TESTTMP/reposetup-test/parent
1851 reposetup() for $TESTTMP/reposetup-test/parent
1833 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1852 reposetup() for $TESTTMP/reposetup-test/parent/sub2
1834 C .hgsub
1853 C .hgsub
1835 C .hgsubstate
1854 C .hgsubstate
1836 C sub1/1
1855 C sub1/1
1837 C sub2/.hgsub
1856 C sub2/.hgsub
1838 C sub2/.hgsubstate
1857 C sub2/.hgsubstate
1839 C sub2/sub21/21
1858 C sub2/sub21/21
1840 C sub3/3
1859 C sub3/3
1841
1860
1842 $ cd ..
1861 $ cd ..
1843
1862
1844 Prohibit registration of commands that don't use @command (issue5137)
1863 Prohibit registration of commands that don't use @command (issue5137)
1845
1864
1846 $ hg init deprecated
1865 $ hg init deprecated
1847 $ cd deprecated
1866 $ cd deprecated
1848
1867
1849 $ cat <<EOF > deprecatedcmd.py
1868 $ cat <<EOF > deprecatedcmd.py
1850 > def deprecatedcmd(repo, ui):
1869 > def deprecatedcmd(repo, ui):
1851 > pass
1870 > pass
1852 > cmdtable = {
1871 > cmdtable = {
1853 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1872 > b'deprecatedcmd': (deprecatedcmd, [], b''),
1854 > }
1873 > }
1855 > EOF
1874 > EOF
1856 $ cat <<EOF > .hg/hgrc
1875 $ cat <<EOF > .hg/hgrc
1857 > [extensions]
1876 > [extensions]
1858 > deprecatedcmd = `pwd`/deprecatedcmd.py
1877 > deprecatedcmd = `pwd`/deprecatedcmd.py
1859 > mq = !
1878 > mq = !
1860 > hgext.mq = !
1879 > hgext.mq = !
1861 > hgext/mq = !
1880 > hgext/mq = !
1862 > EOF
1881 > EOF
1863
1882
1864 $ hg deprecatedcmd > /dev/null
1883 $ hg deprecatedcmd > /dev/null
1865 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1884 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1866 *** (use @command decorator to register 'deprecatedcmd')
1885 *** (use @command decorator to register 'deprecatedcmd')
1867 hg: unknown command 'deprecatedcmd'
1886 hg: unknown command 'deprecatedcmd'
1868 (use 'hg help' for a list of commands)
1887 (use 'hg help' for a list of commands)
1869 [10]
1888 [10]
1870
1889
1871 the extension shouldn't be loaded at all so the mq works:
1890 the extension shouldn't be loaded at all so the mq works:
1872
1891
1873 $ hg qseries --config extensions.mq= > /dev/null
1892 $ hg qseries --config extensions.mq= > /dev/null
1874 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1893 *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
1875 *** (use @command decorator to register 'deprecatedcmd')
1894 *** (use @command decorator to register 'deprecatedcmd')
1876
1895
1877 $ cd ..
1896 $ cd ..
1878
1897
1879 Test synopsis and docstring extending
1898 Test synopsis and docstring extending
1880
1899
1881 $ hg init exthelp
1900 $ hg init exthelp
1882 $ cat > exthelp.py <<EOF
1901 $ cat > exthelp.py <<EOF
1883 > from mercurial import commands, extensions
1902 > from mercurial import commands, extensions
1884 > def exbookmarks(orig, *args, **opts):
1903 > def exbookmarks(orig, *args, **opts):
1885 > return orig(*args, **opts)
1904 > return orig(*args, **opts)
1886 > def uisetup(ui):
1905 > def uisetup(ui):
1887 > synopsis = b' GREPME [--foo] [-x]'
1906 > synopsis = b' GREPME [--foo] [-x]'
1888 > docstring = '''
1907 > docstring = '''
1889 > GREPME make sure that this is in the help!
1908 > GREPME make sure that this is in the help!
1890 > '''
1909 > '''
1891 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1910 > extensions.wrapcommand(commands.table, b'bookmarks', exbookmarks,
1892 > synopsis, docstring)
1911 > synopsis, docstring)
1893 > EOF
1912 > EOF
1894 $ abspath=`pwd`/exthelp.py
1913 $ abspath=`pwd`/exthelp.py
1895 $ echo '[extensions]' >> $HGRCPATH
1914 $ echo '[extensions]' >> $HGRCPATH
1896 $ echo "exthelp = $abspath" >> $HGRCPATH
1915 $ echo "exthelp = $abspath" >> $HGRCPATH
1897 $ cd exthelp
1916 $ cd exthelp
1898 $ hg help bookmarks | grep GREPME
1917 $ hg help bookmarks | grep GREPME
1899 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1918 hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
1900 GREPME make sure that this is in the help!
1919 GREPME make sure that this is in the help!
1901 $ cd ..
1920 $ cd ..
1902
1921
1903 Prohibit the use of unicode strings as the default value of options
1922 Prohibit the use of unicode strings as the default value of options
1904
1923
1905 $ hg init $TESTTMP/opt-unicode-default
1924 $ hg init $TESTTMP/opt-unicode-default
1906
1925
1907 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1926 $ cat > $TESTTMP/test_unicode_default_value.py << EOF
1908 > from __future__ import print_function
1927 > from __future__ import print_function
1909 > from mercurial import registrar
1928 > from mercurial import registrar
1910 > cmdtable = {}
1929 > cmdtable = {}
1911 > command = registrar.command(cmdtable)
1930 > command = registrar.command(cmdtable)
1912 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1931 > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
1913 > def ext(*args, **opts):
1932 > def ext(*args, **opts):
1914 > print(opts[b'opt'], flush=True)
1933 > print(opts[b'opt'], flush=True)
1915 > EOF
1934 > EOF
1916 $ "$PYTHON" $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1935 $ "$PYTHON" $TESTTMP/unflush.py $TESTTMP/test_unicode_default_value.py
1917 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1936 $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
1918 > [extensions]
1937 > [extensions]
1919 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1938 > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
1920 > EOF
1939 > EOF
1921 $ hg -R $TESTTMP/opt-unicode-default dummy
1940 $ hg -R $TESTTMP/opt-unicode-default dummy
1922 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1941 *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode *'value' found in cmdtable.dummy (glob)
1923 *** (use b'' to make it byte string)
1942 *** (use b'' to make it byte string)
1924 hg: unknown command 'dummy'
1943 hg: unknown command 'dummy'
1925 (did you mean summary?)
1944 (did you mean summary?)
1926 [10]
1945 [10]
General Comments 0
You need to be logged in to leave comments. Login now