1# error.py - Mercurial exceptions 2# 3# Copyright 2005-2008 Olivia Mackall <olivia@selenic.com> 4# 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. 7 8"""Mercurial exceptions. 9 10This allows us to catch exceptions at higher levels without forcing 11imports. 12""" 13 14from __future__ import absolute_import 15 16import difflib 17 18# Do not import anything but pycompat here, please 19from . import pycompat 20 21if pycompat.TYPE_CHECKING: 22 from typing import ( 23 Any, 24 AnyStr, 25 Iterable, 26 List, 27 Optional, 28 Sequence, 29 Union, 30 ) 31 32 33def _tobytes(exc): 34 # type: (...) -> bytes 35 """Byte-stringify exception in the same way as BaseException_str()""" 36 if not exc.args: 37 return b'' 38 if len(exc.args) == 1: 39 return pycompat.bytestr(exc.args[0]) 40 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args) 41 42 43class Hint(object): 44 """Mix-in to provide a hint of an error 45 46 This should come first in the inheritance list to consume a hint and 47 pass remaining arguments to the exception class. 48 """ 49 50 def __init__(self, *args, **kw): 51 self.hint = kw.pop('hint', None) # type: Optional[bytes] 52 super(Hint, self).__init__(*args, **kw) 53 54 55class Error(Hint, Exception): 56 """Base class for Mercurial errors.""" 57 58 coarse_exit_code = None 59 detailed_exit_code = None 60 61 def __init__(self, message, hint=None): 62 # type: (bytes, Optional[bytes]) -> None 63 self.message = message 64 self.hint = hint 65 # Pass the message into the Exception constructor to help extensions 66 # that look for exc.args[0]. 67 Exception.__init__(self, message) 68 69 def __bytes__(self): 70 return self.message 71 72 if pycompat.ispy3: 73 74 def __str__(self): 75 # type: () -> str 76 # the output would be unreadable if the message was translated, 77 # but do not replace it with encoding.strfromlocal(), which 78 # may raise another exception. 79 return pycompat.sysstr(self.__bytes__()) 80 81 def format(self): 82 # type: () -> bytes 83 from .i18n import _ 84 85 message = _(b"abort: %s\n") % self.message 86 if self.hint: 87 message += _(b"(%s)\n") % self.hint 88 return message 89 90 91class Abort(Error): 92 """Raised if a command needs to print an error and exit.""" 93 94 95class StorageError(Error): 96 """Raised when an error occurs in a storage layer. 97 98 Usually subclassed by a storage-specific exception. 99 """ 100 101 detailed_exit_code = 50 102 103 104class RevlogError(StorageError): 105 pass 106 107 108class SidedataHashError(RevlogError): 109 def __init__(self, key, expected, got): 110 # type: (int, bytes, bytes) -> None 111 self.hint = None 112 self.sidedatakey = key 113 self.expecteddigest = expected 114 self.actualdigest = got 115 116 117class FilteredIndexError(IndexError): 118 __bytes__ = _tobytes 119 120 121class LookupError(RevlogError, KeyError): 122 def __init__(self, name, index, message): 123 # type: (bytes, bytes, bytes) -> None 124 self.name = name 125 self.index = index 126 # this can't be called 'message' because at least some installs of 127 # Python 2.6+ complain about the 'message' property being deprecated 128 self.lookupmessage = message 129 if isinstance(name, bytes) and len(name) == 20: 130 from .node import hex 131 132 name = hex(name) 133 # if name is a binary node, it can be None 134 RevlogError.__init__( 135 self, b'%s@%s: %s' % (index, pycompat.bytestr(name), message) 136 ) 137 138 def __bytes__(self): 139 return RevlogError.__bytes__(self) 140 141 def __str__(self): 142 return RevlogError.__str__(self) 143 144 145class AmbiguousPrefixLookupError(LookupError): 146 pass 147 148 149class FilteredLookupError(LookupError): 150 pass 151 152 153class ManifestLookupError(LookupError): 154 pass 155 156 157class CommandError(Exception): 158 """Exception raised on errors in parsing the command line.""" 159 160 def __init__(self, command, message): 161 # type: (bytes, bytes) -> None 162 self.command = command 163 self.message = message 164 super(CommandError, self).__init__() 165 166 __bytes__ = _tobytes 167 168 169class UnknownCommand(Exception): 170 """Exception raised if command is not in the command table.""" 171 172 def __init__(self, command, all_commands=None): 173 # type: (bytes, Optional[List[bytes]]) -> None 174 self.command = command 175 self.all_commands = all_commands 176 super(UnknownCommand, self).__init__() 177 178 __bytes__ = _tobytes 179 180 181class AmbiguousCommand(Exception): 182 """Exception raised if command shortcut matches more than one command.""" 183 184 def __init__(self, prefix, matches): 185 # type: (bytes, List[bytes]) -> None 186 self.prefix = prefix 187 self.matches = matches 188 super(AmbiguousCommand, self).__init__() 189 190 __bytes__ = _tobytes 191 192 193class WorkerError(Exception): 194 """Exception raised when a worker process dies.""" 195 196 def __init__(self, status_code): 197 # type: (int) -> None 198 self.status_code = status_code 199 # Pass status code to superclass just so it becomes part of __bytes__ 200 super(WorkerError, self).__init__(status_code) 201 202 __bytes__ = _tobytes 203 204 205class InterventionRequired(Abort): 206 """Exception raised when a command requires human intervention.""" 207 208 coarse_exit_code = 1 209 detailed_exit_code = 240 210 211 def format(self): 212 # type: () -> bytes 213 from .i18n import _ 214 215 message = _(b"%s\n") % self.message 216 if self.hint: 217 message += _(b"(%s)\n") % self.hint 218 return message 219 220 221class ConflictResolutionRequired(InterventionRequired): 222 """Exception raised when a continuable command required merge conflict resolution.""" 223 224 def __init__(self, opname): 225 # type: (bytes) -> None 226 from .i18n import _ 227 228 self.opname = opname 229 InterventionRequired.__init__( 230 self, 231 _( 232 b"unresolved conflicts (see 'hg resolve', then 'hg %s --continue')" 233 ) 234 % opname, 235 ) 236 237 238class InputError(Abort): 239 """Indicates that the user made an error in their input. 240 241 Examples: Invalid command, invalid flags, invalid revision. 242 """ 243 244 detailed_exit_code = 10 245 246 247class StateError(Abort): 248 """Indicates that the operation might work if retried in a different state. 249 250 Examples: Unresolved merge conflicts, unfinished operations. 251 """ 252 253 detailed_exit_code = 20 254 255 256class CanceledError(Abort): 257 """Indicates that the user canceled the operation. 258 259 Examples: Close commit editor with error status, quit chistedit. 260 """ 261 262 detailed_exit_code = 250 263 264 265class SecurityError(Abort): 266 """Indicates that some aspect of security failed. 267 268 Examples: Bad server credentials, expired local credentials for network 269 filesystem, mismatched GPG signature, DoS protection. 270 """ 271 272 detailed_exit_code = 150 273 274 275class HookLoadError(Abort): 276 """raised when loading a hook fails, aborting an operation 277 278 Exists to allow more specialized catching.""" 279 280 281class HookAbort(Abort): 282 """raised when a validation hook fails, aborting an operation 283 284 Exists to allow more specialized catching.""" 285 286 detailed_exit_code = 40 287 288 289class ConfigError(Abort): 290 """Exception raised when parsing config files""" 291 292 detailed_exit_code = 30 293 294 def __init__(self, message, location=None, hint=None): 295 # type: (bytes, Optional[bytes], Optional[bytes]) -> None 296 super(ConfigError, self).__init__(message, hint=hint) 297 self.location = location 298 299 def format(self): 300 # type: () -> bytes 301 from .i18n import _ 302 303 if self.location is not None: 304 message = _(b"config error at %s: %s\n") % ( 305 pycompat.bytestr(self.location), 306 self.message, 307 ) 308 else: 309 message = _(b"config error: %s\n") % self.message 310 if self.hint: 311 message += _(b"(%s)\n") % self.hint 312 return message 313 314 315class UpdateAbort(Abort): 316 """Raised when an update is aborted for destination issue""" 317 318 319class MergeDestAbort(Abort): 320 """Raised when an update is aborted for destination issues""" 321 322 323class NoMergeDestAbort(MergeDestAbort): 324 """Raised when an update is aborted because there is nothing to merge""" 325 326 327class ManyMergeDestAbort(MergeDestAbort): 328 """Raised when an update is aborted because destination is ambiguous""" 329 330 331class ResponseExpected(Abort): 332 """Raised when an EOF is received for a prompt""" 333 334 def __init__(self): 335 from .i18n import _ 336 337 Abort.__init__(self, _(b'response expected')) 338 339 340class RemoteError(Abort): 341 """Exception raised when interacting with a remote repo fails""" 342 343 detailed_exit_code = 100 344 345 346class OutOfBandError(RemoteError): 347 """Exception raised when a remote repo reports failure""" 348 349 def __init__(self, message=None, hint=None): 350 # type: (Optional[bytes], Optional[bytes]) -> None 351 from .i18n import _ 352 353 if message: 354 # Abort.format() adds a trailing newline 355 message = _(b"remote error:\n%s") % message.rstrip(b'\n') 356 else: 357 message = _(b"remote error") 358 super(OutOfBandError, self).__init__(message, hint=hint) 359 360 361class ParseError(Abort): 362 """Raised when parsing config files and {rev,file}sets (msg[, pos])""" 363 364 detailed_exit_code = 10 365 366 def __init__(self, message, location=None, hint=None): 367 # type: (bytes, Optional[Union[bytes, int]], Optional[bytes]) -> None 368 super(ParseError, self).__init__(message, hint=hint) 369 self.location = location 370 371 def format(self): 372 # type: () -> bytes 373 from .i18n import _ 374 375 if self.location is not None: 376 message = _(b"hg: parse error at %s: %s\n") % ( 377 pycompat.bytestr(self.location), 378 self.message, 379 ) 380 else: 381 message = _(b"hg: parse error: %s\n") % self.message 382 if self.hint: 383 message += _(b"(%s)\n") % self.hint 384 return message 385 386 387class PatchError(Exception): 388 __bytes__ = _tobytes 389 390 391def getsimilar(symbols, value): 392 # type: (Iterable[bytes], bytes) -> List[bytes] 393 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio() 394 # The cutoff for similarity here is pretty arbitrary. It should 395 # probably be investigated and tweaked. 396 return [s for s in symbols if sim(s) > 0.6] 397 398 399def similarity_hint(similar): 400 # type: (List[bytes]) -> Optional[bytes] 401 from .i18n import _ 402 403 if len(similar) == 1: 404 return _(b"did you mean %s?") % similar[0] 405 elif similar: 406 ss = b", ".join(sorted(similar)) 407 return _(b"did you mean one of %s?") % ss 408 else: 409 return None 410 411 412class UnknownIdentifier(ParseError): 413 """Exception raised when a {rev,file}set references an unknown identifier""" 414 415 def __init__(self, function, symbols): 416 # type: (bytes, Iterable[bytes]) -> None 417 from .i18n import _ 418 419 similar = getsimilar(symbols, function) 420 hint = similarity_hint(similar) 421 422 ParseError.__init__( 423 self, _(b"unknown identifier: %s") % function, hint=hint 424 ) 425 426 427class RepoError(Hint, Exception): 428 __bytes__ = _tobytes 429 430 431class RepoLookupError(RepoError): 432 pass 433 434 435class FilteredRepoLookupError(RepoLookupError): 436 pass 437 438 439class CapabilityError(RepoError): 440 pass 441 442 443class RequirementError(RepoError): 444 """Exception raised if .hg/requires has an unknown entry.""" 445 446 447class StdioError(IOError): 448 """Raised if I/O to stdout or stderr fails""" 449 450 def __init__(self, err): 451 # type: (IOError) -> None 452 IOError.__init__(self, err.errno, err.strerror) 453 454 # no __bytes__() because error message is derived from the standard IOError 455 456 457class UnsupportedMergeRecords(Abort): 458 def __init__(self, recordtypes): 459 # type: (Iterable[bytes]) -> None 460 from .i18n import _ 461 462 self.recordtypes = sorted(recordtypes) 463 s = b' '.join(self.recordtypes) 464 Abort.__init__( 465 self, 466 _(b'unsupported merge state records: %s') % s, 467 hint=_( 468 b'see https://mercurial-scm.org/wiki/MergeStateRecords for ' 469 b'more information' 470 ), 471 ) 472 473 474class UnknownVersion(Abort): 475 """generic exception for aborting from an encounter with an unknown version""" 476 477 def __init__(self, msg, hint=None, version=None): 478 # type: (bytes, Optional[bytes], Optional[bytes]) -> None 479 self.version = version 480 super(UnknownVersion, self).__init__(msg, hint=hint) 481 482 483class LockError(IOError): 484 def __init__(self, errno, strerror, filename, desc): 485 # TODO: figure out if this should be bytes or str 486 # _type: (int, str, str, bytes) -> None 487 IOError.__init__(self, errno, strerror, filename) 488 self.desc = desc 489 490 # no __bytes__() because error message is derived from the standard IOError 491 492 493class LockHeld(LockError): 494 def __init__(self, errno, filename, desc, locker): 495 LockError.__init__(self, errno, b'Lock held', filename, desc) 496 self.locker = locker 497 498 499class LockUnavailable(LockError): 500 pass 501 502 503# LockError is for errors while acquiring the lock -- this is unrelated 504class LockInheritanceContractViolation(RuntimeError): 505 __bytes__ = _tobytes 506 507 508class ResponseError(Exception): 509 """Raised to print an error with part of output and exit.""" 510 511 __bytes__ = _tobytes 512 513 514# derived from KeyboardInterrupt to simplify some breakout code 515class SignalInterrupt(KeyboardInterrupt): 516 """Exception raised on SIGTERM and SIGHUP.""" 517 518 519class SignatureError(Exception): 520 __bytes__ = _tobytes 521 522 523class PushRaced(RuntimeError): 524 """An exception raised during unbundling that indicate a push race""" 525 526 __bytes__ = _tobytes 527 528 529class ProgrammingError(Hint, RuntimeError): 530 """Raised if a mercurial (core or extension) developer made a mistake""" 531 532 def __init__(self, msg, *args, **kwargs): 533 # type: (AnyStr, Any, Any) -> None 534 # On Python 3, turn the message back into a string since this is 535 # an internal-only error that won't be printed except in a 536 # stack traces. 537 msg = pycompat.sysstr(msg) 538 super(ProgrammingError, self).__init__(msg, *args, **kwargs) 539 540 __bytes__ = _tobytes 541 542 543class WdirUnsupported(Exception): 544 """An exception which is raised when 'wdir()' is not supported""" 545 546 __bytes__ = _tobytes 547 548 549# bundle2 related errors 550class BundleValueError(ValueError): 551 """error raised when bundle2 cannot be processed""" 552 553 __bytes__ = _tobytes 554 555 556class BundleUnknownFeatureError(BundleValueError): 557 def __init__(self, parttype=None, params=(), values=()): 558 self.parttype = parttype 559 self.params = params 560 self.values = values 561 if self.parttype is None: 562 msg = b'Stream Parameter' 563 else: 564 msg = parttype 565 entries = self.params 566 if self.params and self.values: 567 assert len(self.params) == len(self.values) 568 entries = [] 569 for idx, par in enumerate(self.params): 570 val = self.values[idx] 571 if val is None: 572 entries.append(val) 573 else: 574 entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val))) 575 if entries: 576 msg = b'%s - %s' % (msg, b', '.join(entries)) 577 ValueError.__init__(self, msg) # TODO: convert to str? 578 579 580class ReadOnlyPartError(RuntimeError): 581 """error raised when code tries to alter a part being generated""" 582 583 __bytes__ = _tobytes 584 585 586class PushkeyFailed(Abort): 587 """error raised when a pushkey part failed to update a value""" 588 589 def __init__( 590 self, partid, namespace=None, key=None, new=None, old=None, ret=None 591 ): 592 self.partid = partid 593 self.namespace = namespace 594 self.key = key 595 self.new = new 596 self.old = old 597 self.ret = ret 598 # no i18n expected to be processed into a better message 599 Abort.__init__( 600 self, b'failed to update value for "%s/%s"' % (namespace, key) 601 ) 602 603 604class CensoredNodeError(StorageError): 605 """error raised when content verification fails on a censored node 606 607 Also contains the tombstone data substituted for the uncensored data. 608 """ 609 610 def __init__(self, filename, node, tombstone): 611 # type: (bytes, bytes, bytes) -> None 612 from .node import short 613 614 StorageError.__init__(self, b'%s:%s' % (filename, short(node))) 615 self.tombstone = tombstone 616 617 618class CensoredBaseError(StorageError): 619 """error raised when a delta is rejected because its base is censored 620 621 A delta based on a censored revision must be formed as single patch 622 operation which replaces the entire base with new content. This ensures 623 the delta may be applied by clones which have not censored the base. 624 """ 625 626 627class InvalidBundleSpecification(Exception): 628 """error raised when a bundle specification is invalid. 629 630 This is used for syntax errors as opposed to support errors. 631 """ 632 633 __bytes__ = _tobytes 634 635 636class UnsupportedBundleSpecification(Exception): 637 """error raised when a bundle specification is not supported.""" 638 639 __bytes__ = _tobytes 640 641 642class CorruptedState(Exception): 643 """error raised when a command is not able to read its state from file""" 644 645 __bytes__ = _tobytes 646 647 648class PeerTransportError(Abort): 649 """Transport-level I/O error when communicating with a peer repo.""" 650 651 652class InMemoryMergeConflictsError(Exception): 653 """Exception raised when merge conflicts arose during an in-memory merge.""" 654 655 __bytes__ = _tobytes 656 657 658class WireprotoCommandError(Exception): 659 """Represents an error during execution of a wire protocol command. 660 661 Should only be thrown by wire protocol version 2 commands. 662 663 The error is a formatter string and an optional iterable of arguments. 664 """ 665 666 def __init__(self, message, args=None): 667 # type: (bytes, Optional[Sequence[bytes]]) -> None 668 self.message = message 669 self.messageargs = args 670