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