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