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