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