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