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