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