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