##// END OF EJS Templates
extensions: raise when trying to find an extension that failed to load...
Idan Kamara -
r14415:c238b12a default
parent child Browse files
Show More
@@ -1,334 +1,338 b''
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-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 import imp, os
9 9 import util, cmdutil, error
10 10 from i18n import _, gettext
11 11
12 12 _extensions = {}
13 13 _order = []
14 14 _ignore = ['hbisect', 'bookmarks', 'parentrevspec']
15 15
16 16 def extensions():
17 17 for name in _order:
18 18 module = _extensions[name]
19 19 if module:
20 20 yield name, module
21 21
22 22 def find(name):
23 23 '''return module with given extension name'''
24 mod = None
24 25 try:
25 return _extensions[name]
26 mod = _extensions[name]
26 27 except KeyError:
27 28 for k, v in _extensions.iteritems():
28 29 if k.endswith('.' + name) or k.endswith('/' + name):
29 return v
30 mod = v
31 break
32 if not mod:
30 33 raise KeyError(name)
34 return mod
31 35
32 36 def loadpath(path, module_name):
33 37 module_name = module_name.replace('.', '_')
34 38 path = util.expandpath(path)
35 39 if os.path.isdir(path):
36 40 # module/__init__.py style
37 41 d, f = os.path.split(path.rstrip('/'))
38 42 fd, fpath, desc = imp.find_module(f, [d])
39 43 return imp.load_module(module_name, fd, fpath, desc)
40 44 else:
41 45 return imp.load_source(module_name, path)
42 46
43 47 def load(ui, name, path):
44 48 # unused ui argument kept for backwards compatibility
45 49 if name.startswith('hgext.') or name.startswith('hgext/'):
46 50 shortname = name[6:]
47 51 else:
48 52 shortname = name
49 53 if shortname in _ignore:
50 54 return None
51 55 if shortname in _extensions:
52 56 return _extensions[shortname]
53 57 _extensions[shortname] = None
54 58 if path:
55 59 # the module will be loaded in sys.modules
56 60 # choose an unique name so that it doesn't
57 61 # conflicts with other modules
58 62 mod = loadpath(path, 'hgext.%s' % name)
59 63 else:
60 64 def importh(name):
61 65 mod = __import__(name)
62 66 components = name.split('.')
63 67 for comp in components[1:]:
64 68 mod = getattr(mod, comp)
65 69 return mod
66 70 try:
67 71 mod = importh("hgext.%s" % name)
68 72 except ImportError:
69 73 mod = importh(name)
70 74 _extensions[shortname] = mod
71 75 _order.append(shortname)
72 76 return mod
73 77
74 78 def loadall(ui):
75 79 result = ui.configitems("extensions")
76 80 newindex = len(_order)
77 81 for (name, path) in result:
78 82 if path:
79 83 if path[0] == '!':
80 84 continue
81 85 try:
82 86 load(ui, name, path)
83 87 except KeyboardInterrupt:
84 88 raise
85 89 except Exception, inst:
86 90 if path:
87 91 ui.warn(_("*** failed to import extension %s from %s: %s\n")
88 92 % (name, path, inst))
89 93 else:
90 94 ui.warn(_("*** failed to import extension %s: %s\n")
91 95 % (name, inst))
92 96 if ui.traceback():
93 97 return 1
94 98
95 99 for name in _order[newindex:]:
96 100 uisetup = getattr(_extensions[name], 'uisetup', None)
97 101 if uisetup:
98 102 uisetup(ui)
99 103
100 104 for name in _order[newindex:]:
101 105 extsetup = getattr(_extensions[name], 'extsetup', None)
102 106 if extsetup:
103 107 try:
104 108 extsetup(ui)
105 109 except TypeError:
106 110 if extsetup.func_code.co_argcount != 0:
107 111 raise
108 112 extsetup() # old extsetup with no ui argument
109 113
110 114 def wrapcommand(table, command, wrapper):
111 115 '''Wrap the command named `command' in table
112 116
113 117 Replace command in the command table with wrapper. The wrapped command will
114 118 be inserted into the command table specified by the table argument.
115 119
116 120 The wrapper will be called like
117 121
118 122 wrapper(orig, *args, **kwargs)
119 123
120 124 where orig is the original (wrapped) function, and *args, **kwargs
121 125 are the arguments passed to it.
122 126 '''
123 127 assert hasattr(wrapper, '__call__')
124 128 aliases, entry = cmdutil.findcmd(command, table)
125 129 for alias, e in table.iteritems():
126 130 if e is entry:
127 131 key = alias
128 132 break
129 133
130 134 origfn = entry[0]
131 135 def wrap(*args, **kwargs):
132 136 return util.checksignature(wrapper)(
133 137 util.checksignature(origfn), *args, **kwargs)
134 138
135 139 wrap.__doc__ = getattr(origfn, '__doc__')
136 140 wrap.__module__ = getattr(origfn, '__module__')
137 141
138 142 newentry = list(entry)
139 143 newentry[0] = wrap
140 144 table[key] = tuple(newentry)
141 145 return entry
142 146
143 147 def wrapfunction(container, funcname, wrapper):
144 148 '''Wrap the function named funcname in container
145 149
146 150 Replace the funcname member in the given container with the specified
147 151 wrapper. The container is typically a module, class, or instance.
148 152
149 153 The wrapper will be called like
150 154
151 155 wrapper(orig, *args, **kwargs)
152 156
153 157 where orig is the original (wrapped) function, and *args, **kwargs
154 158 are the arguments passed to it.
155 159
156 160 Wrapping methods of the repository object is not recommended since
157 161 it conflicts with extensions that extend the repository by
158 162 subclassing. All extensions that need to extend methods of
159 163 localrepository should use this subclassing trick: namely,
160 164 reposetup() should look like
161 165
162 166 def reposetup(ui, repo):
163 167 class myrepo(repo.__class__):
164 168 def whatever(self, *args, **kwargs):
165 169 [...extension stuff...]
166 170 super(myrepo, self).whatever(*args, **kwargs)
167 171 [...extension stuff...]
168 172
169 173 repo.__class__ = myrepo
170 174
171 175 In general, combining wrapfunction() with subclassing does not
172 176 work. Since you cannot control what other extensions are loaded by
173 177 your end users, you should play nicely with others by using the
174 178 subclass trick.
175 179 '''
176 180 assert hasattr(wrapper, '__call__')
177 181 def wrap(*args, **kwargs):
178 182 return wrapper(origfn, *args, **kwargs)
179 183
180 184 origfn = getattr(container, funcname)
181 185 assert hasattr(origfn, '__call__')
182 186 setattr(container, funcname, wrap)
183 187 return origfn
184 188
185 189 def _disabledpaths(strip_init=False):
186 190 '''find paths of disabled extensions. returns a dict of {name: path}
187 191 removes /__init__.py from packages if strip_init is True'''
188 192 import hgext
189 193 extpath = os.path.dirname(os.path.abspath(hgext.__file__))
190 194 try: # might not be a filesystem path
191 195 files = os.listdir(extpath)
192 196 except OSError:
193 197 return {}
194 198
195 199 exts = {}
196 200 for e in files:
197 201 if e.endswith('.py'):
198 202 name = e.rsplit('.', 1)[0]
199 203 path = os.path.join(extpath, e)
200 204 else:
201 205 name = e
202 206 path = os.path.join(extpath, e, '__init__.py')
203 207 if not os.path.exists(path):
204 208 continue
205 209 if strip_init:
206 210 path = os.path.dirname(path)
207 211 if name in exts or name in _order or name == '__init__':
208 212 continue
209 213 exts[name] = path
210 214 return exts
211 215
212 216 def _moduledoc(file):
213 217 '''return the top-level python documentation for the given file
214 218
215 219 Loosely inspired by pydoc.source_synopsis(), but rewritten to
216 220 handle triple quotes and to return the whole text instead of just
217 221 the synopsis'''
218 222 result = []
219 223
220 224 line = file.readline()
221 225 while line[:1] == '#' or not line.strip():
222 226 line = file.readline()
223 227 if not line:
224 228 break
225 229
226 230 start = line[:3]
227 231 if start == '"""' or start == "'''":
228 232 line = line[3:]
229 233 while line:
230 234 if line.rstrip().endswith(start):
231 235 line = line.split(start)[0]
232 236 if line:
233 237 result.append(line)
234 238 break
235 239 elif not line:
236 240 return None # unmatched delimiter
237 241 result.append(line)
238 242 line = file.readline()
239 243 else:
240 244 return None
241 245
242 246 return ''.join(result)
243 247
244 248 def _disabledhelp(path):
245 249 '''retrieve help synopsis of a disabled extension (without importing)'''
246 250 try:
247 251 file = open(path)
248 252 except IOError:
249 253 return
250 254 else:
251 255 doc = _moduledoc(file)
252 256 file.close()
253 257
254 258 if doc: # extracting localized synopsis
255 259 return gettext(doc).splitlines()[0]
256 260 else:
257 261 return _('(no help text available)')
258 262
259 263 def disabled():
260 264 '''find disabled extensions from hgext
261 265 returns a dict of {name: desc}, and the max name length'''
262 266
263 267 paths = _disabledpaths()
264 268 if not paths:
265 269 return None
266 270
267 271 exts = {}
268 272 for name, path in paths.iteritems():
269 273 doc = _disabledhelp(path)
270 274 if doc:
271 275 exts[name] = doc
272 276
273 277 return exts
274 278
275 279 def disabledext(name):
276 280 '''find a specific disabled extension from hgext. returns desc'''
277 281 paths = _disabledpaths()
278 282 if name in paths:
279 283 return _disabledhelp(paths[name])
280 284
281 285 def disabledcmd(ui, cmd, strict=False):
282 286 '''import disabled extensions until cmd is found.
283 287 returns (cmdname, extname, doc)'''
284 288
285 289 paths = _disabledpaths(strip_init=True)
286 290 if not paths:
287 291 raise error.UnknownCommand(cmd)
288 292
289 293 def findcmd(cmd, name, path):
290 294 try:
291 295 mod = loadpath(path, 'hgext.%s' % name)
292 296 except Exception:
293 297 return
294 298 try:
295 299 aliases, entry = cmdutil.findcmd(cmd,
296 300 getattr(mod, 'cmdtable', {}), strict)
297 301 except (error.AmbiguousCommand, error.UnknownCommand):
298 302 return
299 303 except Exception:
300 304 ui.warn(_('warning: error finding commands in %s\n') % path)
301 305 ui.traceback()
302 306 return
303 307 for c in aliases:
304 308 if c.startswith(cmd):
305 309 cmd = c
306 310 break
307 311 else:
308 312 cmd = aliases[0]
309 313 return (cmd, name, mod)
310 314
311 315 # first, search for an extension with the same name as the command
312 316 path = paths.pop(cmd, None)
313 317 if path:
314 318 ext = findcmd(cmd, cmd, path)
315 319 if ext:
316 320 return ext
317 321
318 322 # otherwise, interrogate each extension until there's a match
319 323 for name, path in paths.iteritems():
320 324 ext = findcmd(cmd, name, path)
321 325 if ext:
322 326 return ext
323 327
324 328 raise error.UnknownCommand(cmd)
325 329
326 330 def enabled():
327 331 '''return a dict of {name: desc} of extensions, and the max name length'''
328 332 exts = {}
329 333 for ename, ext in extensions():
330 334 doc = (gettext(ext.__doc__) or _('(no help text available)'))
331 335 ename = ename.split('.')[-1]
332 336 exts[ename] = doc.splitlines()[0].strip()
333 337
334 338 return exts
@@ -1,368 +1,383 b''
1 1 Create configuration
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "interactive=true" >> $HGRCPATH
5 5
6 6 help record (no record)
7 7
8 8 $ hg help record
9 9 record extension - commands to interactively select changes for commit/qrefresh
10 10
11 11 use "hg help extensions" for information on enabling extensions
12 12
13 13 help qrecord (no record)
14 14
15 15 $ hg help qrecord
16 16 'qrecord' is provided by the following extension:
17 17
18 18 record commands to interactively select changes for commit/qrefresh
19 19
20 20 use "hg help extensions" for information on enabling extensions
21 21
22 22 $ echo "[extensions]" >> $HGRCPATH
23 23 $ echo "record=" >> $HGRCPATH
24 24
25 25 help record (record)
26 26
27 27 $ hg help record
28 28 hg record [OPTION]... [FILE]...
29 29
30 30 interactively select changes to commit
31 31
32 32 If a list of files is omitted, all changes reported by "hg status" will be
33 33 candidates for recording.
34 34
35 35 See "hg help dates" for a list of formats valid for -d/--date.
36 36
37 37 You will be prompted for whether to record changes to each modified file,
38 38 and for files with multiple changes, for each change to use. For each
39 39 query, the following responses are possible:
40 40
41 41 y - record this change
42 42 n - skip this change
43 43
44 44 s - skip remaining changes to this file
45 45 f - record remaining changes to this file
46 46
47 47 d - done, skip remaining changes and files
48 48 a - record all changes to all remaining files
49 49 q - quit, recording no changes
50 50
51 51 ? - display help
52 52
53 53 This command is not available when committing a merge.
54 54
55 55 options:
56 56
57 57 -A --addremove mark new/missing files as added/removed before
58 58 committing
59 59 --close-branch mark a branch as closed, hiding it from the branch
60 60 list
61 61 -I --include PATTERN [+] include names matching the given patterns
62 62 -X --exclude PATTERN [+] exclude names matching the given patterns
63 63 -m --message TEXT use text as commit message
64 64 -l --logfile FILE read commit message from file
65 65 -d --date DATE record the specified date as commit date
66 66 -u --user USER record the specified user as committer
67 67
68 68 [+] marked option can be specified multiple times
69 69
70 70 use "hg -v help record" to show global options
71 71
72 72 help (no mq, so no qrecord)
73 73
74 74 $ hg help qrecord
75 75 hg qrecord [OPTION]... PATCH [FILE]...
76 76
77 77 interactively record a new patch
78 78
79 79 See "hg help qnew" & "hg help record" for more information and usage.
80 80
81 81 use "hg -v help qrecord" to show global options
82 82
83 83 $ hg init a
84 84
85 85 qrecord (mq not present)
86 86
87 87 $ hg -R a qrecord
88 88 hg qrecord: invalid arguments
89 89 hg qrecord [OPTION]... PATCH [FILE]...
90 90
91 91 interactively record a new patch
92 92
93 93 use "hg help qrecord" to show the full help text
94 94 [255]
95 95
96 96 qrecord patch (mq not present)
97 97
98 98 $ hg -R a qrecord patch
99 99 abort: 'mq' extension not loaded
100 100 [255]
101 101
102 help (bad mq)
103
104 $ echo "mq=nonexistant" >> $HGRCPATH
105 $ hg help qrecord
106 *** failed to import extension mq from nonexistant: [Errno 2] No such file or directory
107 hg qrecord [OPTION]... PATCH [FILE]...
108
109 interactively record a new patch
110
111 See "hg help qnew" & "hg help record" for more information and usage.
112
113 use "hg -v help qrecord" to show global options
114
102 115 help (mq present)
103 116
104 $ echo "mq=" >> $HGRCPATH
117 $ sed 's/mq=nonexistant/mq=/' $HGRCPATH > hgrc.tmp
118 $ mv hgrc.tmp $HGRCPATH
119
105 120 $ hg help qrecord
106 121 hg qrecord [OPTION]... PATCH [FILE]...
107 122
108 123 interactively record a new patch
109 124
110 125 See "hg help qnew" & "hg help record" for more information and usage.
111 126
112 127 options:
113 128
114 129 -e --edit edit commit message
115 130 -g --git use git extended diff format
116 131 -U --currentuser add "From: <current user>" to patch
117 132 -u --user USER add "From: <USER>" to patch
118 133 -D --currentdate add "Date: <current date>" to patch
119 134 -d --date DATE add "Date: <DATE>" to patch
120 135 -I --include PATTERN [+] include names matching the given patterns
121 136 -X --exclude PATTERN [+] exclude names matching the given patterns
122 137 -m --message TEXT use text as commit message
123 138 -l --logfile FILE read commit message from file
124 139
125 140 [+] marked option can be specified multiple times
126 141
127 142 use "hg -v help qrecord" to show global options
128 143
129 144 $ cd a
130 145
131 146 Base commit
132 147
133 148 $ cat > 1.txt <<EOF
134 149 > 1
135 150 > 2
136 151 > 3
137 152 > 4
138 153 > 5
139 154 > EOF
140 155 $ cat > 2.txt <<EOF
141 156 > a
142 157 > b
143 158 > c
144 159 > d
145 160 > e
146 161 > f
147 162 > EOF
148 163
149 164 $ mkdir dir
150 165 $ cat > dir/a.txt <<EOF
151 166 > hello world
152 167 >
153 168 > someone
154 169 > up
155 170 > there
156 171 > loves
157 172 > me
158 173 > EOF
159 174
160 175 $ hg add 1.txt 2.txt dir/a.txt
161 176 $ hg commit -m 'initial checkin'
162 177
163 178 Changing files
164 179
165 180 $ sed -e 's/2/2 2/;s/4/4 4/' 1.txt > 1.txt.new
166 181 $ sed -e 's/b/b b/' 2.txt > 2.txt.new
167 182 $ sed -e 's/hello world/hello world!/' dir/a.txt > dir/a.txt.new
168 183
169 184 $ mv -f 1.txt.new 1.txt
170 185 $ mv -f 2.txt.new 2.txt
171 186 $ mv -f dir/a.txt.new dir/a.txt
172 187
173 188 Whole diff
174 189
175 190 $ hg diff --nodates
176 191 diff -r 1057167b20ef 1.txt
177 192 --- a/1.txt
178 193 +++ b/1.txt
179 194 @@ -1,5 +1,5 @@
180 195 1
181 196 -2
182 197 +2 2
183 198 3
184 199 -4
185 200 +4 4
186 201 5
187 202 diff -r 1057167b20ef 2.txt
188 203 --- a/2.txt
189 204 +++ b/2.txt
190 205 @@ -1,5 +1,5 @@
191 206 a
192 207 -b
193 208 +b b
194 209 c
195 210 d
196 211 e
197 212 diff -r 1057167b20ef dir/a.txt
198 213 --- a/dir/a.txt
199 214 +++ b/dir/a.txt
200 215 @@ -1,4 +1,4 @@
201 216 -hello world
202 217 +hello world!
203 218
204 219 someone
205 220 up
206 221
207 222 qrecord a.patch
208 223
209 224 $ hg qrecord -d '0 0' -m aaa a.patch <<EOF
210 225 > y
211 226 > y
212 227 > n
213 228 > y
214 229 > y
215 230 > n
216 231 > EOF
217 232 diff --git a/1.txt b/1.txt
218 233 2 hunks, 2 lines changed
219 234 examine changes to '1.txt'? [Ynsfdaq?]
220 235 @@ -1,3 +1,3 @@
221 236 1
222 237 -2
223 238 +2 2
224 239 3
225 240 record change 1/4 to '1.txt'? [Ynsfdaq?]
226 241 @@ -3,3 +3,3 @@
227 242 3
228 243 -4
229 244 +4 4
230 245 5
231 246 record change 2/4 to '1.txt'? [Ynsfdaq?]
232 247 diff --git a/2.txt b/2.txt
233 248 1 hunks, 1 lines changed
234 249 examine changes to '2.txt'? [Ynsfdaq?]
235 250 @@ -1,5 +1,5 @@
236 251 a
237 252 -b
238 253 +b b
239 254 c
240 255 d
241 256 e
242 257 record change 3/4 to '2.txt'? [Ynsfdaq?]
243 258 diff --git a/dir/a.txt b/dir/a.txt
244 259 1 hunks, 1 lines changed
245 260 examine changes to 'dir/a.txt'? [Ynsfdaq?]
246 261
247 262 After qrecord a.patch 'tip'"
248 263
249 264 $ hg tip -p
250 265 changeset: 1:5d1ca63427ee
251 266 tag: a.patch
252 267 tag: qbase
253 268 tag: qtip
254 269 tag: tip
255 270 user: test
256 271 date: Thu Jan 01 00:00:00 1970 +0000
257 272 summary: aaa
258 273
259 274 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
260 275 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
261 276 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
262 277 @@ -1,5 +1,5 @@
263 278 1
264 279 -2
265 280 +2 2
266 281 3
267 282 4
268 283 5
269 284 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
270 285 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
271 286 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
272 287 @@ -1,5 +1,5 @@
273 288 a
274 289 -b
275 290 +b b
276 291 c
277 292 d
278 293 e
279 294
280 295
281 296 After qrecord a.patch 'diff'"
282 297
283 298 $ hg diff --nodates
284 299 diff -r 5d1ca63427ee 1.txt
285 300 --- a/1.txt
286 301 +++ b/1.txt
287 302 @@ -1,5 +1,5 @@
288 303 1
289 304 2 2
290 305 3
291 306 -4
292 307 +4 4
293 308 5
294 309 diff -r 5d1ca63427ee dir/a.txt
295 310 --- a/dir/a.txt
296 311 +++ b/dir/a.txt
297 312 @@ -1,4 +1,4 @@
298 313 -hello world
299 314 +hello world!
300 315
301 316 someone
302 317 up
303 318
304 319 qrecord b.patch
305 320
306 321 $ hg qrecord -d '0 0' -m bbb b.patch <<EOF
307 322 > y
308 323 > y
309 324 > y
310 325 > y
311 326 > EOF
312 327 diff --git a/1.txt b/1.txt
313 328 1 hunks, 1 lines changed
314 329 examine changes to '1.txt'? [Ynsfdaq?]
315 330 @@ -1,5 +1,5 @@
316 331 1
317 332 2 2
318 333 3
319 334 -4
320 335 +4 4
321 336 5
322 337 record change 1/2 to '1.txt'? [Ynsfdaq?]
323 338 diff --git a/dir/a.txt b/dir/a.txt
324 339 1 hunks, 1 lines changed
325 340 examine changes to 'dir/a.txt'? [Ynsfdaq?]
326 341 @@ -1,4 +1,4 @@
327 342 -hello world
328 343 +hello world!
329 344
330 345 someone
331 346 up
332 347 record change 2/2 to 'dir/a.txt'? [Ynsfdaq?]
333 348
334 349 After qrecord b.patch 'tip'
335 350
336 351 $ hg tip -p
337 352 changeset: 2:b056198bf878
338 353 tag: b.patch
339 354 tag: qtip
340 355 tag: tip
341 356 user: test
342 357 date: Thu Jan 01 00:00:00 1970 +0000
343 358 summary: bbb
344 359
345 360 diff -r 5d1ca63427ee -r b056198bf878 1.txt
346 361 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
347 362 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
348 363 @@ -1,5 +1,5 @@
349 364 1
350 365 2 2
351 366 3
352 367 -4
353 368 +4 4
354 369 5
355 370 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
356 371 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
357 372 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
358 373 @@ -1,4 +1,4 @@
359 374 -hello world
360 375 +hello world!
361 376
362 377 someone
363 378 up
364 379
365 380
366 381 After qrecord b.patch 'diff'
367 382
368 383 $ hg diff --nodates
General Comments 0
You need to be logged in to leave comments. Login now