##// END OF EJS Templates
py3: make adefaults keys str to be compatible with getattr...
Pulkit Goyal -
r32158:89153b0d default
parent child Browse files
Show More
@@ -1,999 +1,1000
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 %d 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 = {r'norepo': True,
483 r'optionalrepo': False, r'inferrepo': False}
483 if name not in adefaults:
484 if name not in adefaults:
484 raise AttributeError(name)
485 raise AttributeError(name)
485 if self.badalias or util.safehasattr(self, 'shell'):
486 if self.badalias or util.safehasattr(self, 'shell'):
486 return adefaults[name]
487 return adefaults[name]
487 return getattr(self.fn, name)
488 return getattr(self.fn, name)
488
489
489 def __call__(self, ui, *args, **opts):
490 def __call__(self, ui, *args, **opts):
490 if self.badalias:
491 if self.badalias:
491 hint = None
492 hint = None
492 if self.unknowncmd:
493 if self.unknowncmd:
493 try:
494 try:
494 # check if the command is in a disabled extension
495 # check if the command is in a disabled extension
495 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
496 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
496 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
497 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
497 except error.UnknownCommand:
498 except error.UnknownCommand:
498 pass
499 pass
499 raise error.Abort(self.badalias, hint=hint)
500 raise error.Abort(self.badalias, hint=hint)
500 if self.shadows:
501 if self.shadows:
501 ui.debug("alias '%s' shadows command '%s'\n" %
502 ui.debug("alias '%s' shadows command '%s'\n" %
502 (self.name, self.cmdname))
503 (self.name, self.cmdname))
503
504
504 ui.log('commandalias', "alias '%s' expands to '%s'\n",
505 ui.log('commandalias', "alias '%s' expands to '%s'\n",
505 self.name, self.definition)
506 self.name, self.definition)
506 if util.safehasattr(self, 'shell'):
507 if util.safehasattr(self, 'shell'):
507 return self.fn(ui, *args, **opts)
508 return self.fn(ui, *args, **opts)
508 else:
509 else:
509 try:
510 try:
510 return util.checksignature(self.fn)(ui, *args, **opts)
511 return util.checksignature(self.fn)(ui, *args, **opts)
511 except error.SignatureError:
512 except error.SignatureError:
512 args = ' '.join([self.cmdname] + self.args)
513 args = ' '.join([self.cmdname] + self.args)
513 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
514 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
514 raise
515 raise
515
516
516 def addaliases(ui, cmdtable):
517 def addaliases(ui, cmdtable):
517 # aliases are processed after extensions have been loaded, so they
518 # aliases are processed after extensions have been loaded, so they
518 # may use extension commands. Aliases can also use other alias definitions,
519 # may use extension commands. Aliases can also use other alias definitions,
519 # but only if they have been defined prior to the current definition.
520 # but only if they have been defined prior to the current definition.
520 for alias, definition in ui.configitems('alias'):
521 for alias, definition in ui.configitems('alias'):
521 source = ui.configsource('alias', alias)
522 source = ui.configsource('alias', alias)
522 aliasdef = cmdalias(alias, definition, cmdtable, source)
523 aliasdef = cmdalias(alias, definition, cmdtable, source)
523
524
524 try:
525 try:
525 olddef = cmdtable[aliasdef.cmd][0]
526 olddef = cmdtable[aliasdef.cmd][0]
526 if olddef.definition == aliasdef.definition:
527 if olddef.definition == aliasdef.definition:
527 continue
528 continue
528 except (KeyError, AttributeError):
529 except (KeyError, AttributeError):
529 # definition might not exist or it might not be a cmdalias
530 # definition might not exist or it might not be a cmdalias
530 pass
531 pass
531
532
532 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
533 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
533
534
534 def _parse(ui, args):
535 def _parse(ui, args):
535 options = {}
536 options = {}
536 cmdoptions = {}
537 cmdoptions = {}
537
538
538 try:
539 try:
539 args = fancyopts.fancyopts(args, commands.globalopts, options)
540 args = fancyopts.fancyopts(args, commands.globalopts, options)
540 except getopt.GetoptError as inst:
541 except getopt.GetoptError as inst:
541 raise error.CommandError(None, inst)
542 raise error.CommandError(None, inst)
542
543
543 if args:
544 if args:
544 cmd, args = args[0], args[1:]
545 cmd, args = args[0], args[1:]
545 aliases, entry = cmdutil.findcmd(cmd, commands.table,
546 aliases, entry = cmdutil.findcmd(cmd, commands.table,
546 ui.configbool("ui", "strict"))
547 ui.configbool("ui", "strict"))
547 cmd = aliases[0]
548 cmd = aliases[0]
548 args = aliasargs(entry[0], args)
549 args = aliasargs(entry[0], args)
549 defaults = ui.config("defaults", cmd)
550 defaults = ui.config("defaults", cmd)
550 if defaults:
551 if defaults:
551 args = pycompat.maplist(
552 args = pycompat.maplist(
552 util.expandpath, pycompat.shlexsplit(defaults)) + args
553 util.expandpath, pycompat.shlexsplit(defaults)) + args
553 c = list(entry[1])
554 c = list(entry[1])
554 else:
555 else:
555 cmd = None
556 cmd = None
556 c = []
557 c = []
557
558
558 # combine global options into local
559 # combine global options into local
559 for o in commands.globalopts:
560 for o in commands.globalopts:
560 c.append((o[0], o[1], options[o[1]], o[3]))
561 c.append((o[0], o[1], options[o[1]], o[3]))
561
562
562 try:
563 try:
563 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
564 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
564 except getopt.GetoptError as inst:
565 except getopt.GetoptError as inst:
565 raise error.CommandError(cmd, inst)
566 raise error.CommandError(cmd, inst)
566
567
567 # separate global options back out
568 # separate global options back out
568 for o in commands.globalopts:
569 for o in commands.globalopts:
569 n = o[1]
570 n = o[1]
570 options[n] = cmdoptions[n]
571 options[n] = cmdoptions[n]
571 del cmdoptions[n]
572 del cmdoptions[n]
572
573
573 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
574 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
574
575
575 def _parseconfig(ui, config):
576 def _parseconfig(ui, config):
576 """parse the --config options from the command line"""
577 """parse the --config options from the command line"""
577 configs = []
578 configs = []
578
579
579 for cfg in config:
580 for cfg in config:
580 try:
581 try:
581 name, value = [cfgelem.strip()
582 name, value = [cfgelem.strip()
582 for cfgelem in cfg.split('=', 1)]
583 for cfgelem in cfg.split('=', 1)]
583 section, name = name.split('.', 1)
584 section, name = name.split('.', 1)
584 if not section or not name:
585 if not section or not name:
585 raise IndexError
586 raise IndexError
586 ui.setconfig(section, name, value, '--config')
587 ui.setconfig(section, name, value, '--config')
587 configs.append((section, name, value))
588 configs.append((section, name, value))
588 except (IndexError, ValueError):
589 except (IndexError, ValueError):
589 raise error.Abort(_('malformed --config option: %r '
590 raise error.Abort(_('malformed --config option: %r '
590 '(use --config section.name=value)') % cfg)
591 '(use --config section.name=value)') % cfg)
591
592
592 return configs
593 return configs
593
594
594 def _earlygetopt(aliases, args):
595 def _earlygetopt(aliases, args):
595 """Return list of values for an option (or aliases).
596 """Return list of values for an option (or aliases).
596
597
597 The values are listed in the order they appear in args.
598 The values are listed in the order they appear in args.
598 The options and values are removed from args.
599 The options and values are removed from args.
599
600
600 >>> args = ['x', '--cwd', 'foo', 'y']
601 >>> args = ['x', '--cwd', 'foo', 'y']
601 >>> _earlygetopt(['--cwd'], args), args
602 >>> _earlygetopt(['--cwd'], args), args
602 (['foo'], ['x', 'y'])
603 (['foo'], ['x', 'y'])
603
604
604 >>> args = ['x', '--cwd=bar', 'y']
605 >>> args = ['x', '--cwd=bar', 'y']
605 >>> _earlygetopt(['--cwd'], args), args
606 >>> _earlygetopt(['--cwd'], args), args
606 (['bar'], ['x', 'y'])
607 (['bar'], ['x', 'y'])
607
608
608 >>> args = ['x', '-R', 'foo', 'y']
609 >>> args = ['x', '-R', 'foo', 'y']
609 >>> _earlygetopt(['-R'], args), args
610 >>> _earlygetopt(['-R'], args), args
610 (['foo'], ['x', 'y'])
611 (['foo'], ['x', 'y'])
611
612
612 >>> args = ['x', '-Rbar', 'y']
613 >>> args = ['x', '-Rbar', 'y']
613 >>> _earlygetopt(['-R'], args), args
614 >>> _earlygetopt(['-R'], args), args
614 (['bar'], ['x', 'y'])
615 (['bar'], ['x', 'y'])
615 """
616 """
616 try:
617 try:
617 argcount = args.index("--")
618 argcount = args.index("--")
618 except ValueError:
619 except ValueError:
619 argcount = len(args)
620 argcount = len(args)
620 shortopts = [opt for opt in aliases if len(opt) == 2]
621 shortopts = [opt for opt in aliases if len(opt) == 2]
621 values = []
622 values = []
622 pos = 0
623 pos = 0
623 while pos < argcount:
624 while pos < argcount:
624 fullarg = arg = args[pos]
625 fullarg = arg = args[pos]
625 equals = arg.find('=')
626 equals = arg.find('=')
626 if equals > -1:
627 if equals > -1:
627 arg = arg[:equals]
628 arg = arg[:equals]
628 if arg in aliases:
629 if arg in aliases:
629 del args[pos]
630 del args[pos]
630 if equals > -1:
631 if equals > -1:
631 values.append(fullarg[equals + 1:])
632 values.append(fullarg[equals + 1:])
632 argcount -= 1
633 argcount -= 1
633 else:
634 else:
634 if pos + 1 >= argcount:
635 if pos + 1 >= argcount:
635 # ignore and let getopt report an error if there is no value
636 # ignore and let getopt report an error if there is no value
636 break
637 break
637 values.append(args.pop(pos))
638 values.append(args.pop(pos))
638 argcount -= 2
639 argcount -= 2
639 elif arg[:2] in shortopts:
640 elif arg[:2] in shortopts:
640 # short option can have no following space, e.g. hg log -Rfoo
641 # short option can have no following space, e.g. hg log -Rfoo
641 values.append(args.pop(pos)[2:])
642 values.append(args.pop(pos)[2:])
642 argcount -= 1
643 argcount -= 1
643 else:
644 else:
644 pos += 1
645 pos += 1
645 return values
646 return values
646
647
647 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
648 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
648 # run pre-hook, and abort if it fails
649 # run pre-hook, and abort if it fails
649 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
650 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
650 pats=cmdpats, opts=cmdoptions)
651 pats=cmdpats, opts=cmdoptions)
651 try:
652 try:
652 ret = _runcommand(ui, options, cmd, d)
653 ret = _runcommand(ui, options, cmd, d)
653 # run post-hook, passing command result
654 # run post-hook, passing command result
654 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
655 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
655 result=ret, pats=cmdpats, opts=cmdoptions)
656 result=ret, pats=cmdpats, opts=cmdoptions)
656 except Exception:
657 except Exception:
657 # run failure hook and re-raise
658 # run failure hook and re-raise
658 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
659 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
659 pats=cmdpats, opts=cmdoptions)
660 pats=cmdpats, opts=cmdoptions)
660 raise
661 raise
661 return ret
662 return ret
662
663
663 def _getlocal(ui, rpath, wd=None):
664 def _getlocal(ui, rpath, wd=None):
664 """Return (path, local ui object) for the given target path.
665 """Return (path, local ui object) for the given target path.
665
666
666 Takes paths in [cwd]/.hg/hgrc into account."
667 Takes paths in [cwd]/.hg/hgrc into account."
667 """
668 """
668 if wd is None:
669 if wd is None:
669 try:
670 try:
670 wd = pycompat.getcwd()
671 wd = pycompat.getcwd()
671 except OSError as e:
672 except OSError as e:
672 raise error.Abort(_("error getting current working directory: %s") %
673 raise error.Abort(_("error getting current working directory: %s") %
673 e.strerror)
674 e.strerror)
674 path = cmdutil.findrepo(wd) or ""
675 path = cmdutil.findrepo(wd) or ""
675 if not path:
676 if not path:
676 lui = ui
677 lui = ui
677 else:
678 else:
678 lui = ui.copy()
679 lui = ui.copy()
679 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
680 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
680
681
681 if rpath and rpath[-1]:
682 if rpath and rpath[-1]:
682 path = lui.expandpath(rpath[-1])
683 path = lui.expandpath(rpath[-1])
683 lui = ui.copy()
684 lui = ui.copy()
684 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
685 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
685
686
686 return path, lui
687 return path, lui
687
688
688 def _checkshellalias(lui, ui, args):
689 def _checkshellalias(lui, ui, args):
689 """Return the function to run the shell alias, if it is required"""
690 """Return the function to run the shell alias, if it is required"""
690 options = {}
691 options = {}
691
692
692 try:
693 try:
693 args = fancyopts.fancyopts(args, commands.globalopts, options)
694 args = fancyopts.fancyopts(args, commands.globalopts, options)
694 except getopt.GetoptError:
695 except getopt.GetoptError:
695 return
696 return
696
697
697 if not args:
698 if not args:
698 return
699 return
699
700
700 cmdtable = commands.table
701 cmdtable = commands.table
701
702
702 cmd = args[0]
703 cmd = args[0]
703 try:
704 try:
704 strict = ui.configbool("ui", "strict")
705 strict = ui.configbool("ui", "strict")
705 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
706 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
706 except (error.AmbiguousCommand, error.UnknownCommand):
707 except (error.AmbiguousCommand, error.UnknownCommand):
707 return
708 return
708
709
709 cmd = aliases[0]
710 cmd = aliases[0]
710 fn = entry[0]
711 fn = entry[0]
711
712
712 if cmd and util.safehasattr(fn, 'shell'):
713 if cmd and util.safehasattr(fn, 'shell'):
713 d = lambda: fn(ui, *args[1:])
714 d = lambda: fn(ui, *args[1:])
714 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
715 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
715 [], {})
716 [], {})
716
717
717 _loaded = set()
718 _loaded = set()
718
719
719 # list of (objname, loadermod, loadername) tuple:
720 # list of (objname, loadermod, loadername) tuple:
720 # - objname is the name of an object in extension module, from which
721 # - objname is the name of an object in extension module, from which
721 # extra information is loaded
722 # extra information is loaded
722 # - loadermod is the module where loader is placed
723 # - loadermod is the module where loader is placed
723 # - loadername is the name of the function, which takes (ui, extensionname,
724 # - loadername is the name of the function, which takes (ui, extensionname,
724 # extraobj) arguments
725 # extraobj) arguments
725 extraloaders = [
726 extraloaders = [
726 ('cmdtable', commands, 'loadcmdtable'),
727 ('cmdtable', commands, 'loadcmdtable'),
727 ('colortable', color, 'loadcolortable'),
728 ('colortable', color, 'loadcolortable'),
728 ('filesetpredicate', fileset, 'loadpredicate'),
729 ('filesetpredicate', fileset, 'loadpredicate'),
729 ('revsetpredicate', revset, 'loadpredicate'),
730 ('revsetpredicate', revset, 'loadpredicate'),
730 ('templatefilter', templatefilters, 'loadfilter'),
731 ('templatefilter', templatefilters, 'loadfilter'),
731 ('templatefunc', templater, 'loadfunction'),
732 ('templatefunc', templater, 'loadfunction'),
732 ('templatekeyword', templatekw, 'loadkeyword'),
733 ('templatekeyword', templatekw, 'loadkeyword'),
733 ]
734 ]
734
735
735 def _dispatch(req):
736 def _dispatch(req):
736 args = req.args
737 args = req.args
737 ui = req.ui
738 ui = req.ui
738
739
739 # check for cwd
740 # check for cwd
740 cwd = _earlygetopt(['--cwd'], args)
741 cwd = _earlygetopt(['--cwd'], args)
741 if cwd:
742 if cwd:
742 os.chdir(cwd[-1])
743 os.chdir(cwd[-1])
743
744
744 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
745 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
745 path, lui = _getlocal(ui, rpath)
746 path, lui = _getlocal(ui, rpath)
746
747
747 # Side-effect of accessing is debugcommands module is guaranteed to be
748 # Side-effect of accessing is debugcommands module is guaranteed to be
748 # imported and commands.table is populated.
749 # imported and commands.table is populated.
749 debugcommands.command
750 debugcommands.command
750
751
751 uis = set([ui, lui])
752 uis = set([ui, lui])
752
753
753 if req.repo:
754 if req.repo:
754 uis.add(req.repo.ui)
755 uis.add(req.repo.ui)
755
756
756 if '--profile' in args:
757 if '--profile' in args:
757 for ui_ in uis:
758 for ui_ in uis:
758 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
759 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
759
760
760 with profiling.maybeprofile(lui):
761 with profiling.maybeprofile(lui):
761 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
762 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
762 # reposetup. Programs like TortoiseHg will call _dispatch several
763 # reposetup. Programs like TortoiseHg will call _dispatch several
763 # times so we keep track of configured extensions in _loaded.
764 # times so we keep track of configured extensions in _loaded.
764 extensions.loadall(lui)
765 extensions.loadall(lui)
765 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
766 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
766 # Propagate any changes to lui.__class__ by extensions
767 # Propagate any changes to lui.__class__ by extensions
767 ui.__class__ = lui.__class__
768 ui.__class__ = lui.__class__
768
769
769 # (uisetup and extsetup are handled in extensions.loadall)
770 # (uisetup and extsetup are handled in extensions.loadall)
770
771
771 for name, module in exts:
772 for name, module in exts:
772 for objname, loadermod, loadername in extraloaders:
773 for objname, loadermod, loadername in extraloaders:
773 extraobj = getattr(module, objname, None)
774 extraobj = getattr(module, objname, None)
774 if extraobj is not None:
775 if extraobj is not None:
775 getattr(loadermod, loadername)(ui, name, extraobj)
776 getattr(loadermod, loadername)(ui, name, extraobj)
776 _loaded.add(name)
777 _loaded.add(name)
777
778
778 # (reposetup is handled in hg.repository)
779 # (reposetup is handled in hg.repository)
779
780
780 addaliases(lui, commands.table)
781 addaliases(lui, commands.table)
781
782
782 # All aliases and commands are completely defined, now.
783 # All aliases and commands are completely defined, now.
783 # Check abbreviation/ambiguity of shell alias.
784 # Check abbreviation/ambiguity of shell alias.
784 shellaliasfn = _checkshellalias(lui, ui, args)
785 shellaliasfn = _checkshellalias(lui, ui, args)
785 if shellaliasfn:
786 if shellaliasfn:
786 return shellaliasfn()
787 return shellaliasfn()
787
788
788 # check for fallback encoding
789 # check for fallback encoding
789 fallback = lui.config('ui', 'fallbackencoding')
790 fallback = lui.config('ui', 'fallbackencoding')
790 if fallback:
791 if fallback:
791 encoding.fallbackencoding = fallback
792 encoding.fallbackencoding = fallback
792
793
793 fullargs = args
794 fullargs = args
794 cmd, func, args, options, cmdoptions = _parse(lui, args)
795 cmd, func, args, options, cmdoptions = _parse(lui, args)
795
796
796 if options["config"]:
797 if options["config"]:
797 raise error.Abort(_("option --config may not be abbreviated!"))
798 raise error.Abort(_("option --config may not be abbreviated!"))
798 if options["cwd"]:
799 if options["cwd"]:
799 raise error.Abort(_("option --cwd may not be abbreviated!"))
800 raise error.Abort(_("option --cwd may not be abbreviated!"))
800 if options["repository"]:
801 if options["repository"]:
801 raise error.Abort(_(
802 raise error.Abort(_(
802 "option -R has to be separated from other options (e.g. not "
803 "option -R has to be separated from other options (e.g. not "
803 "-qR) and --repository may only be abbreviated as --repo!"))
804 "-qR) and --repository may only be abbreviated as --repo!"))
804
805
805 if options["encoding"]:
806 if options["encoding"]:
806 encoding.encoding = options["encoding"]
807 encoding.encoding = options["encoding"]
807 if options["encodingmode"]:
808 if options["encodingmode"]:
808 encoding.encodingmode = options["encodingmode"]
809 encoding.encodingmode = options["encodingmode"]
809 if options["time"]:
810 if options["time"]:
810 def get_times():
811 def get_times():
811 t = os.times()
812 t = os.times()
812 if t[4] == 0.0:
813 if t[4] == 0.0:
813 # Windows leaves this as zero, so use time.clock()
814 # Windows leaves this as zero, so use time.clock()
814 t = (t[0], t[1], t[2], t[3], time.clock())
815 t = (t[0], t[1], t[2], t[3], time.clock())
815 return t
816 return t
816 s = get_times()
817 s = get_times()
817 def print_time():
818 def print_time():
818 t = get_times()
819 t = get_times()
819 ui.warn(
820 ui.warn(
820 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
821 _("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]))
822 (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)
823 ui.atexit(print_time)
823
824
824 if options['verbose'] or options['debug'] or options['quiet']:
825 if options['verbose'] or options['debug'] or options['quiet']:
825 for opt in ('verbose', 'debug', 'quiet'):
826 for opt in ('verbose', 'debug', 'quiet'):
826 val = str(bool(options[opt]))
827 val = str(bool(options[opt]))
827 if pycompat.ispy3:
828 if pycompat.ispy3:
828 val = val.encode('ascii')
829 val = val.encode('ascii')
829 for ui_ in uis:
830 for ui_ in uis:
830 ui_.setconfig('ui', opt, val, '--' + opt)
831 ui_.setconfig('ui', opt, val, '--' + opt)
831
832
832 if options['traceback']:
833 if options['traceback']:
833 for ui_ in uis:
834 for ui_ in uis:
834 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
835 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
835
836
836 if options['noninteractive']:
837 if options['noninteractive']:
837 for ui_ in uis:
838 for ui_ in uis:
838 ui_.setconfig('ui', 'interactive', 'off', '-y')
839 ui_.setconfig('ui', 'interactive', 'off', '-y')
839
840
840 if util.parsebool(options['pager']):
841 if util.parsebool(options['pager']):
841 ui.pager('internal-always-' + cmd)
842 ui.pager('internal-always-' + cmd)
842 elif options['pager'] != 'auto':
843 elif options['pager'] != 'auto':
843 ui.disablepager()
844 ui.disablepager()
844
845
845 if cmdoptions.get('insecure', False):
846 if cmdoptions.get('insecure', False):
846 for ui_ in uis:
847 for ui_ in uis:
847 ui_.insecureconnections = True
848 ui_.insecureconnections = True
848
849
849 # setup color handling
850 # setup color handling
850 coloropt = options['color']
851 coloropt = options['color']
851 for ui_ in uis:
852 for ui_ in uis:
852 if coloropt:
853 if coloropt:
853 ui_.setconfig('ui', 'color', coloropt, '--color')
854 ui_.setconfig('ui', 'color', coloropt, '--color')
854 color.setup(ui_)
855 color.setup(ui_)
855
856
856 if options['version']:
857 if options['version']:
857 return commands.version_(ui)
858 return commands.version_(ui)
858 if options['help']:
859 if options['help']:
859 return commands.help_(ui, cmd, command=cmd is not None)
860 return commands.help_(ui, cmd, command=cmd is not None)
860 elif not cmd:
861 elif not cmd:
861 return commands.help_(ui, 'shortlist')
862 return commands.help_(ui, 'shortlist')
862
863
863 repo = None
864 repo = None
864 cmdpats = args[:]
865 cmdpats = args[:]
865 if not func.norepo:
866 if not func.norepo:
866 # use the repo from the request only if we don't have -R
867 # use the repo from the request only if we don't have -R
867 if not rpath and not cwd:
868 if not rpath and not cwd:
868 repo = req.repo
869 repo = req.repo
869
870
870 if repo:
871 if repo:
871 # set the descriptors of the repo ui to those of ui
872 # set the descriptors of the repo ui to those of ui
872 repo.ui.fin = ui.fin
873 repo.ui.fin = ui.fin
873 repo.ui.fout = ui.fout
874 repo.ui.fout = ui.fout
874 repo.ui.ferr = ui.ferr
875 repo.ui.ferr = ui.ferr
875 else:
876 else:
876 try:
877 try:
877 repo = hg.repository(ui, path=path)
878 repo = hg.repository(ui, path=path)
878 if not repo.local():
879 if not repo.local():
879 raise error.Abort(_("repository '%s' is not local")
880 raise error.Abort(_("repository '%s' is not local")
880 % path)
881 % path)
881 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
882 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
882 'repo')
883 'repo')
883 except error.RequirementError:
884 except error.RequirementError:
884 raise
885 raise
885 except error.RepoError:
886 except error.RepoError:
886 if rpath and rpath[-1]: # invalid -R path
887 if rpath and rpath[-1]: # invalid -R path
887 raise
888 raise
888 if not func.optionalrepo:
889 if not func.optionalrepo:
889 if func.inferrepo and args and not path:
890 if func.inferrepo and args and not path:
890 # try to infer -R from command args
891 # try to infer -R from command args
891 repos = map(cmdutil.findrepo, args)
892 repos = map(cmdutil.findrepo, args)
892 guess = repos[0]
893 guess = repos[0]
893 if guess and repos.count(guess) == len(repos):
894 if guess and repos.count(guess) == len(repos):
894 req.args = ['--repository', guess] + fullargs
895 req.args = ['--repository', guess] + fullargs
895 return _dispatch(req)
896 return _dispatch(req)
896 if not path:
897 if not path:
897 raise error.RepoError(_("no repository found in"
898 raise error.RepoError(_("no repository found in"
898 " '%s' (.hg not found)")
899 " '%s' (.hg not found)")
899 % pycompat.getcwd())
900 % pycompat.getcwd())
900 raise
901 raise
901 if repo:
902 if repo:
902 ui = repo.ui
903 ui = repo.ui
903 if options['hidden']:
904 if options['hidden']:
904 repo = repo.unfiltered()
905 repo = repo.unfiltered()
905 args.insert(0, repo)
906 args.insert(0, repo)
906 elif rpath:
907 elif rpath:
907 ui.warn(_("warning: --repository ignored\n"))
908 ui.warn(_("warning: --repository ignored\n"))
908
909
909 msg = _formatargs(fullargs)
910 msg = _formatargs(fullargs)
910 ui.log("command", '%s\n', msg)
911 ui.log("command", '%s\n', msg)
911 strcmdopt = pycompat.strkwargs(cmdoptions)
912 strcmdopt = pycompat.strkwargs(cmdoptions)
912 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
913 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
913 try:
914 try:
914 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
915 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
915 cmdpats, cmdoptions)
916 cmdpats, cmdoptions)
916 finally:
917 finally:
917 if repo and repo != req.repo:
918 if repo and repo != req.repo:
918 repo.close()
919 repo.close()
919
920
920 def _runcommand(ui, options, cmd, cmdfunc):
921 def _runcommand(ui, options, cmd, cmdfunc):
921 """Run a command function, possibly with profiling enabled."""
922 """Run a command function, possibly with profiling enabled."""
922 try:
923 try:
923 return cmdfunc()
924 return cmdfunc()
924 except error.SignatureError:
925 except error.SignatureError:
925 raise error.CommandError(cmd, _('invalid arguments'))
926 raise error.CommandError(cmd, _('invalid arguments'))
926
927
927 def _exceptionwarning(ui):
928 def _exceptionwarning(ui):
928 """Produce a warning message for the current active exception"""
929 """Produce a warning message for the current active exception"""
929
930
930 # For compatibility checking, we discard the portion of the hg
931 # For compatibility checking, we discard the portion of the hg
931 # version after the + on the assumption that if a "normal
932 # version after the + on the assumption that if a "normal
932 # user" is running a build with a + in it the packager
933 # user" is running a build with a + in it the packager
933 # probably built from fairly close to a tag and anyone with a
934 # 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
935 # 'make local' copy of hg (where the version number can be out
935 # of date) will be clueful enough to notice the implausible
936 # of date) will be clueful enough to notice the implausible
936 # version number and try updating.
937 # version number and try updating.
937 ct = util.versiontuple(n=2)
938 ct = util.versiontuple(n=2)
938 worst = None, ct, ''
939 worst = None, ct, ''
939 if ui.config('ui', 'supportcontact', None) is None:
940 if ui.config('ui', 'supportcontact', None) is None:
940 for name, mod in extensions.extensions():
941 for name, mod in extensions.extensions():
941 testedwith = getattr(mod, 'testedwith', '')
942 testedwith = getattr(mod, 'testedwith', '')
942 if pycompat.ispy3 and isinstance(testedwith, str):
943 if pycompat.ispy3 and isinstance(testedwith, str):
943 testedwith = testedwith.encode(u'utf-8')
944 testedwith = testedwith.encode(u'utf-8')
944 report = getattr(mod, 'buglink', _('the extension author.'))
945 report = getattr(mod, 'buglink', _('the extension author.'))
945 if not testedwith.strip():
946 if not testedwith.strip():
946 # We found an untested extension. It's likely the culprit.
947 # We found an untested extension. It's likely the culprit.
947 worst = name, 'unknown', report
948 worst = name, 'unknown', report
948 break
949 break
949
950
950 # Never blame on extensions bundled with Mercurial.
951 # Never blame on extensions bundled with Mercurial.
951 if extensions.ismoduleinternal(mod):
952 if extensions.ismoduleinternal(mod):
952 continue
953 continue
953
954
954 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
955 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
955 if ct in tested:
956 if ct in tested:
956 continue
957 continue
957
958
958 lower = [t for t in tested if t < ct]
959 lower = [t for t in tested if t < ct]
959 nearest = max(lower or tested)
960 nearest = max(lower or tested)
960 if worst[0] is None or nearest < worst[1]:
961 if worst[0] is None or nearest < worst[1]:
961 worst = name, nearest, report
962 worst = name, nearest, report
962 if worst[0] is not None:
963 if worst[0] is not None:
963 name, testedwith, report = worst
964 name, testedwith, report = worst
964 if not isinstance(testedwith, (bytes, str)):
965 if not isinstance(testedwith, (bytes, str)):
965 testedwith = '.'.join([str(c) for c in testedwith])
966 testedwith = '.'.join([str(c) for c in testedwith])
966 warning = (_('** Unknown exception encountered with '
967 warning = (_('** Unknown exception encountered with '
967 'possibly-broken third-party extension %s\n'
968 'possibly-broken third-party extension %s\n'
968 '** which supports versions %s of Mercurial.\n'
969 '** which supports versions %s of Mercurial.\n'
969 '** Please disable %s and try your action again.\n'
970 '** Please disable %s and try your action again.\n'
970 '** If that fixes the bug please report it to %s\n')
971 '** If that fixes the bug please report it to %s\n')
971 % (name, testedwith, name, report))
972 % (name, testedwith, name, report))
972 else:
973 else:
973 bugtracker = ui.config('ui', 'supportcontact', None)
974 bugtracker = ui.config('ui', 'supportcontact', None)
974 if bugtracker is None:
975 if bugtracker is None:
975 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
976 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
976 warning = (_("** unknown exception encountered, "
977 warning = (_("** unknown exception encountered, "
977 "please report by visiting\n** ") + bugtracker + '\n')
978 "please report by visiting\n** ") + bugtracker + '\n')
978 if pycompat.ispy3:
979 if pycompat.ispy3:
979 sysversion = sys.version.encode(u'utf-8')
980 sysversion = sys.version.encode(u'utf-8')
980 else:
981 else:
981 sysversion = sys.version
982 sysversion = sys.version
982 sysversion = sysversion.replace('\n', '')
983 sysversion = sysversion.replace('\n', '')
983 warning += ((_("** Python %s\n") % sysversion) +
984 warning += ((_("** Python %s\n") % sysversion) +
984 (_("** Mercurial Distributed SCM (version %s)\n") %
985 (_("** Mercurial Distributed SCM (version %s)\n") %
985 util.version()) +
986 util.version()) +
986 (_("** Extensions loaded: %s\n") %
987 (_("** Extensions loaded: %s\n") %
987 ", ".join([x[0] for x in extensions.extensions()])))
988 ", ".join([x[0] for x in extensions.extensions()])))
988 return warning
989 return warning
989
990
990 def handlecommandexception(ui):
991 def handlecommandexception(ui):
991 """Produce a warning message for broken commands
992 """Produce a warning message for broken commands
992
993
993 Called when handling an exception; the exception is reraised if
994 Called when handling an exception; the exception is reraised if
994 this function returns False, ignored otherwise.
995 this function returns False, ignored otherwise.
995 """
996 """
996 warning = _exceptionwarning(ui)
997 warning = _exceptionwarning(ui)
997 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
998 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
998 ui.warn(warning)
999 ui.warn(warning)
999 return False # re-raise the exception
1000 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now