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