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