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