##// END OF EJS Templates
path: use `get_clone_path_obj` in _getlocal...
marmoute -
r50637:30eb36d9 default
parent child Browse files
Show More
@@ -1,1382 +1,1383 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, b'fout'):
110 if util.safehasattr(ui, b'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, b'ferr'):
119 if util.safehasattr(ui, b'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 b'SIGBREAK', b'SIGHUP', b'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 if (
370 if (
371 len(req.args) != 4
371 len(req.args) != 4
372 or req.args[0] != b'-R'
372 or req.args[0] != b'-R'
373 or req.args[1].startswith(b'--')
373 or req.args[1].startswith(b'--')
374 or req.args[2] != b'serve'
374 or req.args[2] != b'serve'
375 or req.args[3] != b'--stdio'
375 or req.args[3] != b'--stdio'
376 ):
376 ):
377 raise error.Abort(
377 raise error.Abort(
378 _(b'potentially unsafe serve --stdio invocation: %s')
378 _(b'potentially unsafe serve --stdio invocation: %s')
379 % (stringutil.pprint(req.args),)
379 % (stringutil.pprint(req.args),)
380 )
380 )
381
381
382 try:
382 try:
383 debugger = b'pdb'
383 debugger = b'pdb'
384 debugtrace = {b'pdb': pdb.set_trace}
384 debugtrace = {b'pdb': pdb.set_trace}
385 debugmortem = {b'pdb': pdb.post_mortem}
385 debugmortem = {b'pdb': pdb.post_mortem}
386
386
387 # read --config before doing anything else
387 # read --config before doing anything else
388 # (e.g. to change trust settings for reading .hg/hgrc)
388 # (e.g. to change trust settings for reading .hg/hgrc)
389 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
389 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
390
390
391 if req.repo:
391 if req.repo:
392 # copy configs that were passed on the cmdline (--config) to
392 # copy configs that were passed on the cmdline (--config) to
393 # the repo ui
393 # the repo ui
394 for sec, name, val in cfgs:
394 for sec, name, val in cfgs:
395 req.repo.ui.setconfig(
395 req.repo.ui.setconfig(
396 sec, name, val, source=b'--config'
396 sec, name, val, source=b'--config'
397 )
397 )
398
398
399 # developer config: ui.debugger
399 # developer config: ui.debugger
400 debugger = ui.config(b"ui", b"debugger")
400 debugger = ui.config(b"ui", b"debugger")
401 debugmod = pdb
401 debugmod = pdb
402 if not debugger or ui.plain():
402 if not debugger or ui.plain():
403 # if we are in HGPLAIN mode, then disable custom debugging
403 # if we are in HGPLAIN mode, then disable custom debugging
404 debugger = b'pdb'
404 debugger = b'pdb'
405 elif req.earlyoptions[b'debugger']:
405 elif req.earlyoptions[b'debugger']:
406 # This import can be slow for fancy debuggers, so only
406 # This import can be slow for fancy debuggers, so only
407 # do it when absolutely necessary, i.e. when actual
407 # do it when absolutely necessary, i.e. when actual
408 # debugging has been requested
408 # debugging has been requested
409 with demandimport.deactivated():
409 with demandimport.deactivated():
410 try:
410 try:
411 debugmod = __import__(debugger)
411 debugmod = __import__(debugger)
412 except ImportError:
412 except ImportError:
413 pass # Leave debugmod = pdb
413 pass # Leave debugmod = pdb
414
414
415 debugtrace[debugger] = debugmod.set_trace
415 debugtrace[debugger] = debugmod.set_trace
416 debugmortem[debugger] = debugmod.post_mortem
416 debugmortem[debugger] = debugmod.post_mortem
417
417
418 # enter the debugger before command execution
418 # enter the debugger before command execution
419 if req.earlyoptions[b'debugger']:
419 if req.earlyoptions[b'debugger']:
420 ui.warn(
420 ui.warn(
421 _(
421 _(
422 b"entering debugger - "
422 b"entering debugger - "
423 b"type c to continue starting hg or h for help\n"
423 b"type c to continue starting hg or h for help\n"
424 )
424 )
425 )
425 )
426
426
427 if (
427 if (
428 debugger != b'pdb'
428 debugger != b'pdb'
429 and debugtrace[debugger] == debugtrace[b'pdb']
429 and debugtrace[debugger] == debugtrace[b'pdb']
430 ):
430 ):
431 ui.warn(
431 ui.warn(
432 _(
432 _(
433 b"%s debugger specified "
433 b"%s debugger specified "
434 b"but its module was not found\n"
434 b"but its module was not found\n"
435 )
435 )
436 % debugger
436 % debugger
437 )
437 )
438 with demandimport.deactivated():
438 with demandimport.deactivated():
439 debugtrace[debugger]()
439 debugtrace[debugger]()
440 try:
440 try:
441 return _dispatch(req)
441 return _dispatch(req)
442 finally:
442 finally:
443 ui.flush()
443 ui.flush()
444 except: # re-raises
444 except: # re-raises
445 # enter the debugger when we hit an exception
445 # enter the debugger when we hit an exception
446 if req.earlyoptions[b'debugger']:
446 if req.earlyoptions[b'debugger']:
447 traceback.print_exc()
447 traceback.print_exc()
448 debugmortem[debugger](sys.exc_info()[2])
448 debugmortem[debugger](sys.exc_info()[2])
449 raise
449 raise
450
450
451 return _callcatch(ui, _runcatchfunc)
451 return _callcatch(ui, _runcatchfunc)
452
452
453
453
454 def _callcatch(ui, func):
454 def _callcatch(ui, func):
455 """like scmutil.callcatch but handles more high-level exceptions about
455 """like scmutil.callcatch but handles more high-level exceptions about
456 config parsing and commands. besides, use handlecommandexception to handle
456 config parsing and commands. besides, use handlecommandexception to handle
457 uncaught exceptions.
457 uncaught exceptions.
458 """
458 """
459 detailed_exit_code = -1
459 detailed_exit_code = -1
460 try:
460 try:
461 return scmutil.callcatch(ui, func)
461 return scmutil.callcatch(ui, func)
462 except error.AmbiguousCommand as inst:
462 except error.AmbiguousCommand as inst:
463 detailed_exit_code = 10
463 detailed_exit_code = 10
464 ui.warn(
464 ui.warn(
465 _(b"hg: command '%s' is ambiguous:\n %s\n")
465 _(b"hg: command '%s' is ambiguous:\n %s\n")
466 % (inst.prefix, b" ".join(inst.matches))
466 % (inst.prefix, b" ".join(inst.matches))
467 )
467 )
468 except error.CommandError as inst:
468 except error.CommandError as inst:
469 detailed_exit_code = 10
469 detailed_exit_code = 10
470 if inst.command:
470 if inst.command:
471 ui.pager(b'help')
471 ui.pager(b'help')
472 msgbytes = pycompat.bytestr(inst.message)
472 msgbytes = pycompat.bytestr(inst.message)
473 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
473 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
474 commands.help_(ui, inst.command, full=False, command=True)
474 commands.help_(ui, inst.command, full=False, command=True)
475 else:
475 else:
476 ui.warn(_(b"hg: %s\n") % inst.message)
476 ui.warn(_(b"hg: %s\n") % inst.message)
477 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
477 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
478 except error.UnknownCommand as inst:
478 except error.UnknownCommand as inst:
479 detailed_exit_code = 10
479 detailed_exit_code = 10
480 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
480 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
481 try:
481 try:
482 # check if the command is in a disabled extension
482 # check if the command is in a disabled extension
483 # (but don't check for extensions themselves)
483 # (but don't check for extensions themselves)
484 formatted = help.formattedhelp(
484 formatted = help.formattedhelp(
485 ui, commands, inst.command, unknowncmd=True
485 ui, commands, inst.command, unknowncmd=True
486 )
486 )
487 ui.warn(nocmdmsg)
487 ui.warn(nocmdmsg)
488 ui.write(formatted)
488 ui.write(formatted)
489 except (error.UnknownCommand, error.Abort):
489 except (error.UnknownCommand, error.Abort):
490 suggested = False
490 suggested = False
491 if inst.all_commands:
491 if inst.all_commands:
492 sim = error.getsimilar(inst.all_commands, inst.command)
492 sim = error.getsimilar(inst.all_commands, inst.command)
493 if sim:
493 if sim:
494 ui.warn(nocmdmsg)
494 ui.warn(nocmdmsg)
495 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
495 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
496 suggested = True
496 suggested = True
497 if not suggested:
497 if not suggested:
498 ui.warn(nocmdmsg)
498 ui.warn(nocmdmsg)
499 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
499 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
500 except IOError:
500 except IOError:
501 raise
501 raise
502 except KeyboardInterrupt:
502 except KeyboardInterrupt:
503 raise
503 raise
504 except: # probably re-raises
504 except: # probably re-raises
505 if not handlecommandexception(ui):
505 if not handlecommandexception(ui):
506 raise
506 raise
507
507
508 if ui.configbool(b'ui', b'detailed-exit-code'):
508 if ui.configbool(b'ui', b'detailed-exit-code'):
509 return detailed_exit_code
509 return detailed_exit_code
510 else:
510 else:
511 return -1
511 return -1
512
512
513
513
514 def aliasargs(fn, givenargs):
514 def aliasargs(fn, givenargs):
515 args = []
515 args = []
516 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
516 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
517 if not util.safehasattr(fn, b'_origfunc'):
517 if not util.safehasattr(fn, b'_origfunc'):
518 args = getattr(fn, 'args', args)
518 args = getattr(fn, 'args', args)
519 if args:
519 if args:
520 cmd = b' '.join(map(procutil.shellquote, args))
520 cmd = b' '.join(map(procutil.shellquote, args))
521
521
522 nums = []
522 nums = []
523
523
524 def replacer(m):
524 def replacer(m):
525 num = int(m.group(1)) - 1
525 num = int(m.group(1)) - 1
526 nums.append(num)
526 nums.append(num)
527 if num < len(givenargs):
527 if num < len(givenargs):
528 return givenargs[num]
528 return givenargs[num]
529 raise error.InputError(_(b'too few arguments for command alias'))
529 raise error.InputError(_(b'too few arguments for command alias'))
530
530
531 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
531 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
532 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
532 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
533 args = pycompat.shlexsplit(cmd)
533 args = pycompat.shlexsplit(cmd)
534 return args + givenargs
534 return args + givenargs
535
535
536
536
537 def aliasinterpolate(name, args, cmd):
537 def aliasinterpolate(name, args, cmd):
538 """interpolate args into cmd for shell aliases
538 """interpolate args into cmd for shell aliases
539
539
540 This also handles $0, $@ and "$@".
540 This also handles $0, $@ and "$@".
541 """
541 """
542 # util.interpolate can't deal with "$@" (with quotes) because it's only
542 # util.interpolate can't deal with "$@" (with quotes) because it's only
543 # built to match prefix + patterns.
543 # built to match prefix + patterns.
544 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
544 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
545 replacemap[b'$0'] = name
545 replacemap[b'$0'] = name
546 replacemap[b'$$'] = b'$'
546 replacemap[b'$$'] = b'$'
547 replacemap[b'$@'] = b' '.join(args)
547 replacemap[b'$@'] = b' '.join(args)
548 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
548 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
549 # parameters, separated out into words. Emulate the same behavior here by
549 # parameters, separated out into words. Emulate the same behavior here by
550 # quoting the arguments individually. POSIX shells will then typically
550 # quoting the arguments individually. POSIX shells will then typically
551 # tokenize each argument into exactly one word.
551 # tokenize each argument into exactly one word.
552 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
552 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
553 # escape '\$' for regex
553 # escape '\$' for regex
554 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
554 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
555 r = re.compile(regex)
555 r = re.compile(regex)
556 return r.sub(lambda x: replacemap[x.group()], cmd)
556 return r.sub(lambda x: replacemap[x.group()], cmd)
557
557
558
558
559 class cmdalias:
559 class cmdalias:
560 def __init__(self, ui, name, definition, cmdtable, source):
560 def __init__(self, ui, name, definition, cmdtable, source):
561 self.name = self.cmd = name
561 self.name = self.cmd = name
562 self.cmdname = b''
562 self.cmdname = b''
563 self.definition = definition
563 self.definition = definition
564 self.fn = None
564 self.fn = None
565 self.givenargs = []
565 self.givenargs = []
566 self.opts = []
566 self.opts = []
567 self.help = b''
567 self.help = b''
568 self.badalias = None
568 self.badalias = None
569 self.unknowncmd = False
569 self.unknowncmd = False
570 self.source = source
570 self.source = source
571
571
572 try:
572 try:
573 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
573 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
574 for alias, e in cmdtable.items():
574 for alias, e in cmdtable.items():
575 if e is entry:
575 if e is entry:
576 self.cmd = alias
576 self.cmd = alias
577 break
577 break
578 self.shadows = True
578 self.shadows = True
579 except error.UnknownCommand:
579 except error.UnknownCommand:
580 self.shadows = False
580 self.shadows = False
581
581
582 if not self.definition:
582 if not self.definition:
583 self.badalias = _(b"no definition for alias '%s'") % self.name
583 self.badalias = _(b"no definition for alias '%s'") % self.name
584 return
584 return
585
585
586 if self.definition.startswith(b'!'):
586 if self.definition.startswith(b'!'):
587 shdef = self.definition[1:]
587 shdef = self.definition[1:]
588 self.shell = True
588 self.shell = True
589
589
590 def fn(ui, *args):
590 def fn(ui, *args):
591 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
591 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
592
592
593 def _checkvar(m):
593 def _checkvar(m):
594 if m.groups()[0] == b'$':
594 if m.groups()[0] == b'$':
595 return m.group()
595 return m.group()
596 elif int(m.groups()[0]) <= len(args):
596 elif int(m.groups()[0]) <= len(args):
597 return m.group()
597 return m.group()
598 else:
598 else:
599 ui.debug(
599 ui.debug(
600 b"No argument found for substitution "
600 b"No argument found for substitution "
601 b"of %i variable in alias '%s' definition.\n"
601 b"of %i variable in alias '%s' definition.\n"
602 % (int(m.groups()[0]), self.name)
602 % (int(m.groups()[0]), self.name)
603 )
603 )
604 return b''
604 return b''
605
605
606 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
606 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
607 cmd = aliasinterpolate(self.name, args, cmd)
607 cmd = aliasinterpolate(self.name, args, cmd)
608 return ui.system(
608 return ui.system(
609 cmd, environ=env, blockedtag=b'alias_%s' % self.name
609 cmd, environ=env, blockedtag=b'alias_%s' % self.name
610 )
610 )
611
611
612 self.fn = fn
612 self.fn = fn
613 self.alias = True
613 self.alias = True
614 self._populatehelp(ui, name, shdef, self.fn)
614 self._populatehelp(ui, name, shdef, self.fn)
615 return
615 return
616
616
617 try:
617 try:
618 args = pycompat.shlexsplit(self.definition)
618 args = pycompat.shlexsplit(self.definition)
619 except ValueError as inst:
619 except ValueError as inst:
620 self.badalias = _(b"error in definition for alias '%s': %s") % (
620 self.badalias = _(b"error in definition for alias '%s': %s") % (
621 self.name,
621 self.name,
622 stringutil.forcebytestr(inst),
622 stringutil.forcebytestr(inst),
623 )
623 )
624 return
624 return
625 earlyopts, args = _earlysplitopts(args)
625 earlyopts, args = _earlysplitopts(args)
626 if earlyopts:
626 if earlyopts:
627 self.badalias = _(
627 self.badalias = _(
628 b"error in definition for alias '%s': %s may "
628 b"error in definition for alias '%s': %s may "
629 b"only be given on the command line"
629 b"only be given on the command line"
630 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
630 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
631 return
631 return
632 self.cmdname = cmd = args.pop(0)
632 self.cmdname = cmd = args.pop(0)
633 self.givenargs = args
633 self.givenargs = args
634
634
635 try:
635 try:
636 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
636 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
637 if len(tableentry) > 2:
637 if len(tableentry) > 2:
638 self.fn, self.opts, cmdhelp = tableentry
638 self.fn, self.opts, cmdhelp = tableentry
639 else:
639 else:
640 self.fn, self.opts = tableentry
640 self.fn, self.opts = tableentry
641 cmdhelp = None
641 cmdhelp = None
642
642
643 self.alias = True
643 self.alias = True
644 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
644 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
645
645
646 except error.UnknownCommand:
646 except error.UnknownCommand:
647 self.badalias = _(
647 self.badalias = _(
648 b"alias '%s' resolves to unknown command '%s'"
648 b"alias '%s' resolves to unknown command '%s'"
649 ) % (
649 ) % (
650 self.name,
650 self.name,
651 cmd,
651 cmd,
652 )
652 )
653 self.unknowncmd = True
653 self.unknowncmd = True
654 except error.AmbiguousCommand:
654 except error.AmbiguousCommand:
655 self.badalias = _(
655 self.badalias = _(
656 b"alias '%s' resolves to ambiguous command '%s'"
656 b"alias '%s' resolves to ambiguous command '%s'"
657 ) % (
657 ) % (
658 self.name,
658 self.name,
659 cmd,
659 cmd,
660 )
660 )
661
661
662 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
662 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
663 # confine strings to be passed to i18n.gettext()
663 # confine strings to be passed to i18n.gettext()
664 cfg = {}
664 cfg = {}
665 for k in (b'doc', b'help', b'category'):
665 for k in (b'doc', b'help', b'category'):
666 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
666 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
667 if v is None:
667 if v is None:
668 continue
668 continue
669 if not encoding.isasciistr(v):
669 if not encoding.isasciistr(v):
670 self.badalias = _(
670 self.badalias = _(
671 b"non-ASCII character in alias definition '%s:%s'"
671 b"non-ASCII character in alias definition '%s:%s'"
672 ) % (name, k)
672 ) % (name, k)
673 return
673 return
674 cfg[k] = v
674 cfg[k] = v
675
675
676 self.help = cfg.get(b'help', defaulthelp or b'')
676 self.help = cfg.get(b'help', defaulthelp or b'')
677 if self.help and self.help.startswith(b"hg " + cmd):
677 if self.help and self.help.startswith(b"hg " + cmd):
678 # drop prefix in old-style help lines so hg shows the alias
678 # drop prefix in old-style help lines so hg shows the alias
679 self.help = self.help[4 + len(cmd) :]
679 self.help = self.help[4 + len(cmd) :]
680
680
681 self.owndoc = b'doc' in cfg
681 self.owndoc = b'doc' in cfg
682 doc = cfg.get(b'doc', pycompat.getdoc(fn))
682 doc = cfg.get(b'doc', pycompat.getdoc(fn))
683 if doc is not None:
683 if doc is not None:
684 doc = pycompat.sysstr(doc)
684 doc = pycompat.sysstr(doc)
685 self.__doc__ = doc
685 self.__doc__ = doc
686
686
687 self.helpcategory = cfg.get(
687 self.helpcategory = cfg.get(
688 b'category', registrar.command.CATEGORY_NONE
688 b'category', registrar.command.CATEGORY_NONE
689 )
689 )
690
690
691 @property
691 @property
692 def args(self):
692 def args(self):
693 args = pycompat.maplist(util.expandpath, self.givenargs)
693 args = pycompat.maplist(util.expandpath, self.givenargs)
694 return aliasargs(self.fn, args)
694 return aliasargs(self.fn, args)
695
695
696 def __getattr__(self, name):
696 def __getattr__(self, name):
697 adefaults = {
697 adefaults = {
698 'norepo': True,
698 'norepo': True,
699 'intents': set(),
699 'intents': set(),
700 'optionalrepo': False,
700 'optionalrepo': False,
701 'inferrepo': False,
701 'inferrepo': False,
702 }
702 }
703 if name not in adefaults:
703 if name not in adefaults:
704 raise AttributeError(name)
704 raise AttributeError(name)
705 if self.badalias or util.safehasattr(self, b'shell'):
705 if self.badalias or util.safehasattr(self, b'shell'):
706 return adefaults[name]
706 return adefaults[name]
707 return getattr(self.fn, name)
707 return getattr(self.fn, name)
708
708
709 def __call__(self, ui, *args, **opts):
709 def __call__(self, ui, *args, **opts):
710 if self.badalias:
710 if self.badalias:
711 hint = None
711 hint = None
712 if self.unknowncmd:
712 if self.unknowncmd:
713 try:
713 try:
714 # check if the command is in a disabled extension
714 # check if the command is in a disabled extension
715 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
715 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
716 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
716 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
717 except error.UnknownCommand:
717 except error.UnknownCommand:
718 pass
718 pass
719 raise error.ConfigError(self.badalias, hint=hint)
719 raise error.ConfigError(self.badalias, hint=hint)
720 if self.shadows:
720 if self.shadows:
721 ui.debug(
721 ui.debug(
722 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
722 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
723 )
723 )
724
724
725 ui.log(
725 ui.log(
726 b'commandalias',
726 b'commandalias',
727 b"alias '%s' expands to '%s'\n",
727 b"alias '%s' expands to '%s'\n",
728 self.name,
728 self.name,
729 self.definition,
729 self.definition,
730 )
730 )
731 if util.safehasattr(self, b'shell'):
731 if util.safehasattr(self, b'shell'):
732 return self.fn(ui, *args, **opts)
732 return self.fn(ui, *args, **opts)
733 else:
733 else:
734 try:
734 try:
735 return util.checksignature(self.fn)(ui, *args, **opts)
735 return util.checksignature(self.fn)(ui, *args, **opts)
736 except error.SignatureError:
736 except error.SignatureError:
737 args = b' '.join([self.cmdname] + self.args)
737 args = b' '.join([self.cmdname] + self.args)
738 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
738 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
739 raise
739 raise
740
740
741
741
742 class lazyaliasentry:
742 class lazyaliasentry:
743 """like a typical command entry (func, opts, help), but is lazy"""
743 """like a typical command entry (func, opts, help), but is lazy"""
744
744
745 def __init__(self, ui, name, definition, cmdtable, source):
745 def __init__(self, ui, name, definition, cmdtable, source):
746 self.ui = ui
746 self.ui = ui
747 self.name = name
747 self.name = name
748 self.definition = definition
748 self.definition = definition
749 self.cmdtable = cmdtable.copy()
749 self.cmdtable = cmdtable.copy()
750 self.source = source
750 self.source = source
751 self.alias = True
751 self.alias = True
752
752
753 @util.propertycache
753 @util.propertycache
754 def _aliasdef(self):
754 def _aliasdef(self):
755 return cmdalias(
755 return cmdalias(
756 self.ui, self.name, self.definition, self.cmdtable, self.source
756 self.ui, self.name, self.definition, self.cmdtable, self.source
757 )
757 )
758
758
759 def __getitem__(self, n):
759 def __getitem__(self, n):
760 aliasdef = self._aliasdef
760 aliasdef = self._aliasdef
761 if n == 0:
761 if n == 0:
762 return aliasdef
762 return aliasdef
763 elif n == 1:
763 elif n == 1:
764 return aliasdef.opts
764 return aliasdef.opts
765 elif n == 2:
765 elif n == 2:
766 return aliasdef.help
766 return aliasdef.help
767 else:
767 else:
768 raise IndexError
768 raise IndexError
769
769
770 def __iter__(self):
770 def __iter__(self):
771 for i in range(3):
771 for i in range(3):
772 yield self[i]
772 yield self[i]
773
773
774 def __len__(self):
774 def __len__(self):
775 return 3
775 return 3
776
776
777
777
778 def addaliases(ui, cmdtable):
778 def addaliases(ui, cmdtable):
779 # aliases are processed after extensions have been loaded, so they
779 # aliases are processed after extensions have been loaded, so they
780 # may use extension commands. Aliases can also use other alias definitions,
780 # may use extension commands. Aliases can also use other alias definitions,
781 # but only if they have been defined prior to the current definition.
781 # but only if they have been defined prior to the current definition.
782 for alias, definition in ui.configitems(b'alias', ignoresub=True):
782 for alias, definition in ui.configitems(b'alias', ignoresub=True):
783 try:
783 try:
784 if cmdtable[alias].definition == definition:
784 if cmdtable[alias].definition == definition:
785 continue
785 continue
786 except (KeyError, AttributeError):
786 except (KeyError, AttributeError):
787 # definition might not exist or it might not be a cmdalias
787 # definition might not exist or it might not be a cmdalias
788 pass
788 pass
789
789
790 source = ui.configsource(b'alias', alias)
790 source = ui.configsource(b'alias', alias)
791 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
791 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
792 cmdtable[alias] = entry
792 cmdtable[alias] = entry
793
793
794
794
795 def _parse(ui, args):
795 def _parse(ui, args):
796 options = {}
796 options = {}
797 cmdoptions = {}
797 cmdoptions = {}
798
798
799 try:
799 try:
800 args = fancyopts.fancyopts(args, commands.globalopts, options)
800 args = fancyopts.fancyopts(args, commands.globalopts, options)
801 except getopt.GetoptError as inst:
801 except getopt.GetoptError as inst:
802 raise error.CommandError(None, stringutil.forcebytestr(inst))
802 raise error.CommandError(None, stringutil.forcebytestr(inst))
803
803
804 if args:
804 if args:
805 cmd, args = args[0], args[1:]
805 cmd, args = args[0], args[1:]
806 aliases, entry = cmdutil.findcmd(
806 aliases, entry = cmdutil.findcmd(
807 cmd, commands.table, ui.configbool(b"ui", b"strict")
807 cmd, commands.table, ui.configbool(b"ui", b"strict")
808 )
808 )
809 cmd = aliases[0]
809 cmd = aliases[0]
810 args = aliasargs(entry[0], args)
810 args = aliasargs(entry[0], args)
811 defaults = ui.config(b"defaults", cmd)
811 defaults = ui.config(b"defaults", cmd)
812 if defaults:
812 if defaults:
813 args = (
813 args = (
814 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
814 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
815 + args
815 + args
816 )
816 )
817 c = list(entry[1])
817 c = list(entry[1])
818 else:
818 else:
819 cmd = None
819 cmd = None
820 c = []
820 c = []
821
821
822 # combine global options into local
822 # combine global options into local
823 for o in commands.globalopts:
823 for o in commands.globalopts:
824 c.append((o[0], o[1], options[o[1]], o[3]))
824 c.append((o[0], o[1], options[o[1]], o[3]))
825
825
826 try:
826 try:
827 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
827 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
828 except getopt.GetoptError as inst:
828 except getopt.GetoptError as inst:
829 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
829 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
830
830
831 # separate global options back out
831 # separate global options back out
832 for o in commands.globalopts:
832 for o in commands.globalopts:
833 n = o[1]
833 n = o[1]
834 options[n] = cmdoptions[n]
834 options[n] = cmdoptions[n]
835 del cmdoptions[n]
835 del cmdoptions[n]
836
836
837 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
837 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
838
838
839
839
840 def _parseconfig(ui, config):
840 def _parseconfig(ui, config):
841 """parse the --config options from the command line"""
841 """parse the --config options from the command line"""
842 configs = []
842 configs = []
843
843
844 for cfg in config:
844 for cfg in config:
845 try:
845 try:
846 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
846 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
847 section, name = name.split(b'.', 1)
847 section, name = name.split(b'.', 1)
848 if not section or not name:
848 if not section or not name:
849 raise IndexError
849 raise IndexError
850 ui.setconfig(section, name, value, b'--config')
850 ui.setconfig(section, name, value, b'--config')
851 configs.append((section, name, value))
851 configs.append((section, name, value))
852 except (IndexError, ValueError):
852 except (IndexError, ValueError):
853 raise error.InputError(
853 raise error.InputError(
854 _(
854 _(
855 b'malformed --config option: %r '
855 b'malformed --config option: %r '
856 b'(use --config section.name=value)'
856 b'(use --config section.name=value)'
857 )
857 )
858 % pycompat.bytestr(cfg)
858 % pycompat.bytestr(cfg)
859 )
859 )
860
860
861 return configs
861 return configs
862
862
863
863
864 def _earlyparseopts(ui, args):
864 def _earlyparseopts(ui, args):
865 options = {}
865 options = {}
866 fancyopts.fancyopts(
866 fancyopts.fancyopts(
867 args,
867 args,
868 commands.globalopts,
868 commands.globalopts,
869 options,
869 options,
870 gnu=not ui.plain(b'strictflags'),
870 gnu=not ui.plain(b'strictflags'),
871 early=True,
871 early=True,
872 optaliases={b'repository': [b'repo']},
872 optaliases={b'repository': [b'repo']},
873 )
873 )
874 return options
874 return options
875
875
876
876
877 def _earlysplitopts(args):
877 def _earlysplitopts(args):
878 """Split args into a list of possible early options and remainder args"""
878 """Split args into a list of possible early options and remainder args"""
879 shortoptions = b'R:'
879 shortoptions = b'R:'
880 # TODO: perhaps 'debugger' should be included
880 # TODO: perhaps 'debugger' should be included
881 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
881 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
882 return fancyopts.earlygetopt(
882 return fancyopts.earlygetopt(
883 args, shortoptions, longoptions, gnu=True, keepsep=True
883 args, shortoptions, longoptions, gnu=True, keepsep=True
884 )
884 )
885
885
886
886
887 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
887 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
888 # run pre-hook, and abort if it fails
888 # run pre-hook, and abort if it fails
889 hook.hook(
889 hook.hook(
890 lui,
890 lui,
891 repo,
891 repo,
892 b"pre-%s" % cmd,
892 b"pre-%s" % cmd,
893 True,
893 True,
894 args=b" ".join(fullargs),
894 args=b" ".join(fullargs),
895 pats=cmdpats,
895 pats=cmdpats,
896 opts=cmdoptions,
896 opts=cmdoptions,
897 )
897 )
898 try:
898 try:
899 ret = _runcommand(ui, options, cmd, d)
899 ret = _runcommand(ui, options, cmd, d)
900 # run post-hook, passing command result
900 # run post-hook, passing command result
901 hook.hook(
901 hook.hook(
902 lui,
902 lui,
903 repo,
903 repo,
904 b"post-%s" % cmd,
904 b"post-%s" % cmd,
905 False,
905 False,
906 args=b" ".join(fullargs),
906 args=b" ".join(fullargs),
907 result=ret,
907 result=ret,
908 pats=cmdpats,
908 pats=cmdpats,
909 opts=cmdoptions,
909 opts=cmdoptions,
910 )
910 )
911 except Exception:
911 except Exception:
912 # run failure hook and re-raise
912 # run failure hook and re-raise
913 hook.hook(
913 hook.hook(
914 lui,
914 lui,
915 repo,
915 repo,
916 b"fail-%s" % cmd,
916 b"fail-%s" % cmd,
917 False,
917 False,
918 args=b" ".join(fullargs),
918 args=b" ".join(fullargs),
919 pats=cmdpats,
919 pats=cmdpats,
920 opts=cmdoptions,
920 opts=cmdoptions,
921 )
921 )
922 raise
922 raise
923 return ret
923 return ret
924
924
925
925
926 def _readsharedsourceconfig(ui, path):
926 def _readsharedsourceconfig(ui, path):
927 """if the current repository is shared one, this tries to read
927 """if the current repository is shared one, this tries to read
928 .hg/hgrc of shared source if we are in share-safe mode
928 .hg/hgrc of shared source if we are in share-safe mode
929
929
930 Config read is loaded into the ui object passed
930 Config read is loaded into the ui object passed
931
931
932 This should be called before reading .hg/hgrc or the main repo
932 This should be called before reading .hg/hgrc or the main repo
933 as that overrides config set in shared source"""
933 as that overrides config set in shared source"""
934 try:
934 try:
935 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
935 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
936 requirements = set(fp.read().splitlines())
936 requirements = set(fp.read().splitlines())
937 if not (
937 if not (
938 requirementsmod.SHARESAFE_REQUIREMENT in requirements
938 requirementsmod.SHARESAFE_REQUIREMENT in requirements
939 and requirementsmod.SHARED_REQUIREMENT in requirements
939 and requirementsmod.SHARED_REQUIREMENT in requirements
940 ):
940 ):
941 return
941 return
942 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
942 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
943 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
943 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
944 root = sharedvfs.base
944 root = sharedvfs.base
945 ui.readconfig(sharedvfs.join(b"hgrc"), root)
945 ui.readconfig(sharedvfs.join(b"hgrc"), root)
946 except IOError:
946 except IOError:
947 pass
947 pass
948
948
949
949
950 def _getlocal(ui, rpath, wd=None):
950 def _getlocal(ui, rpath, wd=None):
951 """Return (path, local ui object) for the given target path.
951 """Return (path, local ui object) for the given target path.
952
952
953 Takes paths in [cwd]/.hg/hgrc into account."
953 Takes paths in [cwd]/.hg/hgrc into account."
954 """
954 """
955 try:
955 try:
956 cwd = encoding.getcwd()
956 cwd = encoding.getcwd()
957 except OSError as e:
957 except OSError as e:
958 raise error.Abort(
958 raise error.Abort(
959 _(b"error getting current working directory: %s")
959 _(b"error getting current working directory: %s")
960 % encoding.strtolocal(e.strerror)
960 % encoding.strtolocal(e.strerror)
961 )
961 )
962
962
963 # If using an alternate wd, temporarily switch to it so that relative
963 # If using an alternate wd, temporarily switch to it so that relative
964 # paths are resolved correctly during config loading.
964 # paths are resolved correctly during config loading.
965 oldcwd = None
965 oldcwd = None
966 if wd is None:
966 if wd is None:
967 wd = cwd
967 wd = cwd
968 else:
968 else:
969 oldcwd = cwd
969 oldcwd = cwd
970 os.chdir(wd)
970 os.chdir(wd)
971
971
972 path = cmdutil.findrepo(wd) or b""
972 path = cmdutil.findrepo(wd) or b""
973 if not path:
973 if not path:
974 lui = ui
974 lui = ui
975 else:
975 else:
976 lui = ui.copy()
976 lui = ui.copy()
977 if rcutil.use_repo_hgrc():
977 if rcutil.use_repo_hgrc():
978 _readsharedsourceconfig(lui, path)
978 _readsharedsourceconfig(lui, path)
979 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
979 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
980 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
980 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
981
981
982 if rpath:
982 if rpath:
983 path = urlutil.get_clone_path(lui, rpath)[0]
983 path_obj = urlutil.get_clone_path_obj(lui, rpath)
984 path = path_obj.rawloc
984 lui = ui.copy()
985 lui = ui.copy()
985 if rcutil.use_repo_hgrc():
986 if rcutil.use_repo_hgrc():
986 _readsharedsourceconfig(lui, path)
987 _readsharedsourceconfig(lui, path)
987 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
988 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
988 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
989 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
989
990
990 if oldcwd:
991 if oldcwd:
991 os.chdir(oldcwd)
992 os.chdir(oldcwd)
992
993
993 return path, lui
994 return path, lui
994
995
995
996
996 def _checkshellalias(lui, ui, args):
997 def _checkshellalias(lui, ui, args):
997 """Return the function to run the shell alias, if it is required"""
998 """Return the function to run the shell alias, if it is required"""
998 options = {}
999 options = {}
999
1000
1000 try:
1001 try:
1001 args = fancyopts.fancyopts(args, commands.globalopts, options)
1002 args = fancyopts.fancyopts(args, commands.globalopts, options)
1002 except getopt.GetoptError:
1003 except getopt.GetoptError:
1003 return
1004 return
1004
1005
1005 if not args:
1006 if not args:
1006 return
1007 return
1007
1008
1008 cmdtable = commands.table
1009 cmdtable = commands.table
1009
1010
1010 cmd = args[0]
1011 cmd = args[0]
1011 try:
1012 try:
1012 strict = ui.configbool(b"ui", b"strict")
1013 strict = ui.configbool(b"ui", b"strict")
1013 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1014 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1014 except (error.AmbiguousCommand, error.UnknownCommand):
1015 except (error.AmbiguousCommand, error.UnknownCommand):
1015 return
1016 return
1016
1017
1017 cmd = aliases[0]
1018 cmd = aliases[0]
1018 fn = entry[0]
1019 fn = entry[0]
1019
1020
1020 if cmd and util.safehasattr(fn, b'shell'):
1021 if cmd and util.safehasattr(fn, b'shell'):
1021 # shell alias shouldn't receive early options which are consumed by hg
1022 # shell alias shouldn't receive early options which are consumed by hg
1022 _earlyopts, args = _earlysplitopts(args)
1023 _earlyopts, args = _earlysplitopts(args)
1023 d = lambda: fn(ui, *args[1:])
1024 d = lambda: fn(ui, *args[1:])
1024 return lambda: runcommand(
1025 return lambda: runcommand(
1025 lui, None, cmd, args[:1], ui, options, d, [], {}
1026 lui, None, cmd, args[:1], ui, options, d, [], {}
1026 )
1027 )
1027
1028
1028
1029
1029 def _dispatch(req):
1030 def _dispatch(req):
1030 args = req.args
1031 args = req.args
1031 ui = req.ui
1032 ui = req.ui
1032
1033
1033 # check for cwd
1034 # check for cwd
1034 cwd = req.earlyoptions[b'cwd']
1035 cwd = req.earlyoptions[b'cwd']
1035 if cwd:
1036 if cwd:
1036 os.chdir(cwd)
1037 os.chdir(cwd)
1037
1038
1038 rpath = req.earlyoptions[b'repository']
1039 rpath = req.earlyoptions[b'repository']
1039 path, lui = _getlocal(ui, rpath)
1040 path, lui = _getlocal(ui, rpath)
1040
1041
1041 uis = {ui, lui}
1042 uis = {ui, lui}
1042
1043
1043 if req.repo:
1044 if req.repo:
1044 uis.add(req.repo.ui)
1045 uis.add(req.repo.ui)
1045
1046
1046 if (
1047 if (
1047 req.earlyoptions[b'verbose']
1048 req.earlyoptions[b'verbose']
1048 or req.earlyoptions[b'debug']
1049 or req.earlyoptions[b'debug']
1049 or req.earlyoptions[b'quiet']
1050 or req.earlyoptions[b'quiet']
1050 ):
1051 ):
1051 for opt in (b'verbose', b'debug', b'quiet'):
1052 for opt in (b'verbose', b'debug', b'quiet'):
1052 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1053 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1053 for ui_ in uis:
1054 for ui_ in uis:
1054 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1055 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1055
1056
1056 if req.earlyoptions[b'profile']:
1057 if req.earlyoptions[b'profile']:
1057 for ui_ in uis:
1058 for ui_ in uis:
1058 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1059 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1059 elif req.earlyoptions[b'profile'] is False:
1060 elif req.earlyoptions[b'profile'] is False:
1060 # Check for it being set already, so that we don't pollute the config
1061 # Check for it being set already, so that we don't pollute the config
1061 # with this when using chg in the very common case that it's not
1062 # with this when using chg in the very common case that it's not
1062 # enabled.
1063 # enabled.
1063 if lui.configbool(b'profiling', b'enabled'):
1064 if lui.configbool(b'profiling', b'enabled'):
1064 # Only do this on lui so that `chg foo` with a user config setting
1065 # Only do this on lui so that `chg foo` with a user config setting
1065 # profiling.enabled=1 still shows profiling information (chg will
1066 # profiling.enabled=1 still shows profiling information (chg will
1066 # specify `--no-profile` when `hg serve` is starting up, we don't
1067 # specify `--no-profile` when `hg serve` is starting up, we don't
1067 # want that to propagate to every later invocation).
1068 # want that to propagate to every later invocation).
1068 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1069 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1069
1070
1070 profile = lui.configbool(b'profiling', b'enabled')
1071 profile = lui.configbool(b'profiling', b'enabled')
1071 with profiling.profile(lui, enabled=profile) as profiler:
1072 with profiling.profile(lui, enabled=profile) as profiler:
1072 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1073 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1073 # reposetup
1074 # reposetup
1074 extensions.loadall(lui)
1075 extensions.loadall(lui)
1075 # Propagate any changes to lui.__class__ by extensions
1076 # Propagate any changes to lui.__class__ by extensions
1076 ui.__class__ = lui.__class__
1077 ui.__class__ = lui.__class__
1077
1078
1078 # (uisetup and extsetup are handled in extensions.loadall)
1079 # (uisetup and extsetup are handled in extensions.loadall)
1079
1080
1080 # (reposetup is handled in hg.repository)
1081 # (reposetup is handled in hg.repository)
1081
1082
1082 addaliases(lui, commands.table)
1083 addaliases(lui, commands.table)
1083
1084
1084 # All aliases and commands are completely defined, now.
1085 # All aliases and commands are completely defined, now.
1085 # Check abbreviation/ambiguity of shell alias.
1086 # Check abbreviation/ambiguity of shell alias.
1086 shellaliasfn = _checkshellalias(lui, ui, args)
1087 shellaliasfn = _checkshellalias(lui, ui, args)
1087 if shellaliasfn:
1088 if shellaliasfn:
1088 # no additional configs will be set, set up the ui instances
1089 # no additional configs will be set, set up the ui instances
1089 for ui_ in uis:
1090 for ui_ in uis:
1090 extensions.populateui(ui_)
1091 extensions.populateui(ui_)
1091 return shellaliasfn()
1092 return shellaliasfn()
1092
1093
1093 # check for fallback encoding
1094 # check for fallback encoding
1094 fallback = lui.config(b'ui', b'fallbackencoding')
1095 fallback = lui.config(b'ui', b'fallbackencoding')
1095 if fallback:
1096 if fallback:
1096 encoding.fallbackencoding = fallback
1097 encoding.fallbackencoding = fallback
1097
1098
1098 fullargs = args
1099 fullargs = args
1099 cmd, func, args, options, cmdoptions = _parse(lui, args)
1100 cmd, func, args, options, cmdoptions = _parse(lui, args)
1100
1101
1101 # store the canonical command name in request object for later access
1102 # store the canonical command name in request object for later access
1102 req.canonical_command = cmd
1103 req.canonical_command = cmd
1103
1104
1104 if options[b"config"] != req.earlyoptions[b"config"]:
1105 if options[b"config"] != req.earlyoptions[b"config"]:
1105 raise error.InputError(_(b"option --config may not be abbreviated"))
1106 raise error.InputError(_(b"option --config may not be abbreviated"))
1106 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1107 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1107 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1108 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1108 if options[b"repository"] != req.earlyoptions[b"repository"]:
1109 if options[b"repository"] != req.earlyoptions[b"repository"]:
1109 raise error.InputError(
1110 raise error.InputError(
1110 _(
1111 _(
1111 b"option -R has to be separated from other options (e.g. not "
1112 b"option -R has to be separated from other options (e.g. not "
1112 b"-qR) and --repository may only be abbreviated as --repo"
1113 b"-qR) and --repository may only be abbreviated as --repo"
1113 )
1114 )
1114 )
1115 )
1115 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1116 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1116 raise error.InputError(
1117 raise error.InputError(
1117 _(b"option --debugger may not be abbreviated")
1118 _(b"option --debugger may not be abbreviated")
1118 )
1119 )
1119 # don't validate --profile/--traceback, which can be enabled from now
1120 # don't validate --profile/--traceback, which can be enabled from now
1120
1121
1121 if options[b"encoding"]:
1122 if options[b"encoding"]:
1122 encoding.encoding = options[b"encoding"]
1123 encoding.encoding = options[b"encoding"]
1123 if options[b"encodingmode"]:
1124 if options[b"encodingmode"]:
1124 encoding.encodingmode = options[b"encodingmode"]
1125 encoding.encodingmode = options[b"encodingmode"]
1125 if options[b"time"]:
1126 if options[b"time"]:
1126
1127
1127 def get_times():
1128 def get_times():
1128 t = os.times()
1129 t = os.times()
1129 if t[4] == 0.0:
1130 if t[4] == 0.0:
1130 # Windows leaves this as zero, so use time.perf_counter()
1131 # Windows leaves this as zero, so use time.perf_counter()
1131 t = (t[0], t[1], t[2], t[3], util.timer())
1132 t = (t[0], t[1], t[2], t[3], util.timer())
1132 return t
1133 return t
1133
1134
1134 s = get_times()
1135 s = get_times()
1135
1136
1136 def print_time():
1137 def print_time():
1137 t = get_times()
1138 t = get_times()
1138 ui.warn(
1139 ui.warn(
1139 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1140 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1140 % (
1141 % (
1141 t[4] - s[4],
1142 t[4] - s[4],
1142 t[0] - s[0],
1143 t[0] - s[0],
1143 t[2] - s[2],
1144 t[2] - s[2],
1144 t[1] - s[1],
1145 t[1] - s[1],
1145 t[3] - s[3],
1146 t[3] - s[3],
1146 )
1147 )
1147 )
1148 )
1148
1149
1149 ui.atexit(print_time)
1150 ui.atexit(print_time)
1150 if options[b"profile"]:
1151 if options[b"profile"]:
1151 profiler.start()
1152 profiler.start()
1152
1153
1153 # if abbreviated version of this were used, take them in account, now
1154 # if abbreviated version of this were used, take them in account, now
1154 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1155 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1155 for opt in (b'verbose', b'debug', b'quiet'):
1156 for opt in (b'verbose', b'debug', b'quiet'):
1156 if options[opt] == req.earlyoptions[opt]:
1157 if options[opt] == req.earlyoptions[opt]:
1157 continue
1158 continue
1158 val = pycompat.bytestr(bool(options[opt]))
1159 val = pycompat.bytestr(bool(options[opt]))
1159 for ui_ in uis:
1160 for ui_ in uis:
1160 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1161 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1161
1162
1162 if options[b'traceback']:
1163 if options[b'traceback']:
1163 for ui_ in uis:
1164 for ui_ in uis:
1164 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1165 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1165
1166
1166 if options[b'noninteractive']:
1167 if options[b'noninteractive']:
1167 for ui_ in uis:
1168 for ui_ in uis:
1168 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1169 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1169
1170
1170 if cmdoptions.get(b'insecure', False):
1171 if cmdoptions.get(b'insecure', False):
1171 for ui_ in uis:
1172 for ui_ in uis:
1172 ui_.insecureconnections = True
1173 ui_.insecureconnections = True
1173
1174
1174 # setup color handling before pager, because setting up pager
1175 # setup color handling before pager, because setting up pager
1175 # might cause incorrect console information
1176 # might cause incorrect console information
1176 coloropt = options[b'color']
1177 coloropt = options[b'color']
1177 for ui_ in uis:
1178 for ui_ in uis:
1178 if coloropt:
1179 if coloropt:
1179 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1180 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1180 color.setup(ui_)
1181 color.setup(ui_)
1181
1182
1182 if stringutil.parsebool(options[b'pager']):
1183 if stringutil.parsebool(options[b'pager']):
1183 # ui.pager() expects 'internal-always-' prefix in this case
1184 # ui.pager() expects 'internal-always-' prefix in this case
1184 ui.pager(b'internal-always-' + cmd)
1185 ui.pager(b'internal-always-' + cmd)
1185 elif options[b'pager'] != b'auto':
1186 elif options[b'pager'] != b'auto':
1186 for ui_ in uis:
1187 for ui_ in uis:
1187 ui_.disablepager()
1188 ui_.disablepager()
1188
1189
1189 # configs are fully loaded, set up the ui instances
1190 # configs are fully loaded, set up the ui instances
1190 for ui_ in uis:
1191 for ui_ in uis:
1191 extensions.populateui(ui_)
1192 extensions.populateui(ui_)
1192
1193
1193 if options[b'version']:
1194 if options[b'version']:
1194 return commands.version_(ui)
1195 return commands.version_(ui)
1195 if options[b'help']:
1196 if options[b'help']:
1196 return commands.help_(ui, cmd, command=cmd is not None)
1197 return commands.help_(ui, cmd, command=cmd is not None)
1197 elif not cmd:
1198 elif not cmd:
1198 return commands.help_(ui, b'shortlist')
1199 return commands.help_(ui, b'shortlist')
1199
1200
1200 repo = None
1201 repo = None
1201 cmdpats = args[:]
1202 cmdpats = args[:]
1202 assert func is not None # help out pytype
1203 assert func is not None # help out pytype
1203 if not func.norepo:
1204 if not func.norepo:
1204 # use the repo from the request only if we don't have -R
1205 # use the repo from the request only if we don't have -R
1205 if not rpath and not cwd:
1206 if not rpath and not cwd:
1206 repo = req.repo
1207 repo = req.repo
1207
1208
1208 if repo:
1209 if repo:
1209 # set the descriptors of the repo ui to those of ui
1210 # set the descriptors of the repo ui to those of ui
1210 repo.ui.fin = ui.fin
1211 repo.ui.fin = ui.fin
1211 repo.ui.fout = ui.fout
1212 repo.ui.fout = ui.fout
1212 repo.ui.ferr = ui.ferr
1213 repo.ui.ferr = ui.ferr
1213 repo.ui.fmsg = ui.fmsg
1214 repo.ui.fmsg = ui.fmsg
1214 else:
1215 else:
1215 try:
1216 try:
1216 repo = hg.repository(
1217 repo = hg.repository(
1217 ui,
1218 ui,
1218 path=path,
1219 path=path,
1219 presetupfuncs=req.prereposetups,
1220 presetupfuncs=req.prereposetups,
1220 intents=func.intents,
1221 intents=func.intents,
1221 )
1222 )
1222 if not repo.local():
1223 if not repo.local():
1223 raise error.InputError(
1224 raise error.InputError(
1224 _(b"repository '%s' is not local") % path
1225 _(b"repository '%s' is not local") % path
1225 )
1226 )
1226 repo.ui.setconfig(
1227 repo.ui.setconfig(
1227 b"bundle", b"mainreporoot", repo.root, b'repo'
1228 b"bundle", b"mainreporoot", repo.root, b'repo'
1228 )
1229 )
1229 except error.RequirementError:
1230 except error.RequirementError:
1230 raise
1231 raise
1231 except error.RepoError:
1232 except error.RepoError:
1232 if rpath: # invalid -R path
1233 if rpath: # invalid -R path
1233 raise
1234 raise
1234 if not func.optionalrepo:
1235 if not func.optionalrepo:
1235 if func.inferrepo and args and not path:
1236 if func.inferrepo and args and not path:
1236 # try to infer -R from command args
1237 # try to infer -R from command args
1237 repos = pycompat.maplist(cmdutil.findrepo, args)
1238 repos = pycompat.maplist(cmdutil.findrepo, args)
1238 guess = repos[0]
1239 guess = repos[0]
1239 if guess and repos.count(guess) == len(repos):
1240 if guess and repos.count(guess) == len(repos):
1240 req.args = [b'--repository', guess] + fullargs
1241 req.args = [b'--repository', guess] + fullargs
1241 req.earlyoptions[b'repository'] = guess
1242 req.earlyoptions[b'repository'] = guess
1242 return _dispatch(req)
1243 return _dispatch(req)
1243 if not path:
1244 if not path:
1244 raise error.InputError(
1245 raise error.InputError(
1245 _(
1246 _(
1246 b"no repository found in"
1247 b"no repository found in"
1247 b" '%s' (.hg not found)"
1248 b" '%s' (.hg not found)"
1248 )
1249 )
1249 % encoding.getcwd()
1250 % encoding.getcwd()
1250 )
1251 )
1251 raise
1252 raise
1252 if repo:
1253 if repo:
1253 ui = repo.ui
1254 ui = repo.ui
1254 if options[b'hidden']:
1255 if options[b'hidden']:
1255 repo = repo.unfiltered()
1256 repo = repo.unfiltered()
1256 args.insert(0, repo)
1257 args.insert(0, repo)
1257 elif rpath:
1258 elif rpath:
1258 ui.warn(_(b"warning: --repository ignored\n"))
1259 ui.warn(_(b"warning: --repository ignored\n"))
1259
1260
1260 msg = _formatargs(fullargs)
1261 msg = _formatargs(fullargs)
1261 ui.log(b"command", b'%s\n', msg)
1262 ui.log(b"command", b'%s\n', msg)
1262 strcmdopt = pycompat.strkwargs(cmdoptions)
1263 strcmdopt = pycompat.strkwargs(cmdoptions)
1263 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1264 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1264 try:
1265 try:
1265 return runcommand(
1266 return runcommand(
1266 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1267 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1267 )
1268 )
1268 finally:
1269 finally:
1269 if repo and repo != req.repo:
1270 if repo and repo != req.repo:
1270 repo.close()
1271 repo.close()
1271
1272
1272
1273
1273 def _runcommand(ui, options, cmd, cmdfunc):
1274 def _runcommand(ui, options, cmd, cmdfunc):
1274 """Run a command function, possibly with profiling enabled."""
1275 """Run a command function, possibly with profiling enabled."""
1275 try:
1276 try:
1276 with tracing.log("Running %s command" % cmd):
1277 with tracing.log("Running %s command" % cmd):
1277 return cmdfunc()
1278 return cmdfunc()
1278 except error.SignatureError:
1279 except error.SignatureError:
1279 raise error.CommandError(cmd, _(b'invalid arguments'))
1280 raise error.CommandError(cmd, _(b'invalid arguments'))
1280
1281
1281
1282
1282 def _exceptionwarning(ui):
1283 def _exceptionwarning(ui):
1283 """Produce a warning message for the current active exception"""
1284 """Produce a warning message for the current active exception"""
1284
1285
1285 # For compatibility checking, we discard the portion of the hg
1286 # For compatibility checking, we discard the portion of the hg
1286 # version after the + on the assumption that if a "normal
1287 # version after the + on the assumption that if a "normal
1287 # user" is running a build with a + in it the packager
1288 # user" is running a build with a + in it the packager
1288 # probably built from fairly close to a tag and anyone with a
1289 # probably built from fairly close to a tag and anyone with a
1289 # 'make local' copy of hg (where the version number can be out
1290 # 'make local' copy of hg (where the version number can be out
1290 # of date) will be clueful enough to notice the implausible
1291 # of date) will be clueful enough to notice the implausible
1291 # version number and try updating.
1292 # version number and try updating.
1292 ct = util.versiontuple(n=2)
1293 ct = util.versiontuple(n=2)
1293 worst = None, ct, b'', b''
1294 worst = None, ct, b'', b''
1294 if ui.config(b'ui', b'supportcontact') is None:
1295 if ui.config(b'ui', b'supportcontact') is None:
1295 for name, mod in extensions.extensions():
1296 for name, mod in extensions.extensions():
1296 # 'testedwith' should be bytes, but not all extensions are ported
1297 # 'testedwith' should be bytes, but not all extensions are ported
1297 # to py3 and we don't want UnicodeException because of that.
1298 # to py3 and we don't want UnicodeException because of that.
1298 testedwith = stringutil.forcebytestr(
1299 testedwith = stringutil.forcebytestr(
1299 getattr(mod, 'testedwith', b'')
1300 getattr(mod, 'testedwith', b'')
1300 )
1301 )
1301 version = extensions.moduleversion(mod)
1302 version = extensions.moduleversion(mod)
1302 report = getattr(mod, 'buglink', _(b'the extension author.'))
1303 report = getattr(mod, 'buglink', _(b'the extension author.'))
1303 if not testedwith.strip():
1304 if not testedwith.strip():
1304 # We found an untested extension. It's likely the culprit.
1305 # We found an untested extension. It's likely the culprit.
1305 worst = name, b'unknown', report, version
1306 worst = name, b'unknown', report, version
1306 break
1307 break
1307
1308
1308 # Never blame on extensions bundled with Mercurial.
1309 # Never blame on extensions bundled with Mercurial.
1309 if extensions.ismoduleinternal(mod):
1310 if extensions.ismoduleinternal(mod):
1310 continue
1311 continue
1311
1312
1312 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1313 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1313 if ct in tested:
1314 if ct in tested:
1314 continue
1315 continue
1315
1316
1316 lower = [t for t in tested if t < ct]
1317 lower = [t for t in tested if t < ct]
1317 nearest = max(lower or tested)
1318 nearest = max(lower or tested)
1318 if worst[0] is None or nearest < worst[1]:
1319 if worst[0] is None or nearest < worst[1]:
1319 worst = name, nearest, report, version
1320 worst = name, nearest, report, version
1320 if worst[0] is not None:
1321 if worst[0] is not None:
1321 name, testedwith, report, version = worst
1322 name, testedwith, report, version = worst
1322 if not isinstance(testedwith, (bytes, str)):
1323 if not isinstance(testedwith, (bytes, str)):
1323 testedwith = b'.'.join(
1324 testedwith = b'.'.join(
1324 [stringutil.forcebytestr(c) for c in testedwith]
1325 [stringutil.forcebytestr(c) for c in testedwith]
1325 )
1326 )
1326 extver = version or _(b"(version N/A)")
1327 extver = version or _(b"(version N/A)")
1327 warning = _(
1328 warning = _(
1328 b'** Unknown exception encountered with '
1329 b'** Unknown exception encountered with '
1329 b'possibly-broken third-party extension "%s" %s\n'
1330 b'possibly-broken third-party extension "%s" %s\n'
1330 b'** which supports versions %s of Mercurial.\n'
1331 b'** which supports versions %s of Mercurial.\n'
1331 b'** Please disable "%s" and try your action again.\n'
1332 b'** Please disable "%s" and try your action again.\n'
1332 b'** If that fixes the bug please report it to %s\n'
1333 b'** If that fixes the bug please report it to %s\n'
1333 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1334 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1334 else:
1335 else:
1335 bugtracker = ui.config(b'ui', b'supportcontact')
1336 bugtracker = ui.config(b'ui', b'supportcontact')
1336 if bugtracker is None:
1337 if bugtracker is None:
1337 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1338 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1338 warning = (
1339 warning = (
1339 _(
1340 _(
1340 b"** unknown exception encountered, "
1341 b"** unknown exception encountered, "
1341 b"please report by visiting\n** "
1342 b"please report by visiting\n** "
1342 )
1343 )
1343 + bugtracker
1344 + bugtracker
1344 + b'\n'
1345 + b'\n'
1345 )
1346 )
1346 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1347 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1347
1348
1348 def ext_with_ver(x):
1349 def ext_with_ver(x):
1349 ext = x[0]
1350 ext = x[0]
1350 ver = extensions.moduleversion(x[1])
1351 ver = extensions.moduleversion(x[1])
1351 if ver:
1352 if ver:
1352 ext += b' ' + ver
1353 ext += b' ' + ver
1353 return ext
1354 return ext
1354
1355
1355 warning += (
1356 warning += (
1356 (_(b"** Python %s\n") % sysversion)
1357 (_(b"** Python %s\n") % sysversion)
1357 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1358 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1358 + (
1359 + (
1359 _(b"** Extensions loaded: %s\n")
1360 _(b"** Extensions loaded: %s\n")
1360 % b", ".join(
1361 % b", ".join(
1361 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1362 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1362 )
1363 )
1363 )
1364 )
1364 )
1365 )
1365 return warning
1366 return warning
1366
1367
1367
1368
1368 def handlecommandexception(ui):
1369 def handlecommandexception(ui):
1369 """Produce a warning message for broken commands
1370 """Produce a warning message for broken commands
1370
1371
1371 Called when handling an exception; the exception is reraised if
1372 Called when handling an exception; the exception is reraised if
1372 this function returns False, ignored otherwise.
1373 this function returns False, ignored otherwise.
1373 """
1374 """
1374 warning = _exceptionwarning(ui)
1375 warning = _exceptionwarning(ui)
1375 ui.log(
1376 ui.log(
1376 b"commandexception",
1377 b"commandexception",
1377 b"%s\n%s\n",
1378 b"%s\n%s\n",
1378 warning,
1379 warning,
1379 pycompat.sysbytes(traceback.format_exc()),
1380 pycompat.sysbytes(traceback.format_exc()),
1380 )
1381 )
1381 ui.warn(warning)
1382 ui.warn(warning)
1382 return False # re-raise the exception
1383 return False # re-raise the exception
General Comments 0
You need to be logged in to leave comments. Login now