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