##// END OF EJS Templates
dispatch: split global error handling out so it can be reused...
Jun Wu -
r29761:39149b60 default
parent child Browse files
Show More
@@ -1,1077 +1,1087 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching 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, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import atexit
10 import atexit
11 import difflib
11 import difflib
12 import errno
12 import errno
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import shlex
16 import shlex
17 import signal
17 import signal
18 import socket
18 import socket
19 import sys
19 import sys
20 import time
20 import time
21 import traceback
21 import traceback
22
22
23
23
24 from .i18n import _
24 from .i18n import _
25
25
26 from . import (
26 from . import (
27 cmdutil,
27 cmdutil,
28 commands,
28 commands,
29 demandimport,
29 demandimport,
30 encoding,
30 encoding,
31 error,
31 error,
32 extensions,
32 extensions,
33 fancyopts,
33 fancyopts,
34 fileset,
34 fileset,
35 hg,
35 hg,
36 hook,
36 hook,
37 revset,
37 revset,
38 templatefilters,
38 templatefilters,
39 templatekw,
39 templatekw,
40 templater,
40 templater,
41 ui as uimod,
41 ui as uimod,
42 util,
42 util,
43 )
43 )
44
44
45 class request(object):
45 class request(object):
46 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
46 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
47 ferr=None):
47 ferr=None):
48 self.args = args
48 self.args = args
49 self.ui = ui
49 self.ui = ui
50 self.repo = repo
50 self.repo = repo
51
51
52 # input/output/error streams
52 # input/output/error streams
53 self.fin = fin
53 self.fin = fin
54 self.fout = fout
54 self.fout = fout
55 self.ferr = ferr
55 self.ferr = ferr
56
56
57 def run():
57 def run():
58 "run the command in sys.argv"
58 "run the command in sys.argv"
59 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
59 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
60
60
61 def _getsimilar(symbols, value):
61 def _getsimilar(symbols, value):
62 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
62 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
63 # The cutoff for similarity here is pretty arbitrary. It should
63 # The cutoff for similarity here is pretty arbitrary. It should
64 # probably be investigated and tweaked.
64 # probably be investigated and tweaked.
65 return [s for s in symbols if sim(s) > 0.6]
65 return [s for s in symbols if sim(s) > 0.6]
66
66
67 def _reportsimilar(write, similar):
67 def _reportsimilar(write, similar):
68 if len(similar) == 1:
68 if len(similar) == 1:
69 write(_("(did you mean %s?)\n") % similar[0])
69 write(_("(did you mean %s?)\n") % similar[0])
70 elif similar:
70 elif similar:
71 ss = ", ".join(sorted(similar))
71 ss = ", ".join(sorted(similar))
72 write(_("(did you mean one of %s?)\n") % ss)
72 write(_("(did you mean one of %s?)\n") % ss)
73
73
74 def _formatparse(write, inst):
74 def _formatparse(write, inst):
75 similar = []
75 similar = []
76 if isinstance(inst, error.UnknownIdentifier):
76 if isinstance(inst, error.UnknownIdentifier):
77 # make sure to check fileset first, as revset can invoke fileset
77 # make sure to check fileset first, as revset can invoke fileset
78 similar = _getsimilar(inst.symbols, inst.function)
78 similar = _getsimilar(inst.symbols, inst.function)
79 if len(inst.args) > 1:
79 if len(inst.args) > 1:
80 write(_("hg: parse error at %s: %s\n") %
80 write(_("hg: parse error at %s: %s\n") %
81 (inst.args[1], inst.args[0]))
81 (inst.args[1], inst.args[0]))
82 if (inst.args[0][0] == ' '):
82 if (inst.args[0][0] == ' '):
83 write(_("unexpected leading whitespace\n"))
83 write(_("unexpected leading whitespace\n"))
84 else:
84 else:
85 write(_("hg: parse error: %s\n") % inst.args[0])
85 write(_("hg: parse error: %s\n") % inst.args[0])
86 _reportsimilar(write, similar)
86 _reportsimilar(write, similar)
87 if inst.hint:
87 if inst.hint:
88 write(_("(%s)\n") % inst.hint)
88 write(_("(%s)\n") % inst.hint)
89
89
90 def dispatch(req):
90 def dispatch(req):
91 "run the command specified in req.args"
91 "run the command specified in req.args"
92 if req.ferr:
92 if req.ferr:
93 ferr = req.ferr
93 ferr = req.ferr
94 elif req.ui:
94 elif req.ui:
95 ferr = req.ui.ferr
95 ferr = req.ui.ferr
96 else:
96 else:
97 ferr = sys.stderr
97 ferr = sys.stderr
98
98
99 try:
99 try:
100 if not req.ui:
100 if not req.ui:
101 req.ui = uimod.ui()
101 req.ui = uimod.ui()
102 if '--traceback' in req.args:
102 if '--traceback' in req.args:
103 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
103 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
104
104
105 # set ui streams from the request
105 # set ui streams from the request
106 if req.fin:
106 if req.fin:
107 req.ui.fin = req.fin
107 req.ui.fin = req.fin
108 if req.fout:
108 if req.fout:
109 req.ui.fout = req.fout
109 req.ui.fout = req.fout
110 if req.ferr:
110 if req.ferr:
111 req.ui.ferr = req.ferr
111 req.ui.ferr = req.ferr
112 except error.Abort as inst:
112 except error.Abort as inst:
113 ferr.write(_("abort: %s\n") % inst)
113 ferr.write(_("abort: %s\n") % inst)
114 if inst.hint:
114 if inst.hint:
115 ferr.write(_("(%s)\n") % inst.hint)
115 ferr.write(_("(%s)\n") % inst.hint)
116 return -1
116 return -1
117 except error.ParseError as inst:
117 except error.ParseError as inst:
118 _formatparse(ferr.write, inst)
118 _formatparse(ferr.write, inst)
119 return -1
119 return -1
120
120
121 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
121 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
122 starttime = time.time()
122 starttime = time.time()
123 ret = None
123 ret = None
124 try:
124 try:
125 ret = _runcatch(req)
125 ret = _runcatch(req)
126 except KeyboardInterrupt:
126 except KeyboardInterrupt:
127 try:
127 try:
128 req.ui.warn(_("interrupted!\n"))
128 req.ui.warn(_("interrupted!\n"))
129 except IOError as inst:
129 except IOError as inst:
130 if inst.errno != errno.EPIPE:
130 if inst.errno != errno.EPIPE:
131 raise
131 raise
132 ret = -1
132 ret = -1
133 finally:
133 finally:
134 duration = time.time() - starttime
134 duration = time.time() - starttime
135 req.ui.flush()
135 req.ui.flush()
136 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
136 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
137 msg, ret or 0, duration)
137 msg, ret or 0, duration)
138 return ret
138 return ret
139
139
140 def _runcatch(req):
140 def _runcatch(req):
141 def catchterm(*args):
141 def catchterm(*args):
142 raise error.SignalInterrupt
142 raise error.SignalInterrupt
143
143
144 ui = req.ui
144 ui = req.ui
145 try:
145 try:
146 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
146 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
147 num = getattr(signal, name, None)
147 num = getattr(signal, name, None)
148 if num:
148 if num:
149 signal.signal(num, catchterm)
149 signal.signal(num, catchterm)
150 except ValueError:
150 except ValueError:
151 pass # happens if called in a thread
151 pass # happens if called in a thread
152
152
153 try:
153 def _runcatchfunc():
154 try:
154 try:
155 debugger = 'pdb'
155 debugger = 'pdb'
156 debugtrace = {
156 debugtrace = {
157 'pdb' : pdb.set_trace
157 'pdb' : pdb.set_trace
158 }
158 }
159 debugmortem = {
159 debugmortem = {
160 'pdb' : pdb.post_mortem
160 'pdb' : pdb.post_mortem
161 }
161 }
162
162
163 # read --config before doing anything else
163 # read --config before doing anything else
164 # (e.g. to change trust settings for reading .hg/hgrc)
164 # (e.g. to change trust settings for reading .hg/hgrc)
165 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
165 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
166
166
167 if req.repo:
167 if req.repo:
168 # copy configs that were passed on the cmdline (--config) to
168 # copy configs that were passed on the cmdline (--config) to
169 # the repo ui
169 # the repo ui
170 for sec, name, val in cfgs:
170 for sec, name, val in cfgs:
171 req.repo.ui.setconfig(sec, name, val, source='--config')
171 req.repo.ui.setconfig(sec, name, val, source='--config')
172
172
173 # developer config: ui.debugger
173 # developer config: ui.debugger
174 debugger = ui.config("ui", "debugger")
174 debugger = ui.config("ui", "debugger")
175 debugmod = pdb
175 debugmod = pdb
176 if not debugger or ui.plain():
176 if not debugger or ui.plain():
177 # if we are in HGPLAIN mode, then disable custom debugging
177 # if we are in HGPLAIN mode, then disable custom debugging
178 debugger = 'pdb'
178 debugger = 'pdb'
179 elif '--debugger' in req.args:
179 elif '--debugger' in req.args:
180 # This import can be slow for fancy debuggers, so only
180 # This import can be slow for fancy debuggers, so only
181 # do it when absolutely necessary, i.e. when actual
181 # do it when absolutely necessary, i.e. when actual
182 # debugging has been requested
182 # debugging has been requested
183 with demandimport.deactivated():
183 with demandimport.deactivated():
184 try:
184 try:
185 debugmod = __import__(debugger)
185 debugmod = __import__(debugger)
186 except ImportError:
186 except ImportError:
187 pass # Leave debugmod = pdb
187 pass # Leave debugmod = pdb
188
188
189 debugtrace[debugger] = debugmod.set_trace
189 debugtrace[debugger] = debugmod.set_trace
190 debugmortem[debugger] = debugmod.post_mortem
190 debugmortem[debugger] = debugmod.post_mortem
191
191
192 # enter the debugger before command execution
192 # enter the debugger before command execution
193 if '--debugger' in req.args:
193 if '--debugger' in req.args:
194 ui.warn(_("entering debugger - "
194 ui.warn(_("entering debugger - "
195 "type c to continue starting hg or h for help\n"))
195 "type c to continue starting hg or h for help\n"))
196
196
197 if (debugger != 'pdb' and
197 if (debugger != 'pdb' and
198 debugtrace[debugger] == debugtrace['pdb']):
198 debugtrace[debugger] == debugtrace['pdb']):
199 ui.warn(_("%s debugger specified "
199 ui.warn(_("%s debugger specified "
200 "but its module was not found\n") % debugger)
200 "but its module was not found\n") % debugger)
201 with demandimport.deactivated():
201 with demandimport.deactivated():
202 debugtrace[debugger]()
202 debugtrace[debugger]()
203 try:
203 try:
204 return _dispatch(req)
204 return _dispatch(req)
205 finally:
205 finally:
206 ui.flush()
206 ui.flush()
207 except: # re-raises
207 except: # re-raises
208 # enter the debugger when we hit an exception
208 # enter the debugger when we hit an exception
209 if '--debugger' in req.args:
209 if '--debugger' in req.args:
210 traceback.print_exc()
210 traceback.print_exc()
211 debugmortem[debugger](sys.exc_info()[2])
211 debugmortem[debugger](sys.exc_info()[2])
212 ui.traceback()
212 ui.traceback()
213 raise
213 raise
214
214
215 return callcatch(ui, _runcatchfunc)
216
217 def callcatch(ui, func):
218 """call func() with global exception handling
219
220 return func() if no exception happens. otherwise do some error handling
221 and return an exit code accordingly.
222 """
223 try:
224 return func()
215 # Global exception handling, alphabetically
225 # Global exception handling, alphabetically
216 # Mercurial-specific first, followed by built-in and library exceptions
226 # Mercurial-specific first, followed by built-in and library exceptions
217 except error.AmbiguousCommand as inst:
227 except error.AmbiguousCommand as inst:
218 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
228 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
219 (inst.args[0], " ".join(inst.args[1])))
229 (inst.args[0], " ".join(inst.args[1])))
220 except error.ParseError as inst:
230 except error.ParseError as inst:
221 _formatparse(ui.warn, inst)
231 _formatparse(ui.warn, inst)
222 return -1
232 return -1
223 except error.LockHeld as inst:
233 except error.LockHeld as inst:
224 if inst.errno == errno.ETIMEDOUT:
234 if inst.errno == errno.ETIMEDOUT:
225 reason = _('timed out waiting for lock held by %s') % inst.locker
235 reason = _('timed out waiting for lock held by %s') % inst.locker
226 else:
236 else:
227 reason = _('lock held by %s') % inst.locker
237 reason = _('lock held by %s') % inst.locker
228 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
238 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
229 except error.LockUnavailable as inst:
239 except error.LockUnavailable as inst:
230 ui.warn(_("abort: could not lock %s: %s\n") %
240 ui.warn(_("abort: could not lock %s: %s\n") %
231 (inst.desc or inst.filename, inst.strerror))
241 (inst.desc or inst.filename, inst.strerror))
232 except error.CommandError as inst:
242 except error.CommandError as inst:
233 if inst.args[0]:
243 if inst.args[0]:
234 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
244 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
235 commands.help_(ui, inst.args[0], full=False, command=True)
245 commands.help_(ui, inst.args[0], full=False, command=True)
236 else:
246 else:
237 ui.warn(_("hg: %s\n") % inst.args[1])
247 ui.warn(_("hg: %s\n") % inst.args[1])
238 commands.help_(ui, 'shortlist')
248 commands.help_(ui, 'shortlist')
239 except error.OutOfBandError as inst:
249 except error.OutOfBandError as inst:
240 if inst.args:
250 if inst.args:
241 msg = _("abort: remote error:\n")
251 msg = _("abort: remote error:\n")
242 else:
252 else:
243 msg = _("abort: remote error\n")
253 msg = _("abort: remote error\n")
244 ui.warn(msg)
254 ui.warn(msg)
245 if inst.args:
255 if inst.args:
246 ui.warn(''.join(inst.args))
256 ui.warn(''.join(inst.args))
247 if inst.hint:
257 if inst.hint:
248 ui.warn('(%s)\n' % inst.hint)
258 ui.warn('(%s)\n' % inst.hint)
249 except error.RepoError as inst:
259 except error.RepoError as inst:
250 ui.warn(_("abort: %s!\n") % inst)
260 ui.warn(_("abort: %s!\n") % inst)
251 if inst.hint:
261 if inst.hint:
252 ui.warn(_("(%s)\n") % inst.hint)
262 ui.warn(_("(%s)\n") % inst.hint)
253 except error.ResponseError as inst:
263 except error.ResponseError as inst:
254 ui.warn(_("abort: %s") % inst.args[0])
264 ui.warn(_("abort: %s") % inst.args[0])
255 if not isinstance(inst.args[1], basestring):
265 if not isinstance(inst.args[1], basestring):
256 ui.warn(" %r\n" % (inst.args[1],))
266 ui.warn(" %r\n" % (inst.args[1],))
257 elif not inst.args[1]:
267 elif not inst.args[1]:
258 ui.warn(_(" empty string\n"))
268 ui.warn(_(" empty string\n"))
259 else:
269 else:
260 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
270 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
261 except error.CensoredNodeError as inst:
271 except error.CensoredNodeError as inst:
262 ui.warn(_("abort: file censored %s!\n") % inst)
272 ui.warn(_("abort: file censored %s!\n") % inst)
263 except error.RevlogError as inst:
273 except error.RevlogError as inst:
264 ui.warn(_("abort: %s!\n") % inst)
274 ui.warn(_("abort: %s!\n") % inst)
265 except error.SignalInterrupt:
275 except error.SignalInterrupt:
266 ui.warn(_("killed!\n"))
276 ui.warn(_("killed!\n"))
267 except error.UnknownCommand as inst:
277 except error.UnknownCommand as inst:
268 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
278 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
269 try:
279 try:
270 # check if the command is in a disabled extension
280 # check if the command is in a disabled extension
271 # (but don't check for extensions themselves)
281 # (but don't check for extensions themselves)
272 commands.help_(ui, inst.args[0], unknowncmd=True)
282 commands.help_(ui, inst.args[0], unknowncmd=True)
273 except (error.UnknownCommand, error.Abort):
283 except (error.UnknownCommand, error.Abort):
274 suggested = False
284 suggested = False
275 if len(inst.args) == 2:
285 if len(inst.args) == 2:
276 sim = _getsimilar(inst.args[1], inst.args[0])
286 sim = _getsimilar(inst.args[1], inst.args[0])
277 if sim:
287 if sim:
278 _reportsimilar(ui.warn, sim)
288 _reportsimilar(ui.warn, sim)
279 suggested = True
289 suggested = True
280 if not suggested:
290 if not suggested:
281 commands.help_(ui, 'shortlist')
291 commands.help_(ui, 'shortlist')
282 except error.InterventionRequired as inst:
292 except error.InterventionRequired as inst:
283 ui.warn("%s\n" % inst)
293 ui.warn("%s\n" % inst)
284 if inst.hint:
294 if inst.hint:
285 ui.warn(_("(%s)\n") % inst.hint)
295 ui.warn(_("(%s)\n") % inst.hint)
286 return 1
296 return 1
287 except error.Abort as inst:
297 except error.Abort as inst:
288 ui.warn(_("abort: %s\n") % inst)
298 ui.warn(_("abort: %s\n") % inst)
289 if inst.hint:
299 if inst.hint:
290 ui.warn(_("(%s)\n") % inst.hint)
300 ui.warn(_("(%s)\n") % inst.hint)
291 except ImportError as inst:
301 except ImportError as inst:
292 ui.warn(_("abort: %s!\n") % inst)
302 ui.warn(_("abort: %s!\n") % inst)
293 m = str(inst).split()[-1]
303 m = str(inst).split()[-1]
294 if m in "mpatch bdiff".split():
304 if m in "mpatch bdiff".split():
295 ui.warn(_("(did you forget to compile extensions?)\n"))
305 ui.warn(_("(did you forget to compile extensions?)\n"))
296 elif m in "zlib".split():
306 elif m in "zlib".split():
297 ui.warn(_("(is your Python install correct?)\n"))
307 ui.warn(_("(is your Python install correct?)\n"))
298 except IOError as inst:
308 except IOError as inst:
299 if util.safehasattr(inst, "code"):
309 if util.safehasattr(inst, "code"):
300 ui.warn(_("abort: %s\n") % inst)
310 ui.warn(_("abort: %s\n") % inst)
301 elif util.safehasattr(inst, "reason"):
311 elif util.safehasattr(inst, "reason"):
302 try: # usually it is in the form (errno, strerror)
312 try: # usually it is in the form (errno, strerror)
303 reason = inst.reason.args[1]
313 reason = inst.reason.args[1]
304 except (AttributeError, IndexError):
314 except (AttributeError, IndexError):
305 # it might be anything, for example a string
315 # it might be anything, for example a string
306 reason = inst.reason
316 reason = inst.reason
307 if isinstance(reason, unicode):
317 if isinstance(reason, unicode):
308 # SSLError of Python 2.7.9 contains a unicode
318 # SSLError of Python 2.7.9 contains a unicode
309 reason = reason.encode(encoding.encoding, 'replace')
319 reason = reason.encode(encoding.encoding, 'replace')
310 ui.warn(_("abort: error: %s\n") % reason)
320 ui.warn(_("abort: error: %s\n") % reason)
311 elif (util.safehasattr(inst, "args")
321 elif (util.safehasattr(inst, "args")
312 and inst.args and inst.args[0] == errno.EPIPE):
322 and inst.args and inst.args[0] == errno.EPIPE):
313 pass
323 pass
314 elif getattr(inst, "strerror", None):
324 elif getattr(inst, "strerror", None):
315 if getattr(inst, "filename", None):
325 if getattr(inst, "filename", None):
316 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
326 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
317 else:
327 else:
318 ui.warn(_("abort: %s\n") % inst.strerror)
328 ui.warn(_("abort: %s\n") % inst.strerror)
319 else:
329 else:
320 raise
330 raise
321 except OSError as inst:
331 except OSError as inst:
322 if getattr(inst, "filename", None) is not None:
332 if getattr(inst, "filename", None) is not None:
323 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
333 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
324 else:
334 else:
325 ui.warn(_("abort: %s\n") % inst.strerror)
335 ui.warn(_("abort: %s\n") % inst.strerror)
326 except KeyboardInterrupt:
336 except KeyboardInterrupt:
327 raise
337 raise
328 except MemoryError:
338 except MemoryError:
329 ui.warn(_("abort: out of memory\n"))
339 ui.warn(_("abort: out of memory\n"))
330 except SystemExit as inst:
340 except SystemExit as inst:
331 # Commands shouldn't sys.exit directly, but give a return code.
341 # Commands shouldn't sys.exit directly, but give a return code.
332 # Just in case catch this and and pass exit code to caller.
342 # Just in case catch this and and pass exit code to caller.
333 return inst.code
343 return inst.code
334 except socket.error as inst:
344 except socket.error as inst:
335 ui.warn(_("abort: %s\n") % inst.args[-1])
345 ui.warn(_("abort: %s\n") % inst.args[-1])
336 except: # perhaps re-raises
346 except: # perhaps re-raises
337 if not handlecommandexception(ui):
347 if not handlecommandexception(ui):
338 raise
348 raise
339
349
340 return -1
350 return -1
341
351
342 def aliasargs(fn, givenargs):
352 def aliasargs(fn, givenargs):
343 args = getattr(fn, 'args', [])
353 args = getattr(fn, 'args', [])
344 if args:
354 if args:
345 cmd = ' '.join(map(util.shellquote, args))
355 cmd = ' '.join(map(util.shellquote, args))
346
356
347 nums = []
357 nums = []
348 def replacer(m):
358 def replacer(m):
349 num = int(m.group(1)) - 1
359 num = int(m.group(1)) - 1
350 nums.append(num)
360 nums.append(num)
351 if num < len(givenargs):
361 if num < len(givenargs):
352 return givenargs[num]
362 return givenargs[num]
353 raise error.Abort(_('too few arguments for command alias'))
363 raise error.Abort(_('too few arguments for command alias'))
354 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
364 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
355 givenargs = [x for i, x in enumerate(givenargs)
365 givenargs = [x for i, x in enumerate(givenargs)
356 if i not in nums]
366 if i not in nums]
357 args = shlex.split(cmd)
367 args = shlex.split(cmd)
358 return args + givenargs
368 return args + givenargs
359
369
360 def aliasinterpolate(name, args, cmd):
370 def aliasinterpolate(name, args, cmd):
361 '''interpolate args into cmd for shell aliases
371 '''interpolate args into cmd for shell aliases
362
372
363 This also handles $0, $@ and "$@".
373 This also handles $0, $@ and "$@".
364 '''
374 '''
365 # util.interpolate can't deal with "$@" (with quotes) because it's only
375 # util.interpolate can't deal with "$@" (with quotes) because it's only
366 # built to match prefix + patterns.
376 # built to match prefix + patterns.
367 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
377 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
368 replacemap['$0'] = name
378 replacemap['$0'] = name
369 replacemap['$$'] = '$'
379 replacemap['$$'] = '$'
370 replacemap['$@'] = ' '.join(args)
380 replacemap['$@'] = ' '.join(args)
371 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
381 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
372 # parameters, separated out into words. Emulate the same behavior here by
382 # parameters, separated out into words. Emulate the same behavior here by
373 # quoting the arguments individually. POSIX shells will then typically
383 # quoting the arguments individually. POSIX shells will then typically
374 # tokenize each argument into exactly one word.
384 # tokenize each argument into exactly one word.
375 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
385 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
376 # escape '\$' for regex
386 # escape '\$' for regex
377 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
387 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
378 r = re.compile(regex)
388 r = re.compile(regex)
379 return r.sub(lambda x: replacemap[x.group()], cmd)
389 return r.sub(lambda x: replacemap[x.group()], cmd)
380
390
381 class cmdalias(object):
391 class cmdalias(object):
382 def __init__(self, name, definition, cmdtable, source):
392 def __init__(self, name, definition, cmdtable, source):
383 self.name = self.cmd = name
393 self.name = self.cmd = name
384 self.cmdname = ''
394 self.cmdname = ''
385 self.definition = definition
395 self.definition = definition
386 self.fn = None
396 self.fn = None
387 self.givenargs = []
397 self.givenargs = []
388 self.opts = []
398 self.opts = []
389 self.help = ''
399 self.help = ''
390 self.badalias = None
400 self.badalias = None
391 self.unknowncmd = False
401 self.unknowncmd = False
392 self.source = source
402 self.source = source
393
403
394 try:
404 try:
395 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
405 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
396 for alias, e in cmdtable.iteritems():
406 for alias, e in cmdtable.iteritems():
397 if e is entry:
407 if e is entry:
398 self.cmd = alias
408 self.cmd = alias
399 break
409 break
400 self.shadows = True
410 self.shadows = True
401 except error.UnknownCommand:
411 except error.UnknownCommand:
402 self.shadows = False
412 self.shadows = False
403
413
404 if not self.definition:
414 if not self.definition:
405 self.badalias = _("no definition for alias '%s'") % self.name
415 self.badalias = _("no definition for alias '%s'") % self.name
406 return
416 return
407
417
408 if self.definition.startswith('!'):
418 if self.definition.startswith('!'):
409 self.shell = True
419 self.shell = True
410 def fn(ui, *args):
420 def fn(ui, *args):
411 env = {'HG_ARGS': ' '.join((self.name,) + args)}
421 env = {'HG_ARGS': ' '.join((self.name,) + args)}
412 def _checkvar(m):
422 def _checkvar(m):
413 if m.groups()[0] == '$':
423 if m.groups()[0] == '$':
414 return m.group()
424 return m.group()
415 elif int(m.groups()[0]) <= len(args):
425 elif int(m.groups()[0]) <= len(args):
416 return m.group()
426 return m.group()
417 else:
427 else:
418 ui.debug("No argument found for substitution "
428 ui.debug("No argument found for substitution "
419 "of %i variable in alias '%s' definition."
429 "of %i variable in alias '%s' definition."
420 % (int(m.groups()[0]), self.name))
430 % (int(m.groups()[0]), self.name))
421 return ''
431 return ''
422 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
432 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
423 cmd = aliasinterpolate(self.name, args, cmd)
433 cmd = aliasinterpolate(self.name, args, cmd)
424 return ui.system(cmd, environ=env)
434 return ui.system(cmd, environ=env)
425 self.fn = fn
435 self.fn = fn
426 return
436 return
427
437
428 try:
438 try:
429 args = shlex.split(self.definition)
439 args = shlex.split(self.definition)
430 except ValueError as inst:
440 except ValueError as inst:
431 self.badalias = (_("error in definition for alias '%s': %s")
441 self.badalias = (_("error in definition for alias '%s': %s")
432 % (self.name, inst))
442 % (self.name, inst))
433 return
443 return
434 self.cmdname = cmd = args.pop(0)
444 self.cmdname = cmd = args.pop(0)
435 self.givenargs = args
445 self.givenargs = args
436
446
437 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
447 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
438 if _earlygetopt([invalidarg], args):
448 if _earlygetopt([invalidarg], args):
439 self.badalias = (_("error in definition for alias '%s': %s may "
449 self.badalias = (_("error in definition for alias '%s': %s may "
440 "only be given on the command line")
450 "only be given on the command line")
441 % (self.name, invalidarg))
451 % (self.name, invalidarg))
442 return
452 return
443
453
444 try:
454 try:
445 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
455 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
446 if len(tableentry) > 2:
456 if len(tableentry) > 2:
447 self.fn, self.opts, self.help = tableentry
457 self.fn, self.opts, self.help = tableentry
448 else:
458 else:
449 self.fn, self.opts = tableentry
459 self.fn, self.opts = tableentry
450
460
451 if self.help.startswith("hg " + cmd):
461 if self.help.startswith("hg " + cmd):
452 # drop prefix in old-style help lines so hg shows the alias
462 # drop prefix in old-style help lines so hg shows the alias
453 self.help = self.help[4 + len(cmd):]
463 self.help = self.help[4 + len(cmd):]
454 self.__doc__ = self.fn.__doc__
464 self.__doc__ = self.fn.__doc__
455
465
456 except error.UnknownCommand:
466 except error.UnknownCommand:
457 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
467 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
458 % (self.name, cmd))
468 % (self.name, cmd))
459 self.unknowncmd = True
469 self.unknowncmd = True
460 except error.AmbiguousCommand:
470 except error.AmbiguousCommand:
461 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
471 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
462 % (self.name, cmd))
472 % (self.name, cmd))
463
473
464 @property
474 @property
465 def args(self):
475 def args(self):
466 args = map(util.expandpath, self.givenargs)
476 args = map(util.expandpath, self.givenargs)
467 return aliasargs(self.fn, args)
477 return aliasargs(self.fn, args)
468
478
469 def __getattr__(self, name):
479 def __getattr__(self, name):
470 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
480 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
471 if name not in adefaults:
481 if name not in adefaults:
472 raise AttributeError(name)
482 raise AttributeError(name)
473 if self.badalias or util.safehasattr(self, 'shell'):
483 if self.badalias or util.safehasattr(self, 'shell'):
474 return adefaults[name]
484 return adefaults[name]
475 return getattr(self.fn, name)
485 return getattr(self.fn, name)
476
486
477 def __call__(self, ui, *args, **opts):
487 def __call__(self, ui, *args, **opts):
478 if self.badalias:
488 if self.badalias:
479 hint = None
489 hint = None
480 if self.unknowncmd:
490 if self.unknowncmd:
481 try:
491 try:
482 # check if the command is in a disabled extension
492 # check if the command is in a disabled extension
483 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
493 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
484 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
494 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
485 except error.UnknownCommand:
495 except error.UnknownCommand:
486 pass
496 pass
487 raise error.Abort(self.badalias, hint=hint)
497 raise error.Abort(self.badalias, hint=hint)
488 if self.shadows:
498 if self.shadows:
489 ui.debug("alias '%s' shadows command '%s'\n" %
499 ui.debug("alias '%s' shadows command '%s'\n" %
490 (self.name, self.cmdname))
500 (self.name, self.cmdname))
491
501
492 if util.safehasattr(self, 'shell'):
502 if util.safehasattr(self, 'shell'):
493 return self.fn(ui, *args, **opts)
503 return self.fn(ui, *args, **opts)
494 else:
504 else:
495 try:
505 try:
496 return util.checksignature(self.fn)(ui, *args, **opts)
506 return util.checksignature(self.fn)(ui, *args, **opts)
497 except error.SignatureError:
507 except error.SignatureError:
498 args = ' '.join([self.cmdname] + self.args)
508 args = ' '.join([self.cmdname] + self.args)
499 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
509 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
500 raise
510 raise
501
511
502 def addaliases(ui, cmdtable):
512 def addaliases(ui, cmdtable):
503 # aliases are processed after extensions have been loaded, so they
513 # aliases are processed after extensions have been loaded, so they
504 # may use extension commands. Aliases can also use other alias definitions,
514 # may use extension commands. Aliases can also use other alias definitions,
505 # but only if they have been defined prior to the current definition.
515 # but only if they have been defined prior to the current definition.
506 for alias, definition in ui.configitems('alias'):
516 for alias, definition in ui.configitems('alias'):
507 source = ui.configsource('alias', alias)
517 source = ui.configsource('alias', alias)
508 aliasdef = cmdalias(alias, definition, cmdtable, source)
518 aliasdef = cmdalias(alias, definition, cmdtable, source)
509
519
510 try:
520 try:
511 olddef = cmdtable[aliasdef.cmd][0]
521 olddef = cmdtable[aliasdef.cmd][0]
512 if olddef.definition == aliasdef.definition:
522 if olddef.definition == aliasdef.definition:
513 continue
523 continue
514 except (KeyError, AttributeError):
524 except (KeyError, AttributeError):
515 # definition might not exist or it might not be a cmdalias
525 # definition might not exist or it might not be a cmdalias
516 pass
526 pass
517
527
518 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
528 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
519
529
520 def _parse(ui, args):
530 def _parse(ui, args):
521 options = {}
531 options = {}
522 cmdoptions = {}
532 cmdoptions = {}
523
533
524 try:
534 try:
525 args = fancyopts.fancyopts(args, commands.globalopts, options)
535 args = fancyopts.fancyopts(args, commands.globalopts, options)
526 except fancyopts.getopt.GetoptError as inst:
536 except fancyopts.getopt.GetoptError as inst:
527 raise error.CommandError(None, inst)
537 raise error.CommandError(None, inst)
528
538
529 if args:
539 if args:
530 cmd, args = args[0], args[1:]
540 cmd, args = args[0], args[1:]
531 aliases, entry = cmdutil.findcmd(cmd, commands.table,
541 aliases, entry = cmdutil.findcmd(cmd, commands.table,
532 ui.configbool("ui", "strict"))
542 ui.configbool("ui", "strict"))
533 cmd = aliases[0]
543 cmd = aliases[0]
534 args = aliasargs(entry[0], args)
544 args = aliasargs(entry[0], args)
535 defaults = ui.config("defaults", cmd)
545 defaults = ui.config("defaults", cmd)
536 if defaults:
546 if defaults:
537 args = map(util.expandpath, shlex.split(defaults)) + args
547 args = map(util.expandpath, shlex.split(defaults)) + args
538 c = list(entry[1])
548 c = list(entry[1])
539 else:
549 else:
540 cmd = None
550 cmd = None
541 c = []
551 c = []
542
552
543 # combine global options into local
553 # combine global options into local
544 for o in commands.globalopts:
554 for o in commands.globalopts:
545 c.append((o[0], o[1], options[o[1]], o[3]))
555 c.append((o[0], o[1], options[o[1]], o[3]))
546
556
547 try:
557 try:
548 args = fancyopts.fancyopts(args, c, cmdoptions, True)
558 args = fancyopts.fancyopts(args, c, cmdoptions, True)
549 except fancyopts.getopt.GetoptError as inst:
559 except fancyopts.getopt.GetoptError as inst:
550 raise error.CommandError(cmd, inst)
560 raise error.CommandError(cmd, inst)
551
561
552 # separate global options back out
562 # separate global options back out
553 for o in commands.globalopts:
563 for o in commands.globalopts:
554 n = o[1]
564 n = o[1]
555 options[n] = cmdoptions[n]
565 options[n] = cmdoptions[n]
556 del cmdoptions[n]
566 del cmdoptions[n]
557
567
558 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
568 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
559
569
560 def _parseconfig(ui, config):
570 def _parseconfig(ui, config):
561 """parse the --config options from the command line"""
571 """parse the --config options from the command line"""
562 configs = []
572 configs = []
563
573
564 for cfg in config:
574 for cfg in config:
565 try:
575 try:
566 name, value = [cfgelem.strip()
576 name, value = [cfgelem.strip()
567 for cfgelem in cfg.split('=', 1)]
577 for cfgelem in cfg.split('=', 1)]
568 section, name = name.split('.', 1)
578 section, name = name.split('.', 1)
569 if not section or not name:
579 if not section or not name:
570 raise IndexError
580 raise IndexError
571 ui.setconfig(section, name, value, '--config')
581 ui.setconfig(section, name, value, '--config')
572 configs.append((section, name, value))
582 configs.append((section, name, value))
573 except (IndexError, ValueError):
583 except (IndexError, ValueError):
574 raise error.Abort(_('malformed --config option: %r '
584 raise error.Abort(_('malformed --config option: %r '
575 '(use --config section.name=value)') % cfg)
585 '(use --config section.name=value)') % cfg)
576
586
577 return configs
587 return configs
578
588
579 def _earlygetopt(aliases, args):
589 def _earlygetopt(aliases, args):
580 """Return list of values for an option (or aliases).
590 """Return list of values for an option (or aliases).
581
591
582 The values are listed in the order they appear in args.
592 The values are listed in the order they appear in args.
583 The options and values are removed from args.
593 The options and values are removed from args.
584
594
585 >>> args = ['x', '--cwd', 'foo', 'y']
595 >>> args = ['x', '--cwd', 'foo', 'y']
586 >>> _earlygetopt(['--cwd'], args), args
596 >>> _earlygetopt(['--cwd'], args), args
587 (['foo'], ['x', 'y'])
597 (['foo'], ['x', 'y'])
588
598
589 >>> args = ['x', '--cwd=bar', 'y']
599 >>> args = ['x', '--cwd=bar', 'y']
590 >>> _earlygetopt(['--cwd'], args), args
600 >>> _earlygetopt(['--cwd'], args), args
591 (['bar'], ['x', 'y'])
601 (['bar'], ['x', 'y'])
592
602
593 >>> args = ['x', '-R', 'foo', 'y']
603 >>> args = ['x', '-R', 'foo', 'y']
594 >>> _earlygetopt(['-R'], args), args
604 >>> _earlygetopt(['-R'], args), args
595 (['foo'], ['x', 'y'])
605 (['foo'], ['x', 'y'])
596
606
597 >>> args = ['x', '-Rbar', 'y']
607 >>> args = ['x', '-Rbar', 'y']
598 >>> _earlygetopt(['-R'], args), args
608 >>> _earlygetopt(['-R'], args), args
599 (['bar'], ['x', 'y'])
609 (['bar'], ['x', 'y'])
600 """
610 """
601 try:
611 try:
602 argcount = args.index("--")
612 argcount = args.index("--")
603 except ValueError:
613 except ValueError:
604 argcount = len(args)
614 argcount = len(args)
605 shortopts = [opt for opt in aliases if len(opt) == 2]
615 shortopts = [opt for opt in aliases if len(opt) == 2]
606 values = []
616 values = []
607 pos = 0
617 pos = 0
608 while pos < argcount:
618 while pos < argcount:
609 fullarg = arg = args[pos]
619 fullarg = arg = args[pos]
610 equals = arg.find('=')
620 equals = arg.find('=')
611 if equals > -1:
621 if equals > -1:
612 arg = arg[:equals]
622 arg = arg[:equals]
613 if arg in aliases:
623 if arg in aliases:
614 del args[pos]
624 del args[pos]
615 if equals > -1:
625 if equals > -1:
616 values.append(fullarg[equals + 1:])
626 values.append(fullarg[equals + 1:])
617 argcount -= 1
627 argcount -= 1
618 else:
628 else:
619 if pos + 1 >= argcount:
629 if pos + 1 >= argcount:
620 # ignore and let getopt report an error if there is no value
630 # ignore and let getopt report an error if there is no value
621 break
631 break
622 values.append(args.pop(pos))
632 values.append(args.pop(pos))
623 argcount -= 2
633 argcount -= 2
624 elif arg[:2] in shortopts:
634 elif arg[:2] in shortopts:
625 # short option can have no following space, e.g. hg log -Rfoo
635 # short option can have no following space, e.g. hg log -Rfoo
626 values.append(args.pop(pos)[2:])
636 values.append(args.pop(pos)[2:])
627 argcount -= 1
637 argcount -= 1
628 else:
638 else:
629 pos += 1
639 pos += 1
630 return values
640 return values
631
641
632 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
642 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
633 # run pre-hook, and abort if it fails
643 # run pre-hook, and abort if it fails
634 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
644 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
635 pats=cmdpats, opts=cmdoptions)
645 pats=cmdpats, opts=cmdoptions)
636 try:
646 try:
637 ret = _runcommand(ui, options, cmd, d)
647 ret = _runcommand(ui, options, cmd, d)
638 # run post-hook, passing command result
648 # run post-hook, passing command result
639 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
649 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
640 result=ret, pats=cmdpats, opts=cmdoptions)
650 result=ret, pats=cmdpats, opts=cmdoptions)
641 except Exception:
651 except Exception:
642 # run failure hook and re-raise
652 # run failure hook and re-raise
643 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
653 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
644 pats=cmdpats, opts=cmdoptions)
654 pats=cmdpats, opts=cmdoptions)
645 raise
655 raise
646 return ret
656 return ret
647
657
648 def _getlocal(ui, rpath, wd=None):
658 def _getlocal(ui, rpath, wd=None):
649 """Return (path, local ui object) for the given target path.
659 """Return (path, local ui object) for the given target path.
650
660
651 Takes paths in [cwd]/.hg/hgrc into account."
661 Takes paths in [cwd]/.hg/hgrc into account."
652 """
662 """
653 if wd is None:
663 if wd is None:
654 try:
664 try:
655 wd = os.getcwd()
665 wd = os.getcwd()
656 except OSError as e:
666 except OSError as e:
657 raise error.Abort(_("error getting current working directory: %s") %
667 raise error.Abort(_("error getting current working directory: %s") %
658 e.strerror)
668 e.strerror)
659 path = cmdutil.findrepo(wd) or ""
669 path = cmdutil.findrepo(wd) or ""
660 if not path:
670 if not path:
661 lui = ui
671 lui = ui
662 else:
672 else:
663 lui = ui.copy()
673 lui = ui.copy()
664 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
674 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
665
675
666 if rpath and rpath[-1]:
676 if rpath and rpath[-1]:
667 path = lui.expandpath(rpath[-1])
677 path = lui.expandpath(rpath[-1])
668 lui = ui.copy()
678 lui = ui.copy()
669 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
679 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
670
680
671 return path, lui
681 return path, lui
672
682
673 def _checkshellalias(lui, ui, args):
683 def _checkshellalias(lui, ui, args):
674 """Return the function to run the shell alias, if it is required"""
684 """Return the function to run the shell alias, if it is required"""
675 options = {}
685 options = {}
676
686
677 try:
687 try:
678 args = fancyopts.fancyopts(args, commands.globalopts, options)
688 args = fancyopts.fancyopts(args, commands.globalopts, options)
679 except fancyopts.getopt.GetoptError:
689 except fancyopts.getopt.GetoptError:
680 return
690 return
681
691
682 if not args:
692 if not args:
683 return
693 return
684
694
685 cmdtable = commands.table
695 cmdtable = commands.table
686
696
687 cmd = args[0]
697 cmd = args[0]
688 try:
698 try:
689 strict = ui.configbool("ui", "strict")
699 strict = ui.configbool("ui", "strict")
690 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
700 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
691 except (error.AmbiguousCommand, error.UnknownCommand):
701 except (error.AmbiguousCommand, error.UnknownCommand):
692 return
702 return
693
703
694 cmd = aliases[0]
704 cmd = aliases[0]
695 fn = entry[0]
705 fn = entry[0]
696
706
697 if cmd and util.safehasattr(fn, 'shell'):
707 if cmd and util.safehasattr(fn, 'shell'):
698 d = lambda: fn(ui, *args[1:])
708 d = lambda: fn(ui, *args[1:])
699 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
709 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
700 [], {})
710 [], {})
701
711
702 def _cmdattr(ui, cmd, func, attr):
712 def _cmdattr(ui, cmd, func, attr):
703 try:
713 try:
704 return getattr(func, attr)
714 return getattr(func, attr)
705 except AttributeError:
715 except AttributeError:
706 ui.deprecwarn("missing attribute '%s', use @command decorator "
716 ui.deprecwarn("missing attribute '%s', use @command decorator "
707 "to register '%s'" % (attr, cmd), '3.8')
717 "to register '%s'" % (attr, cmd), '3.8')
708 return False
718 return False
709
719
710 _loaded = set()
720 _loaded = set()
711
721
712 # list of (objname, loadermod, loadername) tuple:
722 # list of (objname, loadermod, loadername) tuple:
713 # - objname is the name of an object in extension module, from which
723 # - objname is the name of an object in extension module, from which
714 # extra information is loaded
724 # extra information is loaded
715 # - loadermod is the module where loader is placed
725 # - loadermod is the module where loader is placed
716 # - loadername is the name of the function, which takes (ui, extensionname,
726 # - loadername is the name of the function, which takes (ui, extensionname,
717 # extraobj) arguments
727 # extraobj) arguments
718 extraloaders = [
728 extraloaders = [
719 ('cmdtable', commands, 'loadcmdtable'),
729 ('cmdtable', commands, 'loadcmdtable'),
720 ('filesetpredicate', fileset, 'loadpredicate'),
730 ('filesetpredicate', fileset, 'loadpredicate'),
721 ('revsetpredicate', revset, 'loadpredicate'),
731 ('revsetpredicate', revset, 'loadpredicate'),
722 ('templatefilter', templatefilters, 'loadfilter'),
732 ('templatefilter', templatefilters, 'loadfilter'),
723 ('templatefunc', templater, 'loadfunction'),
733 ('templatefunc', templater, 'loadfunction'),
724 ('templatekeyword', templatekw, 'loadkeyword'),
734 ('templatekeyword', templatekw, 'loadkeyword'),
725 ]
735 ]
726
736
727 def _dispatch(req):
737 def _dispatch(req):
728 args = req.args
738 args = req.args
729 ui = req.ui
739 ui = req.ui
730
740
731 # check for cwd
741 # check for cwd
732 cwd = _earlygetopt(['--cwd'], args)
742 cwd = _earlygetopt(['--cwd'], args)
733 if cwd:
743 if cwd:
734 os.chdir(cwd[-1])
744 os.chdir(cwd[-1])
735
745
736 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
746 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
737 path, lui = _getlocal(ui, rpath)
747 path, lui = _getlocal(ui, rpath)
738
748
739 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
749 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
740 # reposetup. Programs like TortoiseHg will call _dispatch several
750 # reposetup. Programs like TortoiseHg will call _dispatch several
741 # times so we keep track of configured extensions in _loaded.
751 # times so we keep track of configured extensions in _loaded.
742 extensions.loadall(lui)
752 extensions.loadall(lui)
743 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
753 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
744 # Propagate any changes to lui.__class__ by extensions
754 # Propagate any changes to lui.__class__ by extensions
745 ui.__class__ = lui.__class__
755 ui.__class__ = lui.__class__
746
756
747 # (uisetup and extsetup are handled in extensions.loadall)
757 # (uisetup and extsetup are handled in extensions.loadall)
748
758
749 for name, module in exts:
759 for name, module in exts:
750 for objname, loadermod, loadername in extraloaders:
760 for objname, loadermod, loadername in extraloaders:
751 extraobj = getattr(module, objname, None)
761 extraobj = getattr(module, objname, None)
752 if extraobj is not None:
762 if extraobj is not None:
753 getattr(loadermod, loadername)(ui, name, extraobj)
763 getattr(loadermod, loadername)(ui, name, extraobj)
754 _loaded.add(name)
764 _loaded.add(name)
755
765
756 # (reposetup is handled in hg.repository)
766 # (reposetup is handled in hg.repository)
757
767
758 addaliases(lui, commands.table)
768 addaliases(lui, commands.table)
759
769
760 # All aliases and commands are completely defined, now.
770 # All aliases and commands are completely defined, now.
761 # Check abbreviation/ambiguity of shell alias.
771 # Check abbreviation/ambiguity of shell alias.
762 shellaliasfn = _checkshellalias(lui, ui, args)
772 shellaliasfn = _checkshellalias(lui, ui, args)
763 if shellaliasfn:
773 if shellaliasfn:
764 return shellaliasfn()
774 return shellaliasfn()
765
775
766 # check for fallback encoding
776 # check for fallback encoding
767 fallback = lui.config('ui', 'fallbackencoding')
777 fallback = lui.config('ui', 'fallbackencoding')
768 if fallback:
778 if fallback:
769 encoding.fallbackencoding = fallback
779 encoding.fallbackencoding = fallback
770
780
771 fullargs = args
781 fullargs = args
772 cmd, func, args, options, cmdoptions = _parse(lui, args)
782 cmd, func, args, options, cmdoptions = _parse(lui, args)
773
783
774 if options["config"]:
784 if options["config"]:
775 raise error.Abort(_("option --config may not be abbreviated!"))
785 raise error.Abort(_("option --config may not be abbreviated!"))
776 if options["cwd"]:
786 if options["cwd"]:
777 raise error.Abort(_("option --cwd may not be abbreviated!"))
787 raise error.Abort(_("option --cwd may not be abbreviated!"))
778 if options["repository"]:
788 if options["repository"]:
779 raise error.Abort(_(
789 raise error.Abort(_(
780 "option -R has to be separated from other options (e.g. not -qR) "
790 "option -R has to be separated from other options (e.g. not -qR) "
781 "and --repository may only be abbreviated as --repo!"))
791 "and --repository may only be abbreviated as --repo!"))
782
792
783 if options["encoding"]:
793 if options["encoding"]:
784 encoding.encoding = options["encoding"]
794 encoding.encoding = options["encoding"]
785 if options["encodingmode"]:
795 if options["encodingmode"]:
786 encoding.encodingmode = options["encodingmode"]
796 encoding.encodingmode = options["encodingmode"]
787 if options["time"]:
797 if options["time"]:
788 def get_times():
798 def get_times():
789 t = os.times()
799 t = os.times()
790 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
800 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
791 t = (t[0], t[1], t[2], t[3], time.clock())
801 t = (t[0], t[1], t[2], t[3], time.clock())
792 return t
802 return t
793 s = get_times()
803 s = get_times()
794 def print_time():
804 def print_time():
795 t = get_times()
805 t = get_times()
796 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
806 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
797 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
807 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
798 atexit.register(print_time)
808 atexit.register(print_time)
799
809
800 uis = set([ui, lui])
810 uis = set([ui, lui])
801
811
802 if req.repo:
812 if req.repo:
803 uis.add(req.repo.ui)
813 uis.add(req.repo.ui)
804
814
805 if options['verbose'] or options['debug'] or options['quiet']:
815 if options['verbose'] or options['debug'] or options['quiet']:
806 for opt in ('verbose', 'debug', 'quiet'):
816 for opt in ('verbose', 'debug', 'quiet'):
807 val = str(bool(options[opt]))
817 val = str(bool(options[opt]))
808 for ui_ in uis:
818 for ui_ in uis:
809 ui_.setconfig('ui', opt, val, '--' + opt)
819 ui_.setconfig('ui', opt, val, '--' + opt)
810
820
811 if options['traceback']:
821 if options['traceback']:
812 for ui_ in uis:
822 for ui_ in uis:
813 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
823 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
814
824
815 if options['noninteractive']:
825 if options['noninteractive']:
816 for ui_ in uis:
826 for ui_ in uis:
817 ui_.setconfig('ui', 'interactive', 'off', '-y')
827 ui_.setconfig('ui', 'interactive', 'off', '-y')
818
828
819 if cmdoptions.get('insecure', False):
829 if cmdoptions.get('insecure', False):
820 for ui_ in uis:
830 for ui_ in uis:
821 ui_.insecureconnections = True
831 ui_.insecureconnections = True
822
832
823 if options['version']:
833 if options['version']:
824 return commands.version_(ui)
834 return commands.version_(ui)
825 if options['help']:
835 if options['help']:
826 return commands.help_(ui, cmd, command=cmd is not None)
836 return commands.help_(ui, cmd, command=cmd is not None)
827 elif not cmd:
837 elif not cmd:
828 return commands.help_(ui, 'shortlist')
838 return commands.help_(ui, 'shortlist')
829
839
830 repo = None
840 repo = None
831 cmdpats = args[:]
841 cmdpats = args[:]
832 if not _cmdattr(ui, cmd, func, 'norepo'):
842 if not _cmdattr(ui, cmd, func, 'norepo'):
833 # use the repo from the request only if we don't have -R
843 # use the repo from the request only if we don't have -R
834 if not rpath and not cwd:
844 if not rpath and not cwd:
835 repo = req.repo
845 repo = req.repo
836
846
837 if repo:
847 if repo:
838 # set the descriptors of the repo ui to those of ui
848 # set the descriptors of the repo ui to those of ui
839 repo.ui.fin = ui.fin
849 repo.ui.fin = ui.fin
840 repo.ui.fout = ui.fout
850 repo.ui.fout = ui.fout
841 repo.ui.ferr = ui.ferr
851 repo.ui.ferr = ui.ferr
842 else:
852 else:
843 try:
853 try:
844 repo = hg.repository(ui, path=path)
854 repo = hg.repository(ui, path=path)
845 if not repo.local():
855 if not repo.local():
846 raise error.Abort(_("repository '%s' is not local") % path)
856 raise error.Abort(_("repository '%s' is not local") % path)
847 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
857 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
848 except error.RequirementError:
858 except error.RequirementError:
849 raise
859 raise
850 except error.RepoError:
860 except error.RepoError:
851 if rpath and rpath[-1]: # invalid -R path
861 if rpath and rpath[-1]: # invalid -R path
852 raise
862 raise
853 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
863 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
854 if (_cmdattr(ui, cmd, func, 'inferrepo') and
864 if (_cmdattr(ui, cmd, func, 'inferrepo') and
855 args and not path):
865 args and not path):
856 # try to infer -R from command args
866 # try to infer -R from command args
857 repos = map(cmdutil.findrepo, args)
867 repos = map(cmdutil.findrepo, args)
858 guess = repos[0]
868 guess = repos[0]
859 if guess and repos.count(guess) == len(repos):
869 if guess and repos.count(guess) == len(repos):
860 req.args = ['--repository', guess] + fullargs
870 req.args = ['--repository', guess] + fullargs
861 return _dispatch(req)
871 return _dispatch(req)
862 if not path:
872 if not path:
863 raise error.RepoError(_("no repository found in '%s'"
873 raise error.RepoError(_("no repository found in '%s'"
864 " (.hg not found)")
874 " (.hg not found)")
865 % os.getcwd())
875 % os.getcwd())
866 raise
876 raise
867 if repo:
877 if repo:
868 ui = repo.ui
878 ui = repo.ui
869 if options['hidden']:
879 if options['hidden']:
870 repo = repo.unfiltered()
880 repo = repo.unfiltered()
871 args.insert(0, repo)
881 args.insert(0, repo)
872 elif rpath:
882 elif rpath:
873 ui.warn(_("warning: --repository ignored\n"))
883 ui.warn(_("warning: --repository ignored\n"))
874
884
875 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
885 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
876 ui.log("command", '%s\n', msg)
886 ui.log("command", '%s\n', msg)
877 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
887 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
878 try:
888 try:
879 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
889 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
880 cmdpats, cmdoptions)
890 cmdpats, cmdoptions)
881 finally:
891 finally:
882 if repo and repo != req.repo:
892 if repo and repo != req.repo:
883 repo.close()
893 repo.close()
884
894
885 def lsprofile(ui, func, fp):
895 def lsprofile(ui, func, fp):
886 format = ui.config('profiling', 'format', default='text')
896 format = ui.config('profiling', 'format', default='text')
887 field = ui.config('profiling', 'sort', default='inlinetime')
897 field = ui.config('profiling', 'sort', default='inlinetime')
888 limit = ui.configint('profiling', 'limit', default=30)
898 limit = ui.configint('profiling', 'limit', default=30)
889 climit = ui.configint('profiling', 'nested', default=0)
899 climit = ui.configint('profiling', 'nested', default=0)
890
900
891 if format not in ['text', 'kcachegrind']:
901 if format not in ['text', 'kcachegrind']:
892 ui.warn(_("unrecognized profiling format '%s'"
902 ui.warn(_("unrecognized profiling format '%s'"
893 " - Ignored\n") % format)
903 " - Ignored\n") % format)
894 format = 'text'
904 format = 'text'
895
905
896 try:
906 try:
897 from . import lsprof
907 from . import lsprof
898 except ImportError:
908 except ImportError:
899 raise error.Abort(_(
909 raise error.Abort(_(
900 'lsprof not available - install from '
910 'lsprof not available - install from '
901 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
911 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
902 p = lsprof.Profiler()
912 p = lsprof.Profiler()
903 p.enable(subcalls=True)
913 p.enable(subcalls=True)
904 try:
914 try:
905 return func()
915 return func()
906 finally:
916 finally:
907 p.disable()
917 p.disable()
908
918
909 if format == 'kcachegrind':
919 if format == 'kcachegrind':
910 from . import lsprofcalltree
920 from . import lsprofcalltree
911 calltree = lsprofcalltree.KCacheGrind(p)
921 calltree = lsprofcalltree.KCacheGrind(p)
912 calltree.output(fp)
922 calltree.output(fp)
913 else:
923 else:
914 # format == 'text'
924 # format == 'text'
915 stats = lsprof.Stats(p.getstats())
925 stats = lsprof.Stats(p.getstats())
916 stats.sort(field)
926 stats.sort(field)
917 stats.pprint(limit=limit, file=fp, climit=climit)
927 stats.pprint(limit=limit, file=fp, climit=climit)
918
928
919 def flameprofile(ui, func, fp):
929 def flameprofile(ui, func, fp):
920 try:
930 try:
921 from flamegraph import flamegraph
931 from flamegraph import flamegraph
922 except ImportError:
932 except ImportError:
923 raise error.Abort(_(
933 raise error.Abort(_(
924 'flamegraph not available - install from '
934 'flamegraph not available - install from '
925 'https://github.com/evanhempel/python-flamegraph'))
935 'https://github.com/evanhempel/python-flamegraph'))
926 # developer config: profiling.freq
936 # developer config: profiling.freq
927 freq = ui.configint('profiling', 'freq', default=1000)
937 freq = ui.configint('profiling', 'freq', default=1000)
928 filter_ = None
938 filter_ = None
929 collapse_recursion = True
939 collapse_recursion = True
930 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
940 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
931 filter_, collapse_recursion)
941 filter_, collapse_recursion)
932 start_time = time.clock()
942 start_time = time.clock()
933 try:
943 try:
934 thread.start()
944 thread.start()
935 func()
945 func()
936 finally:
946 finally:
937 thread.stop()
947 thread.stop()
938 thread.join()
948 thread.join()
939 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
949 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
940 time.clock() - start_time, thread.num_frames(),
950 time.clock() - start_time, thread.num_frames(),
941 thread.num_frames(unique=True)))
951 thread.num_frames(unique=True)))
942
952
943
953
944 def statprofile(ui, func, fp):
954 def statprofile(ui, func, fp):
945 try:
955 try:
946 import statprof
956 import statprof
947 except ImportError:
957 except ImportError:
948 raise error.Abort(_(
958 raise error.Abort(_(
949 'statprof not available - install using "easy_install statprof"'))
959 'statprof not available - install using "easy_install statprof"'))
950
960
951 freq = ui.configint('profiling', 'freq', default=1000)
961 freq = ui.configint('profiling', 'freq', default=1000)
952 if freq > 0:
962 if freq > 0:
953 statprof.reset(freq)
963 statprof.reset(freq)
954 else:
964 else:
955 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
965 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
956
966
957 statprof.start()
967 statprof.start()
958 try:
968 try:
959 return func()
969 return func()
960 finally:
970 finally:
961 statprof.stop()
971 statprof.stop()
962 statprof.display(fp)
972 statprof.display(fp)
963
973
964 def _runcommand(ui, options, cmd, cmdfunc):
974 def _runcommand(ui, options, cmd, cmdfunc):
965 """Enables the profiler if applicable.
975 """Enables the profiler if applicable.
966
976
967 ``profiling.enabled`` - boolean config that enables or disables profiling
977 ``profiling.enabled`` - boolean config that enables or disables profiling
968 """
978 """
969 def checkargs():
979 def checkargs():
970 try:
980 try:
971 return cmdfunc()
981 return cmdfunc()
972 except error.SignatureError:
982 except error.SignatureError:
973 raise error.CommandError(cmd, _("invalid arguments"))
983 raise error.CommandError(cmd, _("invalid arguments"))
974
984
975 if options['profile'] or ui.configbool('profiling', 'enabled'):
985 if options['profile'] or ui.configbool('profiling', 'enabled'):
976 profiler = os.getenv('HGPROF')
986 profiler = os.getenv('HGPROF')
977 if profiler is None:
987 if profiler is None:
978 profiler = ui.config('profiling', 'type', default='ls')
988 profiler = ui.config('profiling', 'type', default='ls')
979 if profiler not in ('ls', 'stat', 'flame'):
989 if profiler not in ('ls', 'stat', 'flame'):
980 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
990 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
981 profiler = 'ls'
991 profiler = 'ls'
982
992
983 output = ui.config('profiling', 'output')
993 output = ui.config('profiling', 'output')
984
994
985 if output == 'blackbox':
995 if output == 'blackbox':
986 fp = util.stringio()
996 fp = util.stringio()
987 elif output:
997 elif output:
988 path = ui.expandpath(output)
998 path = ui.expandpath(output)
989 fp = open(path, 'wb')
999 fp = open(path, 'wb')
990 else:
1000 else:
991 fp = sys.stderr
1001 fp = sys.stderr
992
1002
993 try:
1003 try:
994 if profiler == 'ls':
1004 if profiler == 'ls':
995 return lsprofile(ui, checkargs, fp)
1005 return lsprofile(ui, checkargs, fp)
996 elif profiler == 'flame':
1006 elif profiler == 'flame':
997 return flameprofile(ui, checkargs, fp)
1007 return flameprofile(ui, checkargs, fp)
998 else:
1008 else:
999 return statprofile(ui, checkargs, fp)
1009 return statprofile(ui, checkargs, fp)
1000 finally:
1010 finally:
1001 if output:
1011 if output:
1002 if output == 'blackbox':
1012 if output == 'blackbox':
1003 val = "Profile:\n%s" % fp.getvalue()
1013 val = "Profile:\n%s" % fp.getvalue()
1004 # ui.log treats the input as a format string,
1014 # ui.log treats the input as a format string,
1005 # so we need to escape any % signs.
1015 # so we need to escape any % signs.
1006 val = val.replace('%', '%%')
1016 val = val.replace('%', '%%')
1007 ui.log('profile', val)
1017 ui.log('profile', val)
1008 fp.close()
1018 fp.close()
1009 else:
1019 else:
1010 return checkargs()
1020 return checkargs()
1011
1021
1012 def _exceptionwarning(ui):
1022 def _exceptionwarning(ui):
1013 """Produce a warning message for the current active exception"""
1023 """Produce a warning message for the current active exception"""
1014
1024
1015 # For compatibility checking, we discard the portion of the hg
1025 # For compatibility checking, we discard the portion of the hg
1016 # version after the + on the assumption that if a "normal
1026 # version after the + on the assumption that if a "normal
1017 # user" is running a build with a + in it the packager
1027 # user" is running a build with a + in it the packager
1018 # probably built from fairly close to a tag and anyone with a
1028 # probably built from fairly close to a tag and anyone with a
1019 # 'make local' copy of hg (where the version number can be out
1029 # 'make local' copy of hg (where the version number can be out
1020 # of date) will be clueful enough to notice the implausible
1030 # of date) will be clueful enough to notice the implausible
1021 # version number and try updating.
1031 # version number and try updating.
1022 ct = util.versiontuple(n=2)
1032 ct = util.versiontuple(n=2)
1023 worst = None, ct, ''
1033 worst = None, ct, ''
1024 if ui.config('ui', 'supportcontact', None) is None:
1034 if ui.config('ui', 'supportcontact', None) is None:
1025 for name, mod in extensions.extensions():
1035 for name, mod in extensions.extensions():
1026 testedwith = getattr(mod, 'testedwith', '')
1036 testedwith = getattr(mod, 'testedwith', '')
1027 report = getattr(mod, 'buglink', _('the extension author.'))
1037 report = getattr(mod, 'buglink', _('the extension author.'))
1028 if not testedwith.strip():
1038 if not testedwith.strip():
1029 # We found an untested extension. It's likely the culprit.
1039 # We found an untested extension. It's likely the culprit.
1030 worst = name, 'unknown', report
1040 worst = name, 'unknown', report
1031 break
1041 break
1032
1042
1033 # Never blame on extensions bundled with Mercurial.
1043 # Never blame on extensions bundled with Mercurial.
1034 if testedwith == 'internal':
1044 if testedwith == 'internal':
1035 continue
1045 continue
1036
1046
1037 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1047 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1038 if ct in tested:
1048 if ct in tested:
1039 continue
1049 continue
1040
1050
1041 lower = [t for t in tested if t < ct]
1051 lower = [t for t in tested if t < ct]
1042 nearest = max(lower or tested)
1052 nearest = max(lower or tested)
1043 if worst[0] is None or nearest < worst[1]:
1053 if worst[0] is None or nearest < worst[1]:
1044 worst = name, nearest, report
1054 worst = name, nearest, report
1045 if worst[0] is not None:
1055 if worst[0] is not None:
1046 name, testedwith, report = worst
1056 name, testedwith, report = worst
1047 if not isinstance(testedwith, str):
1057 if not isinstance(testedwith, str):
1048 testedwith = '.'.join([str(c) for c in testedwith])
1058 testedwith = '.'.join([str(c) for c in testedwith])
1049 warning = (_('** Unknown exception encountered with '
1059 warning = (_('** Unknown exception encountered with '
1050 'possibly-broken third-party extension %s\n'
1060 'possibly-broken third-party extension %s\n'
1051 '** which supports versions %s of Mercurial.\n'
1061 '** which supports versions %s of Mercurial.\n'
1052 '** Please disable %s and try your action again.\n'
1062 '** Please disable %s and try your action again.\n'
1053 '** If that fixes the bug please report it to %s\n')
1063 '** If that fixes the bug please report it to %s\n')
1054 % (name, testedwith, name, report))
1064 % (name, testedwith, name, report))
1055 else:
1065 else:
1056 bugtracker = ui.config('ui', 'supportcontact', None)
1066 bugtracker = ui.config('ui', 'supportcontact', None)
1057 if bugtracker is None:
1067 if bugtracker is None:
1058 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1068 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1059 warning = (_("** unknown exception encountered, "
1069 warning = (_("** unknown exception encountered, "
1060 "please report by visiting\n** ") + bugtracker + '\n')
1070 "please report by visiting\n** ") + bugtracker + '\n')
1061 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
1071 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
1062 (_("** Mercurial Distributed SCM (version %s)\n") %
1072 (_("** Mercurial Distributed SCM (version %s)\n") %
1063 util.version()) +
1073 util.version()) +
1064 (_("** Extensions loaded: %s\n") %
1074 (_("** Extensions loaded: %s\n") %
1065 ", ".join([x[0] for x in extensions.extensions()])))
1075 ", ".join([x[0] for x in extensions.extensions()])))
1066 return warning
1076 return warning
1067
1077
1068 def handlecommandexception(ui):
1078 def handlecommandexception(ui):
1069 """Produce a warning message for broken commands
1079 """Produce a warning message for broken commands
1070
1080
1071 Called when handling an exception; the exception is reraised if
1081 Called when handling an exception; the exception is reraised if
1072 this function returns False, ignored otherwise.
1082 this function returns False, ignored otherwise.
1073 """
1083 """
1074 warning = _exceptionwarning(ui)
1084 warning = _exceptionwarning(ui)
1075 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1085 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1076 ui.warn(warning)
1086 ui.warn(warning)
1077 return False # re-raise the exception
1087 return False # re-raise the exception
@@ -1,173 +1,179 b''
1
1
2 $ cat << EOF > buggylocking.py
2 $ cat << EOF > buggylocking.py
3 > """A small extension that tests our developer warnings
3 > """A small extension that tests our developer warnings
4 > """
4 > """
5 >
5 >
6 > from mercurial import cmdutil, repair, revset
6 > from mercurial import cmdutil, repair, revset
7 >
7 >
8 > cmdtable = {}
8 > cmdtable = {}
9 > command = cmdutil.command(cmdtable)
9 > command = cmdutil.command(cmdtable)
10 >
10 >
11 > @command('buggylocking', [], '')
11 > @command('buggylocking', [], '')
12 > def buggylocking(ui, repo):
12 > def buggylocking(ui, repo):
13 > lo = repo.lock()
13 > lo = repo.lock()
14 > wl = repo.wlock()
14 > wl = repo.wlock()
15 > wl.release()
15 > wl.release()
16 > lo.release()
16 > lo.release()
17 >
17 >
18 > @command('buggytransaction', [], '')
18 > @command('buggytransaction', [], '')
19 > def buggylocking(ui, repo):
19 > def buggylocking(ui, repo):
20 > tr = repo.transaction('buggy')
20 > tr = repo.transaction('buggy')
21 > # make sure we rollback the transaction as we don't want to rely on the__del__
21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 > tr.release()
22 > tr.release()
23 >
23 >
24 > @command('properlocking', [], '')
24 > @command('properlocking', [], '')
25 > def properlocking(ui, repo):
25 > def properlocking(ui, repo):
26 > """check that reentrance is fine"""
26 > """check that reentrance is fine"""
27 > wl = repo.wlock()
27 > wl = repo.wlock()
28 > lo = repo.lock()
28 > lo = repo.lock()
29 > tr = repo.transaction('proper')
29 > tr = repo.transaction('proper')
30 > tr2 = repo.transaction('proper')
30 > tr2 = repo.transaction('proper')
31 > lo2 = repo.lock()
31 > lo2 = repo.lock()
32 > wl2 = repo.wlock()
32 > wl2 = repo.wlock()
33 > wl2.release()
33 > wl2.release()
34 > lo2.release()
34 > lo2.release()
35 > tr2.close()
35 > tr2.close()
36 > tr.close()
36 > tr.close()
37 > lo.release()
37 > lo.release()
38 > wl.release()
38 > wl.release()
39 >
39 >
40 > @command('nowaitlocking', [], '')
40 > @command('nowaitlocking', [], '')
41 > def nowaitlocking(ui, repo):
41 > def nowaitlocking(ui, repo):
42 > lo = repo.lock()
42 > lo = repo.lock()
43 > wl = repo.wlock(wait=False)
43 > wl = repo.wlock(wait=False)
44 > wl.release()
44 > wl.release()
45 > lo.release()
45 > lo.release()
46 >
46 >
47 > @command('stripintr', [], '')
47 > @command('stripintr', [], '')
48 > def stripintr(ui, repo):
48 > def stripintr(ui, repo):
49 > lo = repo.lock()
49 > lo = repo.lock()
50 > tr = repo.transaction('foobar')
50 > tr = repo.transaction('foobar')
51 > try:
51 > try:
52 > repair.strip(repo.ui, repo, [repo['.'].node()])
52 > repair.strip(repo.ui, repo, [repo['.'].node()])
53 > finally:
53 > finally:
54 > lo.release()
54 > lo.release()
55 > @command('oldanddeprecated', [], '')
55 > @command('oldanddeprecated', [], '')
56 > def oldanddeprecated(ui, repo):
56 > def oldanddeprecated(ui, repo):
57 > """test deprecation warning API"""
57 > """test deprecation warning API"""
58 > def foobar(ui):
58 > def foobar(ui):
59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
60 > foobar(ui)
60 > foobar(ui)
61 >
61 >
62 > def oldstylerevset(repo, subset, x):
62 > def oldstylerevset(repo, subset, x):
63 > return list(subset)
63 > return list(subset)
64 >
64 >
65 > revset.symbols['oldstyle'] = oldstylerevset
65 > revset.symbols['oldstyle'] = oldstylerevset
66 > EOF
66 > EOF
67
67
68 $ cat << EOF >> $HGRCPATH
68 $ cat << EOF >> $HGRCPATH
69 > [extensions]
69 > [extensions]
70 > buggylocking=$TESTTMP/buggylocking.py
70 > buggylocking=$TESTTMP/buggylocking.py
71 > mock=$TESTDIR/mockblackbox.py
71 > mock=$TESTDIR/mockblackbox.py
72 > blackbox=
72 > blackbox=
73 > [devel]
73 > [devel]
74 > all-warnings=1
74 > all-warnings=1
75 > EOF
75 > EOF
76
76
77 $ hg init lock-checker
77 $ hg init lock-checker
78 $ cd lock-checker
78 $ cd lock-checker
79 $ hg buggylocking
79 $ hg buggylocking
80 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
80 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
81 $ cat << EOF >> $HGRCPATH
81 $ cat << EOF >> $HGRCPATH
82 > [devel]
82 > [devel]
83 > all=0
83 > all=0
84 > check-locks=1
84 > check-locks=1
85 > EOF
85 > EOF
86 $ hg buggylocking
86 $ hg buggylocking
87 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
87 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
88 $ hg buggylocking --traceback
88 $ hg buggylocking --traceback
89 devel-warn: "wlock" acquired after "lock" at:
89 devel-warn: "wlock" acquired after "lock" at:
90 */hg:* in * (glob)
90 */hg:* in * (glob)
91 */mercurial/dispatch.py:* in run (glob)
91 */mercurial/dispatch.py:* in run (glob)
92 */mercurial/dispatch.py:* in dispatch (glob)
92 */mercurial/dispatch.py:* in dispatch (glob)
93 */mercurial/dispatch.py:* in _runcatch (glob)
93 */mercurial/dispatch.py:* in _runcatch (glob)
94 */mercurial/dispatch.py:* in callcatch (glob)
95 */mercurial/dispatch.py:* in _runcatchfunc (glob)
94 */mercurial/dispatch.py:* in _dispatch (glob)
96 */mercurial/dispatch.py:* in _dispatch (glob)
95 */mercurial/dispatch.py:* in runcommand (glob)
97 */mercurial/dispatch.py:* in runcommand (glob)
96 */mercurial/dispatch.py:* in _runcommand (glob)
98 */mercurial/dispatch.py:* in _runcommand (glob)
97 */mercurial/dispatch.py:* in checkargs (glob)
99 */mercurial/dispatch.py:* in checkargs (glob)
98 */mercurial/dispatch.py:* in <lambda> (glob)
100 */mercurial/dispatch.py:* in <lambda> (glob)
99 */mercurial/util.py:* in check (glob)
101 */mercurial/util.py:* in check (glob)
100 $TESTTMP/buggylocking.py:* in buggylocking (glob)
102 $TESTTMP/buggylocking.py:* in buggylocking (glob)
101 $ hg properlocking
103 $ hg properlocking
102 $ hg nowaitlocking
104 $ hg nowaitlocking
103
105
104 $ echo a > a
106 $ echo a > a
105 $ hg add a
107 $ hg add a
106 $ hg commit -m a
108 $ hg commit -m a
107 $ hg stripintr
109 $ hg stripintr
108 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/*-backup.hg (glob)
110 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/*-backup.hg (glob)
109 abort: programming error: cannot strip from inside a transaction
111 abort: programming error: cannot strip from inside a transaction
110 (contact your extension maintainer)
112 (contact your extension maintainer)
111 [255]
113 [255]
112
114
113 $ hg log -r "oldstyle()" -T '{rev}\n'
115 $ hg log -r "oldstyle()" -T '{rev}\n'
114 devel-warn: revset "oldstyle" uses list instead of smartset
116 devel-warn: revset "oldstyle" uses list instead of smartset
115 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
117 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
116 0
118 0
117 $ hg oldanddeprecated
119 $ hg oldanddeprecated
118 devel-warn: foorbar is deprecated, go shopping
120 devel-warn: foorbar is deprecated, go shopping
119 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
121 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
120
122
121 $ hg oldanddeprecated --traceback
123 $ hg oldanddeprecated --traceback
122 devel-warn: foorbar is deprecated, go shopping
124 devel-warn: foorbar is deprecated, go shopping
123 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
125 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
124 */hg:* in <module> (glob)
126 */hg:* in <module> (glob)
125 */mercurial/dispatch.py:* in run (glob)
127 */mercurial/dispatch.py:* in run (glob)
126 */mercurial/dispatch.py:* in dispatch (glob)
128 */mercurial/dispatch.py:* in dispatch (glob)
127 */mercurial/dispatch.py:* in _runcatch (glob)
129 */mercurial/dispatch.py:* in _runcatch (glob)
130 */mercurial/dispatch.py:* in callcatch (glob)
131 */mercurial/dispatch.py:* in _runcatchfunc (glob)
128 */mercurial/dispatch.py:* in _dispatch (glob)
132 */mercurial/dispatch.py:* in _dispatch (glob)
129 */mercurial/dispatch.py:* in runcommand (glob)
133 */mercurial/dispatch.py:* in runcommand (glob)
130 */mercurial/dispatch.py:* in _runcommand (glob)
134 */mercurial/dispatch.py:* in _runcommand (glob)
131 */mercurial/dispatch.py:* in checkargs (glob)
135 */mercurial/dispatch.py:* in checkargs (glob)
132 */mercurial/dispatch.py:* in <lambda> (glob)
136 */mercurial/dispatch.py:* in <lambda> (glob)
133 */mercurial/util.py:* in check (glob)
137 */mercurial/util.py:* in check (glob)
134 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
138 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
135 $ hg blackbox -l 9
139 $ hg blackbox -l 9
136 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: revset "oldstyle" uses list instead of smartset
140 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: revset "oldstyle" uses list instead of smartset
137 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
141 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
138 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> log -r oldstyle() -T {rev}\n exited 0 after * seconds (glob)
142 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> log -r oldstyle() -T {rev}\n exited 0 after * seconds (glob)
139 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
143 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
140 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
144 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
141 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
145 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
142 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
146 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
143 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
147 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
144 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
148 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
145 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
149 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
146 */hg:* in <module> (glob)
150 */hg:* in <module> (glob)
147 */mercurial/dispatch.py:* in run (glob)
151 */mercurial/dispatch.py:* in run (glob)
148 */mercurial/dispatch.py:* in dispatch (glob)
152 */mercurial/dispatch.py:* in dispatch (glob)
149 */mercurial/dispatch.py:* in _runcatch (glob)
153 */mercurial/dispatch.py:* in _runcatch (glob)
154 */mercurial/dispatch.py:* in callcatch (glob)
155 */mercurial/dispatch.py:* in _runcatchfunc (glob)
150 */mercurial/dispatch.py:* in _dispatch (glob)
156 */mercurial/dispatch.py:* in _dispatch (glob)
151 */mercurial/dispatch.py:* in runcommand (glob)
157 */mercurial/dispatch.py:* in runcommand (glob)
152 */mercurial/dispatch.py:* in _runcommand (glob)
158 */mercurial/dispatch.py:* in _runcommand (glob)
153 */mercurial/dispatch.py:* in checkargs (glob)
159 */mercurial/dispatch.py:* in checkargs (glob)
154 */mercurial/dispatch.py:* in <lambda> (glob)
160 */mercurial/dispatch.py:* in <lambda> (glob)
155 */mercurial/util.py:* in check (glob)
161 */mercurial/util.py:* in check (glob)
156 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
162 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
157 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
163 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
158 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 9
164 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 9
159
165
160 Test programming error failure:
166 Test programming error failure:
161
167
162 $ hg buggytransaction 2>&1 | egrep -v '^ '
168 $ hg buggytransaction 2>&1 | egrep -v '^ '
163 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
169 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
164 ** which supports versions unknown of Mercurial.
170 ** which supports versions unknown of Mercurial.
165 ** Please disable buggylocking and try your action again.
171 ** Please disable buggylocking and try your action again.
166 ** If that fixes the bug please report it to the extension author.
172 ** If that fixes the bug please report it to the extension author.
167 ** Python * (glob)
173 ** Python * (glob)
168 ** Mercurial Distributed SCM (*) (glob)
174 ** Mercurial Distributed SCM (*) (glob)
169 ** Extensions loaded: * (glob)
175 ** Extensions loaded: * (glob)
170 Traceback (most recent call last):
176 Traceback (most recent call last):
171 RuntimeError: programming error: transaction requires locking
177 RuntimeError: programming error: transaction requires locking
172
178
173 $ cd ..
179 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now