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