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