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