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