##// END OF EJS Templates
dispatch: factor out command failure handling into a function...
Martijn Pieters -
r28784:09750b12 default
parent child Browse files
Show More
@@ -1,1068 +1,1077 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 try:
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 # Global exception handling, alphabetically
215 # Global exception handling, alphabetically
216 # Mercurial-specific first, followed by built-in and library exceptions
216 # Mercurial-specific first, followed by built-in and library exceptions
217 except error.AmbiguousCommand as inst:
217 except error.AmbiguousCommand as inst:
218 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
218 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
219 (inst.args[0], " ".join(inst.args[1])))
219 (inst.args[0], " ".join(inst.args[1])))
220 except error.ParseError as inst:
220 except error.ParseError as inst:
221 _formatparse(ui.warn, inst)
221 _formatparse(ui.warn, inst)
222 return -1
222 return -1
223 except error.LockHeld as inst:
223 except error.LockHeld as inst:
224 if inst.errno == errno.ETIMEDOUT:
224 if inst.errno == errno.ETIMEDOUT:
225 reason = _('timed out waiting for lock held by %s') % inst.locker
225 reason = _('timed out waiting for lock held by %s') % inst.locker
226 else:
226 else:
227 reason = _('lock held by %s') % inst.locker
227 reason = _('lock held by %s') % inst.locker
228 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
228 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
229 except error.LockUnavailable as inst:
229 except error.LockUnavailable as inst:
230 ui.warn(_("abort: could not lock %s: %s\n") %
230 ui.warn(_("abort: could not lock %s: %s\n") %
231 (inst.desc or inst.filename, inst.strerror))
231 (inst.desc or inst.filename, inst.strerror))
232 except error.CommandError as inst:
232 except error.CommandError as inst:
233 if inst.args[0]:
233 if inst.args[0]:
234 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
234 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
235 commands.help_(ui, inst.args[0], full=False, command=True)
235 commands.help_(ui, inst.args[0], full=False, command=True)
236 else:
236 else:
237 ui.warn(_("hg: %s\n") % inst.args[1])
237 ui.warn(_("hg: %s\n") % inst.args[1])
238 commands.help_(ui, 'shortlist')
238 commands.help_(ui, 'shortlist')
239 except error.OutOfBandError as inst:
239 except error.OutOfBandError as inst:
240 if inst.args:
240 if inst.args:
241 msg = _("abort: remote error:\n")
241 msg = _("abort: remote error:\n")
242 else:
242 else:
243 msg = _("abort: remote error\n")
243 msg = _("abort: remote error\n")
244 ui.warn(msg)
244 ui.warn(msg)
245 if inst.args:
245 if inst.args:
246 ui.warn(''.join(inst.args))
246 ui.warn(''.join(inst.args))
247 if inst.hint:
247 if inst.hint:
248 ui.warn('(%s)\n' % inst.hint)
248 ui.warn('(%s)\n' % inst.hint)
249 except error.RepoError as inst:
249 except error.RepoError as inst:
250 ui.warn(_("abort: %s!\n") % inst)
250 ui.warn(_("abort: %s!\n") % inst)
251 if inst.hint:
251 if inst.hint:
252 ui.warn(_("(%s)\n") % inst.hint)
252 ui.warn(_("(%s)\n") % inst.hint)
253 except error.ResponseError as inst:
253 except error.ResponseError as inst:
254 ui.warn(_("abort: %s") % inst.args[0])
254 ui.warn(_("abort: %s") % inst.args[0])
255 if not isinstance(inst.args[1], basestring):
255 if not isinstance(inst.args[1], basestring):
256 ui.warn(" %r\n" % (inst.args[1],))
256 ui.warn(" %r\n" % (inst.args[1],))
257 elif not inst.args[1]:
257 elif not inst.args[1]:
258 ui.warn(_(" empty string\n"))
258 ui.warn(_(" empty string\n"))
259 else:
259 else:
260 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
260 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
261 except error.CensoredNodeError as inst:
261 except error.CensoredNodeError as inst:
262 ui.warn(_("abort: file censored %s!\n") % inst)
262 ui.warn(_("abort: file censored %s!\n") % inst)
263 except error.RevlogError as inst:
263 except error.RevlogError as inst:
264 ui.warn(_("abort: %s!\n") % inst)
264 ui.warn(_("abort: %s!\n") % inst)
265 except error.SignalInterrupt:
265 except error.SignalInterrupt:
266 ui.warn(_("killed!\n"))
266 ui.warn(_("killed!\n"))
267 except error.UnknownCommand as inst:
267 except error.UnknownCommand as inst:
268 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
268 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
269 try:
269 try:
270 # check if the command is in a disabled extension
270 # check if the command is in a disabled extension
271 # (but don't check for extensions themselves)
271 # (but don't check for extensions themselves)
272 commands.help_(ui, inst.args[0], unknowncmd=True)
272 commands.help_(ui, inst.args[0], unknowncmd=True)
273 except (error.UnknownCommand, error.Abort):
273 except (error.UnknownCommand, error.Abort):
274 suggested = False
274 suggested = False
275 if len(inst.args) == 2:
275 if len(inst.args) == 2:
276 sim = _getsimilar(inst.args[1], inst.args[0])
276 sim = _getsimilar(inst.args[1], inst.args[0])
277 if sim:
277 if sim:
278 _reportsimilar(ui.warn, sim)
278 _reportsimilar(ui.warn, sim)
279 suggested = True
279 suggested = True
280 if not suggested:
280 if not suggested:
281 commands.help_(ui, 'shortlist')
281 commands.help_(ui, 'shortlist')
282 except error.InterventionRequired as inst:
282 except error.InterventionRequired as inst:
283 ui.warn("%s\n" % inst)
283 ui.warn("%s\n" % inst)
284 if inst.hint:
284 if inst.hint:
285 ui.warn(_("(%s)\n") % inst.hint)
285 ui.warn(_("(%s)\n") % inst.hint)
286 return 1
286 return 1
287 except error.Abort as inst:
287 except error.Abort as inst:
288 ui.warn(_("abort: %s\n") % inst)
288 ui.warn(_("abort: %s\n") % inst)
289 if inst.hint:
289 if inst.hint:
290 ui.warn(_("(%s)\n") % inst.hint)
290 ui.warn(_("(%s)\n") % inst.hint)
291 except ImportError as inst:
291 except ImportError as inst:
292 ui.warn(_("abort: %s!\n") % inst)
292 ui.warn(_("abort: %s!\n") % inst)
293 m = str(inst).split()[-1]
293 m = str(inst).split()[-1]
294 if m in "mpatch bdiff".split():
294 if m in "mpatch bdiff".split():
295 ui.warn(_("(did you forget to compile extensions?)\n"))
295 ui.warn(_("(did you forget to compile extensions?)\n"))
296 elif m in "zlib".split():
296 elif m in "zlib".split():
297 ui.warn(_("(is your Python install correct?)\n"))
297 ui.warn(_("(is your Python install correct?)\n"))
298 except IOError as inst:
298 except IOError as inst:
299 if util.safehasattr(inst, "code"):
299 if util.safehasattr(inst, "code"):
300 ui.warn(_("abort: %s\n") % inst)
300 ui.warn(_("abort: %s\n") % inst)
301 elif util.safehasattr(inst, "reason"):
301 elif util.safehasattr(inst, "reason"):
302 try: # usually it is in the form (errno, strerror)
302 try: # usually it is in the form (errno, strerror)
303 reason = inst.reason.args[1]
303 reason = inst.reason.args[1]
304 except (AttributeError, IndexError):
304 except (AttributeError, IndexError):
305 # it might be anything, for example a string
305 # it might be anything, for example a string
306 reason = inst.reason
306 reason = inst.reason
307 if isinstance(reason, unicode):
307 if isinstance(reason, unicode):
308 # SSLError of Python 2.7.9 contains a unicode
308 # SSLError of Python 2.7.9 contains a unicode
309 reason = reason.encode(encoding.encoding, 'replace')
309 reason = reason.encode(encoding.encoding, 'replace')
310 ui.warn(_("abort: error: %s\n") % reason)
310 ui.warn(_("abort: error: %s\n") % reason)
311 elif (util.safehasattr(inst, "args")
311 elif (util.safehasattr(inst, "args")
312 and inst.args and inst.args[0] == errno.EPIPE):
312 and inst.args and inst.args[0] == errno.EPIPE):
313 pass
313 pass
314 elif getattr(inst, "strerror", None):
314 elif getattr(inst, "strerror", None):
315 if getattr(inst, "filename", None):
315 if getattr(inst, "filename", None):
316 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
316 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
317 else:
317 else:
318 ui.warn(_("abort: %s\n") % inst.strerror)
318 ui.warn(_("abort: %s\n") % inst.strerror)
319 else:
319 else:
320 raise
320 raise
321 except OSError as inst:
321 except OSError as inst:
322 if getattr(inst, "filename", None) is not None:
322 if getattr(inst, "filename", None) is not None:
323 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
323 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
324 else:
324 else:
325 ui.warn(_("abort: %s\n") % inst.strerror)
325 ui.warn(_("abort: %s\n") % inst.strerror)
326 except KeyboardInterrupt:
326 except KeyboardInterrupt:
327 raise
327 raise
328 except MemoryError:
328 except MemoryError:
329 ui.warn(_("abort: out of memory\n"))
329 ui.warn(_("abort: out of memory\n"))
330 except SystemExit as inst:
330 except SystemExit as inst:
331 # Commands shouldn't sys.exit directly, but give a return code.
331 # Commands shouldn't sys.exit directly, but give a return code.
332 # Just in case catch this and and pass exit code to caller.
332 # Just in case catch this and and pass exit code to caller.
333 return inst.code
333 return inst.code
334 except socket.error as inst:
334 except socket.error as inst:
335 ui.warn(_("abort: %s\n") % inst.args[-1])
335 ui.warn(_("abort: %s\n") % inst.args[-1])
336 except: # re-raises
336 except: # perhaps re-raises
337 # For compatibility checking, we discard the portion of the hg
337 if not handlecommandexception(ui):
338 # version after the + on the assumption that if a "normal
338 raise
339 # user" is running a build with a + in it the packager
340 # probably built from fairly close to a tag and anyone with a
341 # 'make local' copy of hg (where the version number can be out
342 # of date) will be clueful enough to notice the implausible
343 # version number and try updating.
344 ct = util.versiontuple(n=2)
345 worst = None, ct, ''
346 if ui.config('ui', 'supportcontact', None) is None:
347 for name, mod in extensions.extensions():
348 testedwith = getattr(mod, 'testedwith', '')
349 report = getattr(mod, 'buglink', _('the extension author.'))
350 if not testedwith.strip():
351 # We found an untested extension. It's likely the culprit.
352 worst = name, 'unknown', report
353 break
354
355 # Never blame on extensions bundled with Mercurial.
356 if testedwith == 'internal':
357 continue
358
359 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
360 if ct in tested:
361 continue
362
363 lower = [t for t in tested if t < ct]
364 nearest = max(lower or tested)
365 if worst[0] is None or nearest < worst[1]:
366 worst = name, nearest, report
367 if worst[0] is not None:
368 name, testedwith, report = worst
369 if not isinstance(testedwith, str):
370 testedwith = '.'.join([str(c) for c in testedwith])
371 warning = (_('** Unknown exception encountered with '
372 'possibly-broken third-party extension %s\n'
373 '** which supports versions %s of Mercurial.\n'
374 '** Please disable %s and try your action again.\n'
375 '** If that fixes the bug please report it to %s\n')
376 % (name, testedwith, name, report))
377 else:
378 bugtracker = ui.config('ui', 'supportcontact', None)
379 if bugtracker is None:
380 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
381 warning = (_("** unknown exception encountered, "
382 "please report by visiting\n** ") + bugtracker + '\n')
383 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
384 (_("** Mercurial Distributed SCM (version %s)\n") %
385 util.version()) +
386 (_("** Extensions loaded: %s\n") %
387 ", ".join([x[0] for x in extensions.extensions()])))
388 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
389 ui.warn(warning)
390 raise
391
339
392 return -1
340 return -1
393
341
394 def aliasargs(fn, givenargs):
342 def aliasargs(fn, givenargs):
395 args = getattr(fn, 'args', [])
343 args = getattr(fn, 'args', [])
396 if args:
344 if args:
397 cmd = ' '.join(map(util.shellquote, args))
345 cmd = ' '.join(map(util.shellquote, args))
398
346
399 nums = []
347 nums = []
400 def replacer(m):
348 def replacer(m):
401 num = int(m.group(1)) - 1
349 num = int(m.group(1)) - 1
402 nums.append(num)
350 nums.append(num)
403 if num < len(givenargs):
351 if num < len(givenargs):
404 return givenargs[num]
352 return givenargs[num]
405 raise error.Abort(_('too few arguments for command alias'))
353 raise error.Abort(_('too few arguments for command alias'))
406 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
354 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
407 givenargs = [x for i, x in enumerate(givenargs)
355 givenargs = [x for i, x in enumerate(givenargs)
408 if i not in nums]
356 if i not in nums]
409 args = shlex.split(cmd)
357 args = shlex.split(cmd)
410 return args + givenargs
358 return args + givenargs
411
359
412 def aliasinterpolate(name, args, cmd):
360 def aliasinterpolate(name, args, cmd):
413 '''interpolate args into cmd for shell aliases
361 '''interpolate args into cmd for shell aliases
414
362
415 This also handles $0, $@ and "$@".
363 This also handles $0, $@ and "$@".
416 '''
364 '''
417 # util.interpolate can't deal with "$@" (with quotes) because it's only
365 # util.interpolate can't deal with "$@" (with quotes) because it's only
418 # built to match prefix + patterns.
366 # built to match prefix + patterns.
419 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
367 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
420 replacemap['$0'] = name
368 replacemap['$0'] = name
421 replacemap['$$'] = '$'
369 replacemap['$$'] = '$'
422 replacemap['$@'] = ' '.join(args)
370 replacemap['$@'] = ' '.join(args)
423 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
371 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
424 # parameters, separated out into words. Emulate the same behavior here by
372 # parameters, separated out into words. Emulate the same behavior here by
425 # quoting the arguments individually. POSIX shells will then typically
373 # quoting the arguments individually. POSIX shells will then typically
426 # tokenize each argument into exactly one word.
374 # tokenize each argument into exactly one word.
427 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
375 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
428 # escape '\$' for regex
376 # escape '\$' for regex
429 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
377 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
430 r = re.compile(regex)
378 r = re.compile(regex)
431 return r.sub(lambda x: replacemap[x.group()], cmd)
379 return r.sub(lambda x: replacemap[x.group()], cmd)
432
380
433 class cmdalias(object):
381 class cmdalias(object):
434 def __init__(self, name, definition, cmdtable):
382 def __init__(self, name, definition, cmdtable):
435 self.name = self.cmd = name
383 self.name = self.cmd = name
436 self.cmdname = ''
384 self.cmdname = ''
437 self.definition = definition
385 self.definition = definition
438 self.fn = None
386 self.fn = None
439 self.args = []
387 self.args = []
440 self.opts = []
388 self.opts = []
441 self.help = ''
389 self.help = ''
442 self.badalias = None
390 self.badalias = None
443 self.unknowncmd = False
391 self.unknowncmd = False
444
392
445 try:
393 try:
446 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
394 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
447 for alias, e in cmdtable.iteritems():
395 for alias, e in cmdtable.iteritems():
448 if e is entry:
396 if e is entry:
449 self.cmd = alias
397 self.cmd = alias
450 break
398 break
451 self.shadows = True
399 self.shadows = True
452 except error.UnknownCommand:
400 except error.UnknownCommand:
453 self.shadows = False
401 self.shadows = False
454
402
455 if not self.definition:
403 if not self.definition:
456 self.badalias = _("no definition for alias '%s'") % self.name
404 self.badalias = _("no definition for alias '%s'") % self.name
457 return
405 return
458
406
459 if self.definition.startswith('!'):
407 if self.definition.startswith('!'):
460 self.shell = True
408 self.shell = True
461 def fn(ui, *args):
409 def fn(ui, *args):
462 env = {'HG_ARGS': ' '.join((self.name,) + args)}
410 env = {'HG_ARGS': ' '.join((self.name,) + args)}
463 def _checkvar(m):
411 def _checkvar(m):
464 if m.groups()[0] == '$':
412 if m.groups()[0] == '$':
465 return m.group()
413 return m.group()
466 elif int(m.groups()[0]) <= len(args):
414 elif int(m.groups()[0]) <= len(args):
467 return m.group()
415 return m.group()
468 else:
416 else:
469 ui.debug("No argument found for substitution "
417 ui.debug("No argument found for substitution "
470 "of %i variable in alias '%s' definition."
418 "of %i variable in alias '%s' definition."
471 % (int(m.groups()[0]), self.name))
419 % (int(m.groups()[0]), self.name))
472 return ''
420 return ''
473 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
421 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
474 cmd = aliasinterpolate(self.name, args, cmd)
422 cmd = aliasinterpolate(self.name, args, cmd)
475 return ui.system(cmd, environ=env)
423 return ui.system(cmd, environ=env)
476 self.fn = fn
424 self.fn = fn
477 return
425 return
478
426
479 try:
427 try:
480 args = shlex.split(self.definition)
428 args = shlex.split(self.definition)
481 except ValueError as inst:
429 except ValueError as inst:
482 self.badalias = (_("error in definition for alias '%s': %s")
430 self.badalias = (_("error in definition for alias '%s': %s")
483 % (self.name, inst))
431 % (self.name, inst))
484 return
432 return
485 self.cmdname = cmd = args.pop(0)
433 self.cmdname = cmd = args.pop(0)
486 args = map(util.expandpath, args)
434 args = map(util.expandpath, args)
487
435
488 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
436 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
489 if _earlygetopt([invalidarg], args):
437 if _earlygetopt([invalidarg], args):
490 self.badalias = (_("error in definition for alias '%s': %s may "
438 self.badalias = (_("error in definition for alias '%s': %s may "
491 "only be given on the command line")
439 "only be given on the command line")
492 % (self.name, invalidarg))
440 % (self.name, invalidarg))
493 return
441 return
494
442
495 try:
443 try:
496 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
444 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
497 if len(tableentry) > 2:
445 if len(tableentry) > 2:
498 self.fn, self.opts, self.help = tableentry
446 self.fn, self.opts, self.help = tableentry
499 else:
447 else:
500 self.fn, self.opts = tableentry
448 self.fn, self.opts = tableentry
501
449
502 self.args = aliasargs(self.fn, args)
450 self.args = aliasargs(self.fn, args)
503 if self.help.startswith("hg " + cmd):
451 if self.help.startswith("hg " + cmd):
504 # drop prefix in old-style help lines so hg shows the alias
452 # drop prefix in old-style help lines so hg shows the alias
505 self.help = self.help[4 + len(cmd):]
453 self.help = self.help[4 + len(cmd):]
506 self.__doc__ = self.fn.__doc__
454 self.__doc__ = self.fn.__doc__
507
455
508 except error.UnknownCommand:
456 except error.UnknownCommand:
509 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
457 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
510 % (self.name, cmd))
458 % (self.name, cmd))
511 self.unknowncmd = True
459 self.unknowncmd = True
512 except error.AmbiguousCommand:
460 except error.AmbiguousCommand:
513 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
461 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
514 % (self.name, cmd))
462 % (self.name, cmd))
515
463
516 def __getattr__(self, name):
464 def __getattr__(self, name):
517 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
465 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
518 if name not in adefaults:
466 if name not in adefaults:
519 raise AttributeError(name)
467 raise AttributeError(name)
520 if self.badalias or util.safehasattr(self, 'shell'):
468 if self.badalias or util.safehasattr(self, 'shell'):
521 return adefaults[name]
469 return adefaults[name]
522 return getattr(self.fn, name)
470 return getattr(self.fn, name)
523
471
524 def __call__(self, ui, *args, **opts):
472 def __call__(self, ui, *args, **opts):
525 if self.badalias:
473 if self.badalias:
526 hint = None
474 hint = None
527 if self.unknowncmd:
475 if self.unknowncmd:
528 try:
476 try:
529 # check if the command is in a disabled extension
477 # check if the command is in a disabled extension
530 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
478 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
531 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
479 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
532 except error.UnknownCommand:
480 except error.UnknownCommand:
533 pass
481 pass
534 raise error.Abort(self.badalias, hint=hint)
482 raise error.Abort(self.badalias, hint=hint)
535 if self.shadows:
483 if self.shadows:
536 ui.debug("alias '%s' shadows command '%s'\n" %
484 ui.debug("alias '%s' shadows command '%s'\n" %
537 (self.name, self.cmdname))
485 (self.name, self.cmdname))
538
486
539 if util.safehasattr(self, 'shell'):
487 if util.safehasattr(self, 'shell'):
540 return self.fn(ui, *args, **opts)
488 return self.fn(ui, *args, **opts)
541 else:
489 else:
542 try:
490 try:
543 return util.checksignature(self.fn)(ui, *args, **opts)
491 return util.checksignature(self.fn)(ui, *args, **opts)
544 except error.SignatureError:
492 except error.SignatureError:
545 args = ' '.join([self.cmdname] + self.args)
493 args = ' '.join([self.cmdname] + self.args)
546 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
494 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
547 raise
495 raise
548
496
549 def addaliases(ui, cmdtable):
497 def addaliases(ui, cmdtable):
550 # aliases are processed after extensions have been loaded, so they
498 # aliases are processed after extensions have been loaded, so they
551 # may use extension commands. Aliases can also use other alias definitions,
499 # may use extension commands. Aliases can also use other alias definitions,
552 # but only if they have been defined prior to the current definition.
500 # but only if they have been defined prior to the current definition.
553 for alias, definition in ui.configitems('alias'):
501 for alias, definition in ui.configitems('alias'):
554 aliasdef = cmdalias(alias, definition, cmdtable)
502 aliasdef = cmdalias(alias, definition, cmdtable)
555
503
556 try:
504 try:
557 olddef = cmdtable[aliasdef.cmd][0]
505 olddef = cmdtable[aliasdef.cmd][0]
558 if olddef.definition == aliasdef.definition:
506 if olddef.definition == aliasdef.definition:
559 continue
507 continue
560 except (KeyError, AttributeError):
508 except (KeyError, AttributeError):
561 # definition might not exist or it might not be a cmdalias
509 # definition might not exist or it might not be a cmdalias
562 pass
510 pass
563
511
564 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
512 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
565
513
566 def _parse(ui, args):
514 def _parse(ui, args):
567 options = {}
515 options = {}
568 cmdoptions = {}
516 cmdoptions = {}
569
517
570 try:
518 try:
571 args = fancyopts.fancyopts(args, commands.globalopts, options)
519 args = fancyopts.fancyopts(args, commands.globalopts, options)
572 except fancyopts.getopt.GetoptError as inst:
520 except fancyopts.getopt.GetoptError as inst:
573 raise error.CommandError(None, inst)
521 raise error.CommandError(None, inst)
574
522
575 if args:
523 if args:
576 cmd, args = args[0], args[1:]
524 cmd, args = args[0], args[1:]
577 aliases, entry = cmdutil.findcmd(cmd, commands.table,
525 aliases, entry = cmdutil.findcmd(cmd, commands.table,
578 ui.configbool("ui", "strict"))
526 ui.configbool("ui", "strict"))
579 cmd = aliases[0]
527 cmd = aliases[0]
580 args = aliasargs(entry[0], args)
528 args = aliasargs(entry[0], args)
581 defaults = ui.config("defaults", cmd)
529 defaults = ui.config("defaults", cmd)
582 if defaults:
530 if defaults:
583 args = map(util.expandpath, shlex.split(defaults)) + args
531 args = map(util.expandpath, shlex.split(defaults)) + args
584 c = list(entry[1])
532 c = list(entry[1])
585 else:
533 else:
586 cmd = None
534 cmd = None
587 c = []
535 c = []
588
536
589 # combine global options into local
537 # combine global options into local
590 for o in commands.globalopts:
538 for o in commands.globalopts:
591 c.append((o[0], o[1], options[o[1]], o[3]))
539 c.append((o[0], o[1], options[o[1]], o[3]))
592
540
593 try:
541 try:
594 args = fancyopts.fancyopts(args, c, cmdoptions, True)
542 args = fancyopts.fancyopts(args, c, cmdoptions, True)
595 except fancyopts.getopt.GetoptError as inst:
543 except fancyopts.getopt.GetoptError as inst:
596 raise error.CommandError(cmd, inst)
544 raise error.CommandError(cmd, inst)
597
545
598 # separate global options back out
546 # separate global options back out
599 for o in commands.globalopts:
547 for o in commands.globalopts:
600 n = o[1]
548 n = o[1]
601 options[n] = cmdoptions[n]
549 options[n] = cmdoptions[n]
602 del cmdoptions[n]
550 del cmdoptions[n]
603
551
604 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
552 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
605
553
606 def _parseconfig(ui, config):
554 def _parseconfig(ui, config):
607 """parse the --config options from the command line"""
555 """parse the --config options from the command line"""
608 configs = []
556 configs = []
609
557
610 for cfg in config:
558 for cfg in config:
611 try:
559 try:
612 name, value = [cfgelem.strip()
560 name, value = [cfgelem.strip()
613 for cfgelem in cfg.split('=', 1)]
561 for cfgelem in cfg.split('=', 1)]
614 section, name = name.split('.', 1)
562 section, name = name.split('.', 1)
615 if not section or not name:
563 if not section or not name:
616 raise IndexError
564 raise IndexError
617 ui.setconfig(section, name, value, '--config')
565 ui.setconfig(section, name, value, '--config')
618 configs.append((section, name, value))
566 configs.append((section, name, value))
619 except (IndexError, ValueError):
567 except (IndexError, ValueError):
620 raise error.Abort(_('malformed --config option: %r '
568 raise error.Abort(_('malformed --config option: %r '
621 '(use --config section.name=value)') % cfg)
569 '(use --config section.name=value)') % cfg)
622
570
623 return configs
571 return configs
624
572
625 def _earlygetopt(aliases, args):
573 def _earlygetopt(aliases, args):
626 """Return list of values for an option (or aliases).
574 """Return list of values for an option (or aliases).
627
575
628 The values are listed in the order they appear in args.
576 The values are listed in the order they appear in args.
629 The options and values are removed from args.
577 The options and values are removed from args.
630
578
631 >>> args = ['x', '--cwd', 'foo', 'y']
579 >>> args = ['x', '--cwd', 'foo', 'y']
632 >>> _earlygetopt(['--cwd'], args), args
580 >>> _earlygetopt(['--cwd'], args), args
633 (['foo'], ['x', 'y'])
581 (['foo'], ['x', 'y'])
634
582
635 >>> args = ['x', '--cwd=bar', 'y']
583 >>> args = ['x', '--cwd=bar', 'y']
636 >>> _earlygetopt(['--cwd'], args), args
584 >>> _earlygetopt(['--cwd'], args), args
637 (['bar'], ['x', 'y'])
585 (['bar'], ['x', 'y'])
638
586
639 >>> args = ['x', '-R', 'foo', 'y']
587 >>> args = ['x', '-R', 'foo', 'y']
640 >>> _earlygetopt(['-R'], args), args
588 >>> _earlygetopt(['-R'], args), args
641 (['foo'], ['x', 'y'])
589 (['foo'], ['x', 'y'])
642
590
643 >>> args = ['x', '-Rbar', 'y']
591 >>> args = ['x', '-Rbar', 'y']
644 >>> _earlygetopt(['-R'], args), args
592 >>> _earlygetopt(['-R'], args), args
645 (['bar'], ['x', 'y'])
593 (['bar'], ['x', 'y'])
646 """
594 """
647 try:
595 try:
648 argcount = args.index("--")
596 argcount = args.index("--")
649 except ValueError:
597 except ValueError:
650 argcount = len(args)
598 argcount = len(args)
651 shortopts = [opt for opt in aliases if len(opt) == 2]
599 shortopts = [opt for opt in aliases if len(opt) == 2]
652 values = []
600 values = []
653 pos = 0
601 pos = 0
654 while pos < argcount:
602 while pos < argcount:
655 fullarg = arg = args[pos]
603 fullarg = arg = args[pos]
656 equals = arg.find('=')
604 equals = arg.find('=')
657 if equals > -1:
605 if equals > -1:
658 arg = arg[:equals]
606 arg = arg[:equals]
659 if arg in aliases:
607 if arg in aliases:
660 del args[pos]
608 del args[pos]
661 if equals > -1:
609 if equals > -1:
662 values.append(fullarg[equals + 1:])
610 values.append(fullarg[equals + 1:])
663 argcount -= 1
611 argcount -= 1
664 else:
612 else:
665 if pos + 1 >= argcount:
613 if pos + 1 >= argcount:
666 # ignore and let getopt report an error if there is no value
614 # ignore and let getopt report an error if there is no value
667 break
615 break
668 values.append(args.pop(pos))
616 values.append(args.pop(pos))
669 argcount -= 2
617 argcount -= 2
670 elif arg[:2] in shortopts:
618 elif arg[:2] in shortopts:
671 # short option can have no following space, e.g. hg log -Rfoo
619 # short option can have no following space, e.g. hg log -Rfoo
672 values.append(args.pop(pos)[2:])
620 values.append(args.pop(pos)[2:])
673 argcount -= 1
621 argcount -= 1
674 else:
622 else:
675 pos += 1
623 pos += 1
676 return values
624 return values
677
625
678 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
626 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
679 # run pre-hook, and abort if it fails
627 # run pre-hook, and abort if it fails
680 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
628 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
681 pats=cmdpats, opts=cmdoptions)
629 pats=cmdpats, opts=cmdoptions)
682 ret = _runcommand(ui, options, cmd, d)
630 ret = _runcommand(ui, options, cmd, d)
683 # run post-hook, passing command result
631 # run post-hook, passing command result
684 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
632 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
685 result=ret, pats=cmdpats, opts=cmdoptions)
633 result=ret, pats=cmdpats, opts=cmdoptions)
686 return ret
634 return ret
687
635
688 def _getlocal(ui, rpath, wd=None):
636 def _getlocal(ui, rpath, wd=None):
689 """Return (path, local ui object) for the given target path.
637 """Return (path, local ui object) for the given target path.
690
638
691 Takes paths in [cwd]/.hg/hgrc into account."
639 Takes paths in [cwd]/.hg/hgrc into account."
692 """
640 """
693 if wd is None:
641 if wd is None:
694 try:
642 try:
695 wd = os.getcwd()
643 wd = os.getcwd()
696 except OSError as e:
644 except OSError as e:
697 raise error.Abort(_("error getting current working directory: %s") %
645 raise error.Abort(_("error getting current working directory: %s") %
698 e.strerror)
646 e.strerror)
699 path = cmdutil.findrepo(wd) or ""
647 path = cmdutil.findrepo(wd) or ""
700 if not path:
648 if not path:
701 lui = ui
649 lui = ui
702 else:
650 else:
703 lui = ui.copy()
651 lui = ui.copy()
704 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
652 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
705
653
706 if rpath and rpath[-1]:
654 if rpath and rpath[-1]:
707 path = lui.expandpath(rpath[-1])
655 path = lui.expandpath(rpath[-1])
708 lui = ui.copy()
656 lui = ui.copy()
709 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
657 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
710
658
711 return path, lui
659 return path, lui
712
660
713 def _checkshellalias(lui, ui, args, precheck=True):
661 def _checkshellalias(lui, ui, args, precheck=True):
714 """Return the function to run the shell alias, if it is required
662 """Return the function to run the shell alias, if it is required
715
663
716 'precheck' is whether this function is invoked before adding
664 'precheck' is whether this function is invoked before adding
717 aliases or not.
665 aliases or not.
718 """
666 """
719 options = {}
667 options = {}
720
668
721 try:
669 try:
722 args = fancyopts.fancyopts(args, commands.globalopts, options)
670 args = fancyopts.fancyopts(args, commands.globalopts, options)
723 except fancyopts.getopt.GetoptError:
671 except fancyopts.getopt.GetoptError:
724 return
672 return
725
673
726 if not args:
674 if not args:
727 return
675 return
728
676
729 if precheck:
677 if precheck:
730 strict = True
678 strict = True
731 cmdtable = commands.table.copy()
679 cmdtable = commands.table.copy()
732 addaliases(lui, cmdtable)
680 addaliases(lui, cmdtable)
733 else:
681 else:
734 strict = False
682 strict = False
735 cmdtable = commands.table
683 cmdtable = commands.table
736
684
737 cmd = args[0]
685 cmd = args[0]
738 try:
686 try:
739 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
687 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
740 except (error.AmbiguousCommand, error.UnknownCommand):
688 except (error.AmbiguousCommand, error.UnknownCommand):
741 return
689 return
742
690
743 cmd = aliases[0]
691 cmd = aliases[0]
744 fn = entry[0]
692 fn = entry[0]
745
693
746 if cmd and util.safehasattr(fn, 'shell'):
694 if cmd and util.safehasattr(fn, 'shell'):
747 d = lambda: fn(ui, *args[1:])
695 d = lambda: fn(ui, *args[1:])
748 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
696 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
749 [], {})
697 [], {})
750
698
751 def _cmdattr(ui, cmd, func, attr):
699 def _cmdattr(ui, cmd, func, attr):
752 try:
700 try:
753 return getattr(func, attr)
701 return getattr(func, attr)
754 except AttributeError:
702 except AttributeError:
755 ui.deprecwarn("missing attribute '%s', use @command decorator "
703 ui.deprecwarn("missing attribute '%s', use @command decorator "
756 "to register '%s'" % (attr, cmd), '3.8')
704 "to register '%s'" % (attr, cmd), '3.8')
757 return False
705 return False
758
706
759 _loaded = set()
707 _loaded = set()
760
708
761 # list of (objname, loadermod, loadername) tuple:
709 # list of (objname, loadermod, loadername) tuple:
762 # - objname is the name of an object in extension module, from which
710 # - objname is the name of an object in extension module, from which
763 # extra information is loaded
711 # extra information is loaded
764 # - loadermod is the module where loader is placed
712 # - loadermod is the module where loader is placed
765 # - loadername is the name of the function, which takes (ui, extensionname,
713 # - loadername is the name of the function, which takes (ui, extensionname,
766 # extraobj) arguments
714 # extraobj) arguments
767 extraloaders = [
715 extraloaders = [
768 ('cmdtable', commands, 'loadcmdtable'),
716 ('cmdtable', commands, 'loadcmdtable'),
769 ('filesetpredicate', fileset, 'loadpredicate'),
717 ('filesetpredicate', fileset, 'loadpredicate'),
770 ('revsetpredicate', revset, 'loadpredicate'),
718 ('revsetpredicate', revset, 'loadpredicate'),
771 ('templatefilter', templatefilters, 'loadfilter'),
719 ('templatefilter', templatefilters, 'loadfilter'),
772 ('templatefunc', templater, 'loadfunction'),
720 ('templatefunc', templater, 'loadfunction'),
773 ('templatekeyword', templatekw, 'loadkeyword'),
721 ('templatekeyword', templatekw, 'loadkeyword'),
774 ]
722 ]
775
723
776 def _dispatch(req):
724 def _dispatch(req):
777 args = req.args
725 args = req.args
778 ui = req.ui
726 ui = req.ui
779
727
780 # check for cwd
728 # check for cwd
781 cwd = _earlygetopt(['--cwd'], args)
729 cwd = _earlygetopt(['--cwd'], args)
782 if cwd:
730 if cwd:
783 os.chdir(cwd[-1])
731 os.chdir(cwd[-1])
784
732
785 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
733 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
786 path, lui = _getlocal(ui, rpath)
734 path, lui = _getlocal(ui, rpath)
787
735
788 # Now that we're operating in the right directory/repository with
736 # Now that we're operating in the right directory/repository with
789 # the right config settings, check for shell aliases
737 # the right config settings, check for shell aliases
790 shellaliasfn = _checkshellalias(lui, ui, args)
738 shellaliasfn = _checkshellalias(lui, ui, args)
791 if shellaliasfn:
739 if shellaliasfn:
792 return shellaliasfn()
740 return shellaliasfn()
793
741
794 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
742 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
795 # reposetup. Programs like TortoiseHg will call _dispatch several
743 # reposetup. Programs like TortoiseHg will call _dispatch several
796 # times so we keep track of configured extensions in _loaded.
744 # times so we keep track of configured extensions in _loaded.
797 extensions.loadall(lui)
745 extensions.loadall(lui)
798 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
746 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
799 # Propagate any changes to lui.__class__ by extensions
747 # Propagate any changes to lui.__class__ by extensions
800 ui.__class__ = lui.__class__
748 ui.__class__ = lui.__class__
801
749
802 # (uisetup and extsetup are handled in extensions.loadall)
750 # (uisetup and extsetup are handled in extensions.loadall)
803
751
804 for name, module in exts:
752 for name, module in exts:
805 for objname, loadermod, loadername in extraloaders:
753 for objname, loadermod, loadername in extraloaders:
806 extraobj = getattr(module, objname, None)
754 extraobj = getattr(module, objname, None)
807 if extraobj is not None:
755 if extraobj is not None:
808 getattr(loadermod, loadername)(ui, name, extraobj)
756 getattr(loadermod, loadername)(ui, name, extraobj)
809 _loaded.add(name)
757 _loaded.add(name)
810
758
811 # (reposetup is handled in hg.repository)
759 # (reposetup is handled in hg.repository)
812
760
813 addaliases(lui, commands.table)
761 addaliases(lui, commands.table)
814
762
815 if not lui.configbool("ui", "strict"):
763 if not lui.configbool("ui", "strict"):
816 # All aliases and commands are completely defined, now.
764 # All aliases and commands are completely defined, now.
817 # Check abbreviation/ambiguity of shell alias again, because shell
765 # Check abbreviation/ambiguity of shell alias again, because shell
818 # alias may cause failure of "_parse" (see issue4355)
766 # alias may cause failure of "_parse" (see issue4355)
819 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
767 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
820 if shellaliasfn:
768 if shellaliasfn:
821 return shellaliasfn()
769 return shellaliasfn()
822
770
823 # check for fallback encoding
771 # check for fallback encoding
824 fallback = lui.config('ui', 'fallbackencoding')
772 fallback = lui.config('ui', 'fallbackencoding')
825 if fallback:
773 if fallback:
826 encoding.fallbackencoding = fallback
774 encoding.fallbackencoding = fallback
827
775
828 fullargs = args
776 fullargs = args
829 cmd, func, args, options, cmdoptions = _parse(lui, args)
777 cmd, func, args, options, cmdoptions = _parse(lui, args)
830
778
831 if options["config"]:
779 if options["config"]:
832 raise error.Abort(_("option --config may not be abbreviated!"))
780 raise error.Abort(_("option --config may not be abbreviated!"))
833 if options["cwd"]:
781 if options["cwd"]:
834 raise error.Abort(_("option --cwd may not be abbreviated!"))
782 raise error.Abort(_("option --cwd may not be abbreviated!"))
835 if options["repository"]:
783 if options["repository"]:
836 raise error.Abort(_(
784 raise error.Abort(_(
837 "option -R has to be separated from other options (e.g. not -qR) "
785 "option -R has to be separated from other options (e.g. not -qR) "
838 "and --repository may only be abbreviated as --repo!"))
786 "and --repository may only be abbreviated as --repo!"))
839
787
840 if options["encoding"]:
788 if options["encoding"]:
841 encoding.encoding = options["encoding"]
789 encoding.encoding = options["encoding"]
842 if options["encodingmode"]:
790 if options["encodingmode"]:
843 encoding.encodingmode = options["encodingmode"]
791 encoding.encodingmode = options["encodingmode"]
844 if options["time"]:
792 if options["time"]:
845 def get_times():
793 def get_times():
846 t = os.times()
794 t = os.times()
847 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
795 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
848 t = (t[0], t[1], t[2], t[3], time.clock())
796 t = (t[0], t[1], t[2], t[3], time.clock())
849 return t
797 return t
850 s = get_times()
798 s = get_times()
851 def print_time():
799 def print_time():
852 t = get_times()
800 t = get_times()
853 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
801 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
854 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
802 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
855 atexit.register(print_time)
803 atexit.register(print_time)
856
804
857 uis = set([ui, lui])
805 uis = set([ui, lui])
858
806
859 if req.repo:
807 if req.repo:
860 uis.add(req.repo.ui)
808 uis.add(req.repo.ui)
861
809
862 if options['verbose'] or options['debug'] or options['quiet']:
810 if options['verbose'] or options['debug'] or options['quiet']:
863 for opt in ('verbose', 'debug', 'quiet'):
811 for opt in ('verbose', 'debug', 'quiet'):
864 val = str(bool(options[opt]))
812 val = str(bool(options[opt]))
865 for ui_ in uis:
813 for ui_ in uis:
866 ui_.setconfig('ui', opt, val, '--' + opt)
814 ui_.setconfig('ui', opt, val, '--' + opt)
867
815
868 if options['traceback']:
816 if options['traceback']:
869 for ui_ in uis:
817 for ui_ in uis:
870 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
818 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
871
819
872 if options['noninteractive']:
820 if options['noninteractive']:
873 for ui_ in uis:
821 for ui_ in uis:
874 ui_.setconfig('ui', 'interactive', 'off', '-y')
822 ui_.setconfig('ui', 'interactive', 'off', '-y')
875
823
876 if cmdoptions.get('insecure', False):
824 if cmdoptions.get('insecure', False):
877 for ui_ in uis:
825 for ui_ in uis:
878 ui_.setconfig('web', 'cacerts', '!', '--insecure')
826 ui_.setconfig('web', 'cacerts', '!', '--insecure')
879
827
880 if options['version']:
828 if options['version']:
881 return commands.version_(ui)
829 return commands.version_(ui)
882 if options['help']:
830 if options['help']:
883 return commands.help_(ui, cmd, command=cmd is not None)
831 return commands.help_(ui, cmd, command=cmd is not None)
884 elif not cmd:
832 elif not cmd:
885 return commands.help_(ui, 'shortlist')
833 return commands.help_(ui, 'shortlist')
886
834
887 repo = None
835 repo = None
888 cmdpats = args[:]
836 cmdpats = args[:]
889 if not _cmdattr(ui, cmd, func, 'norepo'):
837 if not _cmdattr(ui, cmd, func, 'norepo'):
890 # use the repo from the request only if we don't have -R
838 # use the repo from the request only if we don't have -R
891 if not rpath and not cwd:
839 if not rpath and not cwd:
892 repo = req.repo
840 repo = req.repo
893
841
894 if repo:
842 if repo:
895 # set the descriptors of the repo ui to those of ui
843 # set the descriptors of the repo ui to those of ui
896 repo.ui.fin = ui.fin
844 repo.ui.fin = ui.fin
897 repo.ui.fout = ui.fout
845 repo.ui.fout = ui.fout
898 repo.ui.ferr = ui.ferr
846 repo.ui.ferr = ui.ferr
899 else:
847 else:
900 try:
848 try:
901 repo = hg.repository(ui, path=path)
849 repo = hg.repository(ui, path=path)
902 if not repo.local():
850 if not repo.local():
903 raise error.Abort(_("repository '%s' is not local") % path)
851 raise error.Abort(_("repository '%s' is not local") % path)
904 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
852 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
905 except error.RequirementError:
853 except error.RequirementError:
906 raise
854 raise
907 except error.RepoError:
855 except error.RepoError:
908 if rpath and rpath[-1]: # invalid -R path
856 if rpath and rpath[-1]: # invalid -R path
909 raise
857 raise
910 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
858 if not _cmdattr(ui, cmd, func, 'optionalrepo'):
911 if (_cmdattr(ui, cmd, func, 'inferrepo') and
859 if (_cmdattr(ui, cmd, func, 'inferrepo') and
912 args and not path):
860 args and not path):
913 # try to infer -R from command args
861 # try to infer -R from command args
914 repos = map(cmdutil.findrepo, args)
862 repos = map(cmdutil.findrepo, args)
915 guess = repos[0]
863 guess = repos[0]
916 if guess and repos.count(guess) == len(repos):
864 if guess and repos.count(guess) == len(repos):
917 req.args = ['--repository', guess] + fullargs
865 req.args = ['--repository', guess] + fullargs
918 return _dispatch(req)
866 return _dispatch(req)
919 if not path:
867 if not path:
920 raise error.RepoError(_("no repository found in '%s'"
868 raise error.RepoError(_("no repository found in '%s'"
921 " (.hg not found)")
869 " (.hg not found)")
922 % os.getcwd())
870 % os.getcwd())
923 raise
871 raise
924 if repo:
872 if repo:
925 ui = repo.ui
873 ui = repo.ui
926 if options['hidden']:
874 if options['hidden']:
927 repo = repo.unfiltered()
875 repo = repo.unfiltered()
928 args.insert(0, repo)
876 args.insert(0, repo)
929 elif rpath:
877 elif rpath:
930 ui.warn(_("warning: --repository ignored\n"))
878 ui.warn(_("warning: --repository ignored\n"))
931
879
932 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
880 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
933 ui.log("command", '%s\n', msg)
881 ui.log("command", '%s\n', msg)
934 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
882 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
935 try:
883 try:
936 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
884 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
937 cmdpats, cmdoptions)
885 cmdpats, cmdoptions)
938 finally:
886 finally:
939 if repo and repo != req.repo:
887 if repo and repo != req.repo:
940 repo.close()
888 repo.close()
941
889
942 def lsprofile(ui, func, fp):
890 def lsprofile(ui, func, fp):
943 format = ui.config('profiling', 'format', default='text')
891 format = ui.config('profiling', 'format', default='text')
944 field = ui.config('profiling', 'sort', default='inlinetime')
892 field = ui.config('profiling', 'sort', default='inlinetime')
945 limit = ui.configint('profiling', 'limit', default=30)
893 limit = ui.configint('profiling', 'limit', default=30)
946 climit = ui.configint('profiling', 'nested', default=0)
894 climit = ui.configint('profiling', 'nested', default=0)
947
895
948 if format not in ['text', 'kcachegrind']:
896 if format not in ['text', 'kcachegrind']:
949 ui.warn(_("unrecognized profiling format '%s'"
897 ui.warn(_("unrecognized profiling format '%s'"
950 " - Ignored\n") % format)
898 " - Ignored\n") % format)
951 format = 'text'
899 format = 'text'
952
900
953 try:
901 try:
954 from . import lsprof
902 from . import lsprof
955 except ImportError:
903 except ImportError:
956 raise error.Abort(_(
904 raise error.Abort(_(
957 'lsprof not available - install from '
905 'lsprof not available - install from '
958 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
906 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
959 p = lsprof.Profiler()
907 p = lsprof.Profiler()
960 p.enable(subcalls=True)
908 p.enable(subcalls=True)
961 try:
909 try:
962 return func()
910 return func()
963 finally:
911 finally:
964 p.disable()
912 p.disable()
965
913
966 if format == 'kcachegrind':
914 if format == 'kcachegrind':
967 from . import lsprofcalltree
915 from . import lsprofcalltree
968 calltree = lsprofcalltree.KCacheGrind(p)
916 calltree = lsprofcalltree.KCacheGrind(p)
969 calltree.output(fp)
917 calltree.output(fp)
970 else:
918 else:
971 # format == 'text'
919 # format == 'text'
972 stats = lsprof.Stats(p.getstats())
920 stats = lsprof.Stats(p.getstats())
973 stats.sort(field)
921 stats.sort(field)
974 stats.pprint(limit=limit, file=fp, climit=climit)
922 stats.pprint(limit=limit, file=fp, climit=climit)
975
923
976 def flameprofile(ui, func, fp):
924 def flameprofile(ui, func, fp):
977 try:
925 try:
978 from flamegraph import flamegraph
926 from flamegraph import flamegraph
979 except ImportError:
927 except ImportError:
980 raise error.Abort(_(
928 raise error.Abort(_(
981 'flamegraph not available - install from '
929 'flamegraph not available - install from '
982 'https://github.com/evanhempel/python-flamegraph'))
930 'https://github.com/evanhempel/python-flamegraph'))
983 # developer config: profiling.freq
931 # developer config: profiling.freq
984 freq = ui.configint('profiling', 'freq', default=1000)
932 freq = ui.configint('profiling', 'freq', default=1000)
985 filter_ = None
933 filter_ = None
986 collapse_recursion = True
934 collapse_recursion = True
987 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
935 thread = flamegraph.ProfileThread(fp, 1.0 / freq,
988 filter_, collapse_recursion)
936 filter_, collapse_recursion)
989 start_time = time.clock()
937 start_time = time.clock()
990 try:
938 try:
991 thread.start()
939 thread.start()
992 func()
940 func()
993 finally:
941 finally:
994 thread.stop()
942 thread.stop()
995 thread.join()
943 thread.join()
996 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
944 print('Collected %d stack frames (%d unique) in %2.2f seconds.' % (
997 time.clock() - start_time, thread.num_frames(),
945 time.clock() - start_time, thread.num_frames(),
998 thread.num_frames(unique=True)))
946 thread.num_frames(unique=True)))
999
947
1000
948
1001 def statprofile(ui, func, fp):
949 def statprofile(ui, func, fp):
1002 try:
950 try:
1003 import statprof
951 import statprof
1004 except ImportError:
952 except ImportError:
1005 raise error.Abort(_(
953 raise error.Abort(_(
1006 'statprof not available - install using "easy_install statprof"'))
954 'statprof not available - install using "easy_install statprof"'))
1007
955
1008 freq = ui.configint('profiling', 'freq', default=1000)
956 freq = ui.configint('profiling', 'freq', default=1000)
1009 if freq > 0:
957 if freq > 0:
1010 statprof.reset(freq)
958 statprof.reset(freq)
1011 else:
959 else:
1012 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
960 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
1013
961
1014 statprof.start()
962 statprof.start()
1015 try:
963 try:
1016 return func()
964 return func()
1017 finally:
965 finally:
1018 statprof.stop()
966 statprof.stop()
1019 statprof.display(fp)
967 statprof.display(fp)
1020
968
1021 def _runcommand(ui, options, cmd, cmdfunc):
969 def _runcommand(ui, options, cmd, cmdfunc):
1022 """Enables the profiler if applicable.
970 """Enables the profiler if applicable.
1023
971
1024 ``profiling.enabled`` - boolean config that enables or disables profiling
972 ``profiling.enabled`` - boolean config that enables or disables profiling
1025 """
973 """
1026 def checkargs():
974 def checkargs():
1027 try:
975 try:
1028 return cmdfunc()
976 return cmdfunc()
1029 except error.SignatureError:
977 except error.SignatureError:
1030 raise error.CommandError(cmd, _("invalid arguments"))
978 raise error.CommandError(cmd, _("invalid arguments"))
1031
979
1032 if options['profile'] or ui.configbool('profiling', 'enabled'):
980 if options['profile'] or ui.configbool('profiling', 'enabled'):
1033 profiler = os.getenv('HGPROF')
981 profiler = os.getenv('HGPROF')
1034 if profiler is None:
982 if profiler is None:
1035 profiler = ui.config('profiling', 'type', default='ls')
983 profiler = ui.config('profiling', 'type', default='ls')
1036 if profiler not in ('ls', 'stat', 'flame'):
984 if profiler not in ('ls', 'stat', 'flame'):
1037 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
985 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
1038 profiler = 'ls'
986 profiler = 'ls'
1039
987
1040 output = ui.config('profiling', 'output')
988 output = ui.config('profiling', 'output')
1041
989
1042 if output == 'blackbox':
990 if output == 'blackbox':
1043 import StringIO
991 import StringIO
1044 fp = StringIO.StringIO()
992 fp = StringIO.StringIO()
1045 elif output:
993 elif output:
1046 path = ui.expandpath(output)
994 path = ui.expandpath(output)
1047 fp = open(path, 'wb')
995 fp = open(path, 'wb')
1048 else:
996 else:
1049 fp = sys.stderr
997 fp = sys.stderr
1050
998
1051 try:
999 try:
1052 if profiler == 'ls':
1000 if profiler == 'ls':
1053 return lsprofile(ui, checkargs, fp)
1001 return lsprofile(ui, checkargs, fp)
1054 elif profiler == 'flame':
1002 elif profiler == 'flame':
1055 return flameprofile(ui, checkargs, fp)
1003 return flameprofile(ui, checkargs, fp)
1056 else:
1004 else:
1057 return statprofile(ui, checkargs, fp)
1005 return statprofile(ui, checkargs, fp)
1058 finally:
1006 finally:
1059 if output:
1007 if output:
1060 if output == 'blackbox':
1008 if output == 'blackbox':
1061 val = "Profile:\n%s" % fp.getvalue()
1009 val = "Profile:\n%s" % fp.getvalue()
1062 # ui.log treats the input as a format string,
1010 # ui.log treats the input as a format string,
1063 # so we need to escape any % signs.
1011 # so we need to escape any % signs.
1064 val = val.replace('%', '%%')
1012 val = val.replace('%', '%%')
1065 ui.log('profile', val)
1013 ui.log('profile', val)
1066 fp.close()
1014 fp.close()
1067 else:
1015 else:
1068 return checkargs()
1016 return checkargs()
1017
1018 def handlecommandexception(ui):
1019 """Produce a warning message for broken commands
1020
1021 Called when handling an exception; the exception is reraised if
1022 this function returns False, ignored otherwise.
1023 """
1024 # For compatibility checking, we discard the portion of the hg
1025 # version after the + on the assumption that if a "normal
1026 # user" is running a build with a + in it the packager
1027 # probably built from fairly close to a tag and anyone with a
1028 # 'make local' copy of hg (where the version number can be out
1029 # of date) will be clueful enough to notice the implausible
1030 # version number and try updating.
1031 ct = util.versiontuple(n=2)
1032 worst = None, ct, ''
1033 if ui.config('ui', 'supportcontact', None) is None:
1034 for name, mod in extensions.extensions():
1035 testedwith = getattr(mod, 'testedwith', '')
1036 report = getattr(mod, 'buglink', _('the extension author.'))
1037 if not testedwith.strip():
1038 # We found an untested extension. It's likely the culprit.
1039 worst = name, 'unknown', report
1040 break
1041
1042 # Never blame on extensions bundled with Mercurial.
1043 if testedwith == 'internal':
1044 continue
1045
1046 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1047 if ct in tested:
1048 continue
1049
1050 lower = [t for t in tested if t < ct]
1051 nearest = max(lower or tested)
1052 if worst[0] is None or nearest < worst[1]:
1053 worst = name, nearest, report
1054 if worst[0] is not None:
1055 name, testedwith, report = worst
1056 if not isinstance(testedwith, str):
1057 testedwith = '.'.join([str(c) for c in testedwith])
1058 warning = (_('** Unknown exception encountered with '
1059 'possibly-broken third-party extension %s\n'
1060 '** which supports versions %s of Mercurial.\n'
1061 '** Please disable %s and try your action again.\n'
1062 '** If that fixes the bug please report it to %s\n')
1063 % (name, testedwith, name, report))
1064 else:
1065 bugtracker = ui.config('ui', 'supportcontact', None)
1066 if bugtracker is None:
1067 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
1068 warning = (_("** unknown exception encountered, "
1069 "please report by visiting\n** ") + bugtracker + '\n')
1070 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
1071 (_("** Mercurial Distributed SCM (version %s)\n") %
1072 util.version()) +
1073 (_("** Extensions loaded: %s\n") %
1074 ", ".join([x[0] for x in extensions.extensions()])))
1075 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1076 ui.warn(warning)
1077 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now