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