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