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