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