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