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