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