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