##// END OF EJS Templates
demandimport: support "absolute_import" for external libraries (issue4029)...
FUJIWARA Katsunori -
r19932:e3a5922e default
parent child Browse files
Show More
@@ -1,155 +1,156 b''
1 1 # demandimport.py - global demand-loading of modules for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''
9 9 demandimport - automatic demandloading of modules
10 10
11 11 To enable this module, do:
12 12
13 13 import demandimport; demandimport.enable()
14 14
15 15 Imports of the following forms will be demand-loaded:
16 16
17 17 import a, b.c
18 18 import a.b as c
19 19 from a import b,c # a will be loaded immediately
20 20
21 21 These imports will not be delayed:
22 22
23 23 from a import *
24 24 b = __import__(a)
25 25 '''
26 26
27 27 import __builtin__
28 28 _origimport = __import__
29 29
30 30 nothing = object()
31 31
32 32 try:
33 33 _origimport(__builtin__.__name__, {}, {}, None, -1)
34 34 except TypeError: # no level argument
35 35 def _import(name, globals, locals, fromlist, level):
36 36 "call _origimport with no level argument"
37 37 return _origimport(name, globals, locals, fromlist)
38 38 else:
39 39 _import = _origimport
40 40
41 41 class _demandmod(object):
42 42 """module demand-loader and proxy"""
43 def __init__(self, name, globals, locals):
43 def __init__(self, name, globals, locals, level=-1):
44 44 if '.' in name:
45 45 head, rest = name.split('.', 1)
46 46 after = [rest]
47 47 else:
48 48 head = name
49 49 after = []
50 object.__setattr__(self, "_data", (head, globals, locals, after))
50 object.__setattr__(self, "_data",
51 (head, globals, locals, after, level))
51 52 object.__setattr__(self, "_module", None)
52 53 def _extend(self, name):
53 54 """add to the list of submodules to load"""
54 55 self._data[3].append(name)
55 56 def _load(self):
56 57 if not self._module:
57 head, globals, locals, after = self._data
58 mod = _origimport(head, globals, locals)
58 head, globals, locals, after, level = self._data
59 mod = _import(head, globals, locals, None, level)
59 60 # load submodules
60 61 def subload(mod, p):
61 62 h, t = p, None
62 63 if '.' in p:
63 64 h, t = p.split('.', 1)
64 65 if getattr(mod, h, nothing) is nothing:
65 66 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
66 67 elif t:
67 68 subload(getattr(mod, h), t)
68 69
69 70 for x in after:
70 71 subload(mod, x)
71 72
72 73 # are we in the locals dictionary still?
73 74 if locals and locals.get(head) == self:
74 75 locals[head] = mod
75 76 object.__setattr__(self, "_module", mod)
76 77
77 78 def __repr__(self):
78 79 if self._module:
79 80 return "<proxied module '%s'>" % self._data[0]
80 81 return "<unloaded module '%s'>" % self._data[0]
81 82 def __call__(self, *args, **kwargs):
82 83 raise TypeError("%s object is not callable" % repr(self))
83 84 def __getattribute__(self, attr):
84 85 if attr in ('_data', '_extend', '_load', '_module'):
85 86 return object.__getattribute__(self, attr)
86 87 self._load()
87 88 return getattr(self._module, attr)
88 89 def __setattr__(self, attr, val):
89 90 self._load()
90 91 setattr(self._module, attr, val)
91 92
92 93 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
93 94 if not locals or name in ignore or fromlist == ('*',):
94 95 # these cases we can't really delay
95 96 return _import(name, globals, locals, fromlist, level)
96 97 elif not fromlist:
97 98 # import a [as b]
98 99 if '.' in name: # a.b
99 100 base, rest = name.split('.', 1)
100 101 # email.__init__ loading email.mime
101 102 if globals and globals.get('__name__', None) == base:
102 103 return _import(name, globals, locals, fromlist, level)
103 104 # if a is already demand-loaded, add b to its submodule list
104 105 if base in locals:
105 106 if isinstance(locals[base], _demandmod):
106 107 locals[base]._extend(rest)
107 108 return locals[base]
108 return _demandmod(name, globals, locals)
109 return _demandmod(name, globals, locals, level)
109 110 else:
110 111 if level != -1:
111 112 # from . import b,c,d or from .a import b,c,d
112 113 return _origimport(name, globals, locals, fromlist, level)
113 114 # from a import b,c,d
114 115 mod = _origimport(name, globals, locals)
115 116 # recurse down the module chain
116 117 for comp in name.split('.')[1:]:
117 118 if getattr(mod, comp, nothing) is nothing:
118 119 setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__))
119 120 mod = getattr(mod, comp)
120 121 for x in fromlist:
121 122 # set requested submodules for demand load
122 123 if getattr(mod, x, nothing) is nothing:
123 124 setattr(mod, x, _demandmod(x, mod.__dict__, locals))
124 125 return mod
125 126
126 127 ignore = [
127 128 '_hashlib',
128 129 '_xmlplus',
129 130 'fcntl',
130 131 'win32com.gen_py',
131 132 '_winreg', # 2.7 mimetypes needs immediate ImportError
132 133 'pythoncom',
133 134 # imported by tarfile, not available under Windows
134 135 'pwd',
135 136 'grp',
136 137 # imported by profile, itself imported by hotshot.stats,
137 138 # not available under Windows
138 139 'resource',
139 140 # this trips up many extension authors
140 141 'gtk',
141 142 # setuptools' pkg_resources.py expects "from __main__ import x" to
142 143 # raise ImportError if x not defined
143 144 '__main__',
144 145 '_ssl', # conditional imports in the stdlib, issue1964
145 146 'rfc822',
146 147 'mimetools',
147 148 ]
148 149
149 150 def enable():
150 151 "enable global demand-loading of modules"
151 152 __builtin__.__import__ = _demandimport
152 153
153 154 def disable():
154 155 "disable global demand-loading of modules"
155 156 __builtin__.__import__ = _origimport
@@ -1,710 +1,749 b''
1 1 Test basic extension support
2 2
3 3 $ cat > foobar.py <<EOF
4 4 > import os
5 5 > from mercurial import commands
6 6 >
7 7 > def uisetup(ui):
8 8 > ui.write("uisetup called\\n")
9 9 >
10 10 > def reposetup(ui, repo):
11 11 > ui.write("reposetup called for %s\\n" % os.path.basename(repo.root))
12 12 > ui.write("ui %s= repo.ui\\n" % (ui == repo.ui and "=" or "!"))
13 13 >
14 14 > def foo(ui, *args, **kwargs):
15 15 > ui.write("Foo\\n")
16 16 >
17 17 > def bar(ui, *args, **kwargs):
18 18 > ui.write("Bar\\n")
19 19 >
20 20 > cmdtable = {
21 21 > "foo": (foo, [], "hg foo"),
22 22 > "bar": (bar, [], "hg bar"),
23 23 > }
24 24 >
25 25 > commands.norepo += ' bar'
26 26 > EOF
27 27 $ abspath=`pwd`/foobar.py
28 28
29 29 $ mkdir barfoo
30 30 $ cp foobar.py barfoo/__init__.py
31 31 $ barfoopath=`pwd`/barfoo
32 32
33 33 $ hg init a
34 34 $ cd a
35 35 $ echo foo > file
36 36 $ hg add file
37 37 $ hg commit -m 'add file'
38 38
39 39 $ echo '[extensions]' >> $HGRCPATH
40 40 $ echo "foobar = $abspath" >> $HGRCPATH
41 41 $ hg foo
42 42 uisetup called
43 43 reposetup called for a
44 44 ui == repo.ui
45 45 Foo
46 46
47 47 $ cd ..
48 48 $ hg clone a b
49 49 uisetup called
50 50 reposetup called for a
51 51 ui == repo.ui
52 52 reposetup called for b
53 53 ui == repo.ui
54 54 updating to branch default
55 55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56
57 57 $ hg bar
58 58 uisetup called
59 59 Bar
60 60 $ echo 'foobar = !' >> $HGRCPATH
61 61
62 62 module/__init__.py-style
63 63
64 64 $ echo "barfoo = $barfoopath" >> $HGRCPATH
65 65 $ cd a
66 66 $ hg foo
67 67 uisetup called
68 68 reposetup called for a
69 69 ui == repo.ui
70 70 Foo
71 71 $ echo 'barfoo = !' >> $HGRCPATH
72 72
73 73 Check that extensions are loaded in phases:
74 74
75 75 $ cat > foo.py <<EOF
76 76 > import os
77 77 > name = os.path.basename(__file__).rsplit('.', 1)[0]
78 78 > print "1) %s imported" % name
79 79 > def uisetup(ui):
80 80 > print "2) %s uisetup" % name
81 81 > def extsetup():
82 82 > print "3) %s extsetup" % name
83 83 > def reposetup(ui, repo):
84 84 > print "4) %s reposetup" % name
85 85 > EOF
86 86
87 87 $ cp foo.py bar.py
88 88 $ echo 'foo = foo.py' >> $HGRCPATH
89 89 $ echo 'bar = bar.py' >> $HGRCPATH
90 90
91 91 Command with no output, we just want to see the extensions loaded:
92 92
93 93 $ hg paths
94 94 1) foo imported
95 95 1) bar imported
96 96 2) foo uisetup
97 97 2) bar uisetup
98 98 3) foo extsetup
99 99 3) bar extsetup
100 100 4) foo reposetup
101 101 4) bar reposetup
102 102
103 103 Check hgweb's load order:
104 104
105 105 $ cat > hgweb.cgi <<EOF
106 106 > #!/usr/bin/env python
107 107 > from mercurial import demandimport; demandimport.enable()
108 108 > from mercurial.hgweb import hgweb
109 109 > from mercurial.hgweb import wsgicgi
110 110 >
111 111 > application = hgweb('.', 'test repo')
112 112 > wsgicgi.launch(application)
113 113 > EOF
114 114
115 115 $ REQUEST_METHOD='GET' PATH_INFO='/' SCRIPT_NAME='' QUERY_STRING='' \
116 116 > SERVER_PORT='80' SERVER_NAME='localhost' python hgweb.cgi \
117 117 > | grep '^[0-9]) ' # ignores HTML output
118 118 1) foo imported
119 119 1) bar imported
120 120 2) foo uisetup
121 121 2) bar uisetup
122 122 3) foo extsetup
123 123 3) bar extsetup
124 124 4) foo reposetup
125 125 4) bar reposetup
126 126 4) foo reposetup
127 127 4) bar reposetup
128 128
129 129 $ echo 'foo = !' >> $HGRCPATH
130 130 $ echo 'bar = !' >> $HGRCPATH
131 131
132 Check "from __future__ import absolute_import" support for external libraries
133
134 $ mkdir $TESTTMP/libroot
135 $ echo "s = 'libroot/ambig.py'" > $TESTTMP/libroot/ambig.py
136 $ mkdir $TESTTMP/libroot/mod
137 $ touch $TESTTMP/libroot/mod/__init__.py
138 $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
139
140 #if absimport
141 $ cat > $TESTTMP/libroot/mod/ambigabs.py <<EOF
142 > from __future__ import absolute_import
143 > import ambig # should load "libroot/ambig.py"
144 > s = ambig.s
145 > EOF
146 $ cat > loadabs.py <<EOF
147 > import mod.ambigabs as ambigabs
148 > def extsetup():
149 > print 'ambigabs.s=%s' % ambigabs.s
150 > EOF
151 $ (PYTHONPATH=$PYTHONPATH:$TESTTMP/libroot; hg --config extensions.loadabs=loadabs.py root)
152 ambigabs.s=libroot/ambig.py
153 $TESTTMP/a
154 #endif
155
156 #if no-py3k
157 $ cat > $TESTTMP/libroot/mod/ambigrel.py <<EOF
158 > import ambig # should load "libroot/mod/ambig.py"
159 > s = ambig.s
160 > EOF
161 $ cat > loadrel.py <<EOF
162 > import mod.ambigrel as ambigrel
163 > def extsetup():
164 > print 'ambigrel.s=%s' % ambigrel.s
165 > EOF
166 $ (PYTHONPATH=$PYTHONPATH:$TESTTMP/libroot; hg --config extensions.loadrel=loadrel.py root)
167 ambigrel.s=libroot/mod/ambig.py
168 $TESTTMP/a
169 #endif
170
132 171 $ cd ..
133 172
134 173 hide outer repo
135 174 $ hg init
136 175
137 176 $ cat > empty.py <<EOF
138 177 > '''empty cmdtable
139 178 > '''
140 179 > cmdtable = {}
141 180 > EOF
142 181 $ emptypath=`pwd`/empty.py
143 182 $ echo "empty = $emptypath" >> $HGRCPATH
144 183 $ hg help empty
145 184 empty extension - empty cmdtable
146 185
147 186 no commands defined
148 187
149 188 $ echo 'empty = !' >> $HGRCPATH
150 189
151 190 $ cat > debugextension.py <<EOF
152 191 > '''only debugcommands
153 192 > '''
154 193 > def debugfoobar(ui, repo, *args, **opts):
155 194 > "yet another debug command"
156 195 > pass
157 196 >
158 197 > def foo(ui, repo, *args, **opts):
159 198 > """yet another foo command
160 199 >
161 200 > This command has been DEPRECATED since forever.
162 201 > """
163 202 > pass
164 203 >
165 204 > cmdtable = {
166 205 > "debugfoobar": (debugfoobar, (), "hg debugfoobar"),
167 206 > "foo": (foo, (), "hg foo")
168 207 > }
169 208 > EOF
170 209 $ debugpath=`pwd`/debugextension.py
171 210 $ echo "debugextension = $debugpath" >> $HGRCPATH
172 211
173 212 $ hg help debugextension
174 213 debugextension extension - only debugcommands
175 214
176 215 no commands defined
177 216
178 217 $ hg --verbose help debugextension
179 218 debugextension extension - only debugcommands
180 219
181 220 list of commands:
182 221
183 222 foo yet another foo command
184 223
185 224 global options:
186 225
187 226 -R --repository REPO repository root directory or name of overlay bundle
188 227 file
189 228 --cwd DIR change working directory
190 229 -y --noninteractive do not prompt, automatically pick the first choice for
191 230 all prompts
192 231 -q --quiet suppress output
193 232 -v --verbose enable additional output
194 233 --config CONFIG [+] set/override config option (use 'section.name=value')
195 234 --debug enable debugging output
196 235 --debugger start debugger
197 236 --encoding ENCODE set the charset encoding (default: ascii)
198 237 --encodingmode MODE set the charset encoding mode (default: strict)
199 238 --traceback always print a traceback on exception
200 239 --time time how long the command takes
201 240 --profile print command execution profile
202 241 --version output version information and exit
203 242 -h --help display help and exit
204 243 --hidden consider hidden changesets
205 244
206 245 [+] marked option can be specified multiple times
207 246
208 247 $ hg --debug help debugextension
209 248 debugextension extension - only debugcommands
210 249
211 250 list of commands:
212 251
213 252 debugfoobar yet another debug command
214 253 foo yet another foo command
215 254
216 255 global options:
217 256
218 257 -R --repository REPO repository root directory or name of overlay bundle
219 258 file
220 259 --cwd DIR change working directory
221 260 -y --noninteractive do not prompt, automatically pick the first choice for
222 261 all prompts
223 262 -q --quiet suppress output
224 263 -v --verbose enable additional output
225 264 --config CONFIG [+] set/override config option (use 'section.name=value')
226 265 --debug enable debugging output
227 266 --debugger start debugger
228 267 --encoding ENCODE set the charset encoding (default: ascii)
229 268 --encodingmode MODE set the charset encoding mode (default: strict)
230 269 --traceback always print a traceback on exception
231 270 --time time how long the command takes
232 271 --profile print command execution profile
233 272 --version output version information and exit
234 273 -h --help display help and exit
235 274 --hidden consider hidden changesets
236 275
237 276 [+] marked option can be specified multiple times
238 277 $ echo 'debugextension = !' >> $HGRCPATH
239 278
240 279 Extension module help vs command help:
241 280
242 281 $ echo 'extdiff =' >> $HGRCPATH
243 282 $ hg help extdiff
244 283 hg extdiff [OPT]... [FILE]...
245 284
246 285 use external program to diff repository (or selected files)
247 286
248 287 Show differences between revisions for the specified files, using an
249 288 external program. The default program used is diff, with default options
250 289 "-Npru".
251 290
252 291 To select a different program, use the -p/--program option. The program
253 292 will be passed the names of two directories to compare. To pass additional
254 293 options to the program, use -o/--option. These will be passed before the
255 294 names of the directories to compare.
256 295
257 296 When two revision arguments are given, then changes are shown between
258 297 those revisions. If only one revision is specified then that revision is
259 298 compared to the working directory, and, when no revisions are specified,
260 299 the working directory files are compared to its parent.
261 300
262 301 use "hg help -e extdiff" to show help for the extdiff extension
263 302
264 303 options:
265 304
266 305 -p --program CMD comparison program to run
267 306 -o --option OPT [+] pass option to comparison program
268 307 -r --rev REV [+] revision
269 308 -c --change REV change made by revision
270 309 -I --include PATTERN [+] include names matching the given patterns
271 310 -X --exclude PATTERN [+] exclude names matching the given patterns
272 311
273 312 [+] marked option can be specified multiple times
274 313
275 314 use "hg -v help extdiff" to show the global options
276 315
277 316 $ hg help --extension extdiff
278 317 extdiff extension - command to allow external programs to compare revisions
279 318
280 319 The extdiff Mercurial extension allows you to use external programs to compare
281 320 revisions, or revision with working directory. The external diff programs are
282 321 called with a configurable set of options and two non-option arguments: paths
283 322 to directories containing snapshots of files to compare.
284 323
285 324 The extdiff extension also allows you to configure new diff commands, so you
286 325 do not need to type "hg extdiff -p kdiff3" always.
287 326
288 327 [extdiff]
289 328 # add new command that runs GNU diff(1) in 'context diff' mode
290 329 cdiff = gdiff -Nprc5
291 330 ## or the old way:
292 331 #cmd.cdiff = gdiff
293 332 #opts.cdiff = -Nprc5
294 333
295 334 # add new command called vdiff, runs kdiff3
296 335 vdiff = kdiff3
297 336
298 337 # add new command called meld, runs meld (no need to name twice)
299 338 meld =
300 339
301 340 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
302 341 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
303 342 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
304 343 # your .vimrc
305 344 vimdiff = gvim -f "+next" \
306 345 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
307 346
308 347 Tool arguments can include variables that are expanded at runtime:
309 348
310 349 $parent1, $plabel1 - filename, descriptive label of first parent
311 350 $child, $clabel - filename, descriptive label of child revision
312 351 $parent2, $plabel2 - filename, descriptive label of second parent
313 352 $root - repository root
314 353 $parent is an alias for $parent1.
315 354
316 355 The extdiff extension will look in your [diff-tools] and [merge-tools]
317 356 sections for diff tool arguments, when none are specified in [extdiff].
318 357
319 358 [extdiff]
320 359 kdiff3 =
321 360
322 361 [diff-tools]
323 362 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
324 363
325 364 You can use -I/-X and list of file or directory names like normal "hg diff"
326 365 command. The extdiff extension makes snapshots of only needed files, so
327 366 running the external diff program will actually be pretty fast (at least
328 367 faster than having to compare the entire tree).
329 368
330 369 list of commands:
331 370
332 371 extdiff use external program to diff repository (or selected files)
333 372
334 373 use "hg -v help extdiff" to show builtin aliases and global options
335 374
336 375 $ echo 'extdiff = !' >> $HGRCPATH
337 376
338 377 Test help topic with same name as extension
339 378
340 379 $ cat > multirevs.py <<EOF
341 380 > from mercurial import commands
342 381 > """multirevs extension
343 382 > Big multi-line module docstring."""
344 383 > def multirevs(ui, repo, arg, *args, **opts):
345 384 > """multirevs command"""
346 385 > pass
347 386 > cmdtable = {
348 387 > "multirevs": (multirevs, [], 'ARG')
349 388 > }
350 389 > commands.norepo += ' multirevs'
351 390 > EOF
352 391 $ echo "multirevs = multirevs.py" >> $HGRCPATH
353 392
354 393 $ hg help multirevs
355 394 Specifying Multiple Revisions
356 395 """""""""""""""""""""""""""""
357 396
358 397 When Mercurial accepts more than one revision, they may be specified
359 398 individually, or provided as a topologically continuous range, separated
360 399 by the ":" character.
361 400
362 401 The syntax of range notation is [BEGIN]:[END], where BEGIN and END are
363 402 revision identifiers. Both BEGIN and END are optional. If BEGIN is not
364 403 specified, it defaults to revision number 0. If END is not specified, it
365 404 defaults to the tip. The range ":" thus means "all revisions".
366 405
367 406 If BEGIN is greater than END, revisions are treated in reverse order.
368 407
369 408 A range acts as a closed interval. This means that a range of 3:5 gives 3,
370 409 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6.
371 410
372 411 use "hg help -c multirevs" to see help for the multirevs command
373 412
374 413 $ hg help -c multirevs
375 414 hg multirevs ARG
376 415
377 416 multirevs command
378 417
379 418 use "hg -v help multirevs" to show the global options
380 419
381 420 $ hg multirevs
382 421 hg multirevs: invalid arguments
383 422 hg multirevs ARG
384 423
385 424 multirevs command
386 425
387 426 use "hg help multirevs" to show the full help text
388 427 [255]
389 428
390 429 $ echo "multirevs = !" >> $HGRCPATH
391 430
392 431 Issue811: Problem loading extensions twice (by site and by user)
393 432
394 433 $ debugpath=`pwd`/debugissue811.py
395 434 $ cat > debugissue811.py <<EOF
396 435 > '''show all loaded extensions
397 436 > '''
398 437 > from mercurial import extensions, commands
399 438 >
400 439 > def debugextensions(ui):
401 440 > "yet another debug command"
402 441 > ui.write("%s\n" % '\n'.join([x for x, y in extensions.extensions()]))
403 442 >
404 443 > cmdtable = {"debugextensions": (debugextensions, (), "hg debugextensions")}
405 444 > commands.norepo += " debugextensions"
406 445 > EOF
407 446 $ echo "debugissue811 = $debugpath" >> $HGRCPATH
408 447 $ echo "mq=" >> $HGRCPATH
409 448 $ echo "strip=" >> $HGRCPATH
410 449 $ echo "hgext.mq=" >> $HGRCPATH
411 450 $ echo "hgext/mq=" >> $HGRCPATH
412 451
413 452 Show extensions:
414 453 (note that mq force load strip, also checking it's not loaded twice)
415 454
416 455 $ hg debugextensions
417 456 debugissue811
418 457 strip
419 458 mq
420 459
421 460 Disabled extension commands:
422 461
423 462 $ ORGHGRCPATH=$HGRCPATH
424 463 $ HGRCPATH=
425 464 $ export HGRCPATH
426 465 $ hg help email
427 466 'email' is provided by the following extension:
428 467
429 468 patchbomb command to send changesets as (a series of) patch emails
430 469
431 470 use "hg help extensions" for information on enabling extensions
432 471 $ hg qdel
433 472 hg: unknown command 'qdel'
434 473 'qdelete' is provided by the following extension:
435 474
436 475 mq manage a stack of patches
437 476
438 477 use "hg help extensions" for information on enabling extensions
439 478 [255]
440 479 $ hg churn
441 480 hg: unknown command 'churn'
442 481 'churn' is provided by the following extension:
443 482
444 483 churn command to display statistics about repository history
445 484
446 485 use "hg help extensions" for information on enabling extensions
447 486 [255]
448 487
449 488 Disabled extensions:
450 489
451 490 $ hg help churn
452 491 churn extension - command to display statistics about repository history
453 492
454 493 use "hg help extensions" for information on enabling extensions
455 494 $ hg help patchbomb
456 495 patchbomb extension - command to send changesets as (a series of) patch emails
457 496
458 497 use "hg help extensions" for information on enabling extensions
459 498
460 499 Broken disabled extension and command:
461 500
462 501 $ mkdir hgext
463 502 $ echo > hgext/__init__.py
464 503 $ cat > hgext/broken.py <<EOF
465 504 > "broken extension'
466 505 > EOF
467 506 $ cat > path.py <<EOF
468 507 > import os, sys
469 508 > sys.path.insert(0, os.environ['HGEXTPATH'])
470 509 > EOF
471 510 $ HGEXTPATH=`pwd`
472 511 $ export HGEXTPATH
473 512
474 513 $ hg --config extensions.path=./path.py help broken
475 514 broken extension - (no help text available)
476 515
477 516 use "hg help extensions" for information on enabling extensions
478 517
479 518 $ cat > hgext/forest.py <<EOF
480 519 > cmdtable = None
481 520 > EOF
482 521 $ hg --config extensions.path=./path.py help foo > /dev/null
483 522 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
484 523 hg: unknown command 'foo'
485 524 warning: error finding commands in $TESTTMP/hgext/forest.py (glob)
486 525 [255]
487 526
488 527 $ cat > throw.py <<EOF
489 528 > from mercurial import cmdutil, commands
490 529 > cmdtable = {}
491 530 > command = cmdutil.command(cmdtable)
492 531 > class Bogon(Exception): pass
493 532 >
494 533 > @command('throw', [], 'hg throw')
495 534 > def throw(ui, **opts):
496 535 > """throws an exception"""
497 536 > raise Bogon()
498 537 > commands.norepo += " throw"
499 538 > EOF
500 539 No declared supported version, extension complains:
501 540 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
502 541 ** Unknown exception encountered with possibly-broken third-party extension throw
503 542 ** which supports versions unknown of Mercurial.
504 543 ** Please disable throw and try your action again.
505 544 ** If that fixes the bug please report it to the extension author.
506 545 ** Python * (glob)
507 546 ** Mercurial Distributed SCM * (glob)
508 547 ** Extensions loaded: throw
509 548 empty declaration of supported version, extension complains:
510 549 $ echo "testedwith = ''" >> throw.py
511 550 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
512 551 ** Unknown exception encountered with possibly-broken third-party extension throw
513 552 ** which supports versions unknown of Mercurial.
514 553 ** Please disable throw and try your action again.
515 554 ** If that fixes the bug please report it to the extension author.
516 555 ** Python * (glob)
517 556 ** Mercurial Distributed SCM (*) (glob)
518 557 ** Extensions loaded: throw
519 558 If the extension specifies a buglink, show that:
520 559 $ echo 'buglink = "http://example.com/bts"' >> throw.py
521 560 $ rm -f throw.pyc throw.pyo
522 561 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
523 562 ** Unknown exception encountered with possibly-broken third-party extension throw
524 563 ** which supports versions unknown of Mercurial.
525 564 ** Please disable throw and try your action again.
526 565 ** If that fixes the bug please report it to http://example.com/bts
527 566 ** Python * (glob)
528 567 ** Mercurial Distributed SCM (*) (glob)
529 568 ** Extensions loaded: throw
530 569 If the extensions declare outdated versions, accuse the older extension first:
531 570 $ echo "from mercurial import util" >> older.py
532 571 $ echo "util.version = lambda:'2.2'" >> older.py
533 572 $ echo "testedwith = '1.9.3'" >> older.py
534 573 $ echo "testedwith = '2.1.1'" >> throw.py
535 574 $ rm -f throw.pyc throw.pyo
536 575 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
537 576 > throw 2>&1 | egrep '^\*\*'
538 577 ** Unknown exception encountered with possibly-broken third-party extension older
539 578 ** which supports versions 1.9.3 of Mercurial.
540 579 ** Please disable older and try your action again.
541 580 ** If that fixes the bug please report it to the extension author.
542 581 ** Python * (glob)
543 582 ** Mercurial Distributed SCM (version 2.2)
544 583 ** Extensions loaded: throw, older
545 584 One extension only tested with older, one only with newer versions:
546 585 $ echo "util.version = lambda:'2.1.0'" >> older.py
547 586 $ rm -f older.pyc older.pyo
548 587 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
549 588 > throw 2>&1 | egrep '^\*\*'
550 589 ** Unknown exception encountered with possibly-broken third-party extension older
551 590 ** which supports versions 1.9.3 of Mercurial.
552 591 ** Please disable older and try your action again.
553 592 ** If that fixes the bug please report it to the extension author.
554 593 ** Python * (glob)
555 594 ** Mercurial Distributed SCM (version 2.1.0)
556 595 ** Extensions loaded: throw, older
557 596 Older extension is tested with current version, the other only with newer:
558 597 $ echo "util.version = lambda:'1.9.3'" >> older.py
559 598 $ rm -f older.pyc older.pyo
560 599 $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
561 600 > throw 2>&1 | egrep '^\*\*'
562 601 ** Unknown exception encountered with possibly-broken third-party extension throw
563 602 ** which supports versions 2.1.1 of Mercurial.
564 603 ** Please disable throw and try your action again.
565 604 ** If that fixes the bug please report it to http://example.com/bts
566 605 ** Python * (glob)
567 606 ** Mercurial Distributed SCM (version 1.9.3)
568 607 ** Extensions loaded: throw, older
569 608
570 609 Declare the version as supporting this hg version, show regular bts link:
571 610 $ hgver=`python -c 'from mercurial import util; print util.version().split("+")[0]'`
572 611 $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
573 612 $ rm -f throw.pyc throw.pyo
574 613 $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
575 614 ** unknown exception encountered, please report by visiting
576 615 ** http://mercurial.selenic.com/wiki/BugTracker
577 616 ** Python * (glob)
578 617 ** Mercurial Distributed SCM (*) (glob)
579 618 ** Extensions loaded: throw
580 619
581 620 Restore HGRCPATH
582 621
583 622 $ HGRCPATH=$ORGHGRCPATH
584 623 $ export HGRCPATH
585 624
586 625 Commands handling multiple repositories at a time should invoke only
587 626 "reposetup()" of extensions enabling in the target repository.
588 627
589 628 $ mkdir reposetup-test
590 629 $ cd reposetup-test
591 630
592 631 $ cat > $TESTTMP/reposetuptest.py <<EOF
593 632 > from mercurial import extensions
594 633 > def reposetup(ui, repo):
595 634 > ui.write('reposetup() for %s\n' % (repo.root))
596 635 > EOF
597 636 $ hg init src
598 637 $ echo a > src/a
599 638 $ hg -R src commit -Am '#0 at src/a'
600 639 adding a
601 640 $ echo '[extensions]' >> src/.hg/hgrc
602 641 $ echo '# enable extension locally' >> src/.hg/hgrc
603 642 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
604 643 $ hg -R src status
605 644 reposetup() for $TESTTMP/reposetup-test/src
606 645
607 646 $ hg clone -U src clone-dst1
608 647 reposetup() for $TESTTMP/reposetup-test/src
609 648 $ hg init push-dst1
610 649 $ hg -q -R src push push-dst1
611 650 reposetup() for $TESTTMP/reposetup-test/src
612 651 $ hg init pull-src1
613 652 $ hg -q -R pull-src1 pull src
614 653 reposetup() for $TESTTMP/reposetup-test/src
615 654
616 655 $ echo '[extensions]' >> $HGRCPATH
617 656 $ echo '# disable extension globally and explicitly' >> $HGRCPATH
618 657 $ echo 'reposetuptest = !' >> $HGRCPATH
619 658 $ hg clone -U src clone-dst2
620 659 reposetup() for $TESTTMP/reposetup-test/src
621 660 $ hg init push-dst2
622 661 $ hg -q -R src push push-dst2
623 662 reposetup() for $TESTTMP/reposetup-test/src
624 663 $ hg init pull-src2
625 664 $ hg -q -R pull-src2 pull src
626 665 reposetup() for $TESTTMP/reposetup-test/src
627 666
628 667 $ echo '[extensions]' >> $HGRCPATH
629 668 $ echo '# enable extension globally' >> $HGRCPATH
630 669 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> $HGRCPATH
631 670 $ hg clone -U src clone-dst3
632 671 reposetup() for $TESTTMP/reposetup-test/src
633 672 reposetup() for $TESTTMP/reposetup-test/clone-dst3
634 673 $ hg init push-dst3
635 674 reposetup() for $TESTTMP/reposetup-test/push-dst3
636 675 $ hg -q -R src push push-dst3
637 676 reposetup() for $TESTTMP/reposetup-test/src
638 677 reposetup() for $TESTTMP/reposetup-test/push-dst3
639 678 $ hg init pull-src3
640 679 reposetup() for $TESTTMP/reposetup-test/pull-src3
641 680 $ hg -q -R pull-src3 pull src
642 681 reposetup() for $TESTTMP/reposetup-test/pull-src3
643 682 reposetup() for $TESTTMP/reposetup-test/src
644 683
645 684 $ echo '[extensions]' >> src/.hg/hgrc
646 685 $ echo '# disable extension locally' >> src/.hg/hgrc
647 686 $ echo 'reposetuptest = !' >> src/.hg/hgrc
648 687 $ hg clone -U src clone-dst4
649 688 reposetup() for $TESTTMP/reposetup-test/clone-dst4
650 689 $ hg init push-dst4
651 690 reposetup() for $TESTTMP/reposetup-test/push-dst4
652 691 $ hg -q -R src push push-dst4
653 692 reposetup() for $TESTTMP/reposetup-test/push-dst4
654 693 $ hg init pull-src4
655 694 reposetup() for $TESTTMP/reposetup-test/pull-src4
656 695 $ hg -q -R pull-src4 pull src
657 696 reposetup() for $TESTTMP/reposetup-test/pull-src4
658 697
659 698 disabling in command line overlays with all configuration
660 699 $ hg --config extensions.reposetuptest=! clone -U src clone-dst5
661 700 $ hg --config extensions.reposetuptest=! init push-dst5
662 701 $ hg --config extensions.reposetuptest=! -q -R src push push-dst5
663 702 $ hg --config extensions.reposetuptest=! init pull-src5
664 703 $ hg --config extensions.reposetuptest=! -q -R pull-src5 pull src
665 704
666 705 $ echo '[extensions]' >> $HGRCPATH
667 706 $ echo '# disable extension globally and explicitly' >> $HGRCPATH
668 707 $ echo 'reposetuptest = !' >> $HGRCPATH
669 708 $ hg init parent
670 709 $ hg init parent/sub1
671 710 $ echo 1 > parent/sub1/1
672 711 $ hg -R parent/sub1 commit -Am '#0 at parent/sub1'
673 712 adding 1
674 713 $ hg init parent/sub2
675 714 $ hg init parent/sub2/sub21
676 715 $ echo 21 > parent/sub2/sub21/21
677 716 $ hg -R parent/sub2/sub21 commit -Am '#0 at parent/sub2/sub21'
678 717 adding 21
679 718 $ cat > parent/sub2/.hgsub <<EOF
680 719 > sub21 = sub21
681 720 > EOF
682 721 $ hg -R parent/sub2 commit -Am '#0 at parent/sub2'
683 722 adding .hgsub
684 723 $ hg init parent/sub3
685 724 $ echo 3 > parent/sub3/3
686 725 $ hg -R parent/sub3 commit -Am '#0 at parent/sub3'
687 726 adding 3
688 727 $ cat > parent/.hgsub <<EOF
689 728 > sub1 = sub1
690 729 > sub2 = sub2
691 730 > sub3 = sub3
692 731 > EOF
693 732 $ hg -R parent commit -Am '#0 at parent'
694 733 adding .hgsub
695 734 $ echo '[extensions]' >> parent/.hg/hgrc
696 735 $ echo '# enable extension locally' >> parent/.hg/hgrc
697 736 $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> parent/.hg/hgrc
698 737 $ cp parent/.hg/hgrc parent/sub2/.hg/hgrc
699 738 $ hg -R parent status -S -A
700 739 reposetup() for $TESTTMP/reposetup-test/parent
701 740 reposetup() for $TESTTMP/reposetup-test/parent/sub2
702 741 C .hgsub
703 742 C .hgsubstate
704 743 C sub1/1
705 744 C sub2/.hgsub
706 745 C sub2/.hgsubstate
707 746 C sub2/sub21/21
708 747 C sub3/3
709 748
710 749 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now