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