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