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