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