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