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