##// END OF EJS Templates
lock: properly convert error to bytes...
marmoute -
r52179:81224afd default
parent child Browse files
Show More
@@ -1,692 +1,691 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
15 15 import difflib
16 16
17 17 from typing import (
18 18 Any,
19 19 AnyStr,
20 20 Iterable,
21 21 List,
22 22 Optional,
23 23 Sequence,
24 24 Union,
25 25 )
26 26
27 27 # Do not import anything but pycompat here, please
28 28 from . import pycompat
29 29
30 30
31 31 # keeps pyflakes happy
32 32 assert [
33 33 Any,
34 34 AnyStr,
35 35 Iterable,
36 36 List,
37 37 Optional,
38 38 Sequence,
39 39 Union,
40 40 ]
41 41
42 42
43 43 def _tobytes(exc):
44 44 # type: (...) -> bytes
45 45 """Byte-stringify exception in the same way as BaseException_str()"""
46 46 if not exc.args:
47 47 return b''
48 48 if len(exc.args) == 1:
49 49 return pycompat.bytestr(exc.args[0])
50 50 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
51 51
52 52
53 53 class Hint:
54 54 """Mix-in to provide a hint of an error
55 55
56 56 This should come first in the inheritance list to consume a hint and
57 57 pass remaining arguments to the exception class.
58 58 """
59 59
60 60 def __init__(self, *args, **kw):
61 61 self.hint = kw.pop('hint', None) # type: Optional[bytes]
62 62 super(Hint, self).__init__(*args, **kw)
63 63
64 64
65 65 class Error(Hint, Exception):
66 66 """Base class for Mercurial errors."""
67 67
68 68 coarse_exit_code = None
69 69 detailed_exit_code = None
70 70
71 71 def __init__(self, message, hint=None):
72 72 # type: (bytes, Optional[bytes]) -> None
73 73 self.message = message
74 74 self.hint = hint
75 75 # Pass the message into the Exception constructor to help extensions
76 76 # that look for exc.args[0].
77 77 Exception.__init__(self, message)
78 78
79 79 def __bytes__(self):
80 80 return self.message
81 81
82 82 def __str__(self):
83 83 # type: () -> str
84 84 # the output would be unreadable if the message was translated,
85 85 # but do not replace it with encoding.strfromlocal(), which
86 86 # may raise another exception.
87 87 return pycompat.sysstr(self.__bytes__())
88 88
89 89 def format(self):
90 90 # type: () -> bytes
91 91 from .i18n import _
92 92
93 93 message = _(b"abort: %s\n") % self.message
94 94 if self.hint:
95 95 message += _(b"(%s)\n") % self.hint
96 96 return message
97 97
98 98
99 99 class Abort(Error):
100 100 """Raised if a command needs to print an error and exit."""
101 101
102 102
103 103 class StorageError(Error):
104 104 """Raised when an error occurs in a storage layer.
105 105
106 106 Usually subclassed by a storage-specific exception.
107 107 """
108 108
109 109 detailed_exit_code = 50
110 110
111 111
112 112 class RevlogError(StorageError):
113 113 pass
114 114
115 115
116 116 class SidedataHashError(RevlogError):
117 117 def __init__(self, key, expected, got):
118 118 # type: (int, bytes, bytes) -> None
119 119 self.hint = None
120 120 self.sidedatakey = key
121 121 self.expecteddigest = expected
122 122 self.actualdigest = got
123 123
124 124
125 125 class FilteredIndexError(IndexError):
126 126 __bytes__ = _tobytes
127 127
128 128
129 129 class LookupError(RevlogError, KeyError):
130 130 def __init__(self, name, index, message):
131 131 # type: (bytes, bytes, bytes) -> None
132 132 self.name = name
133 133 self.index = index
134 134 # this can't be called 'message' because at least some installs of
135 135 # Python 2.6+ complain about the 'message' property being deprecated
136 136 self.lookupmessage = message
137 137 if isinstance(name, bytes) and len(name) == 20:
138 138 from .node import hex
139 139
140 140 name = hex(name)
141 141 # if name is a binary node, it can be None
142 142 RevlogError.__init__(
143 143 self, b'%s@%s: %s' % (index, pycompat.bytestr(name), message)
144 144 )
145 145
146 146 def __bytes__(self):
147 147 return RevlogError.__bytes__(self)
148 148
149 149 def __str__(self):
150 150 return RevlogError.__str__(self)
151 151
152 152
153 153 class AmbiguousPrefixLookupError(LookupError):
154 154 pass
155 155
156 156
157 157 class FilteredLookupError(LookupError):
158 158 pass
159 159
160 160
161 161 class ManifestLookupError(LookupError):
162 162 pass
163 163
164 164
165 165 class CommandError(Exception):
166 166 """Exception raised on errors in parsing the command line."""
167 167
168 168 def __init__(self, command, message):
169 169 # type: (Optional[bytes], bytes) -> None
170 170 self.command = command
171 171 self.message = message
172 172 super(CommandError, self).__init__()
173 173
174 174 __bytes__ = _tobytes
175 175
176 176
177 177 class UnknownCommand(Exception):
178 178 """Exception raised if command is not in the command table."""
179 179
180 180 def __init__(self, command, all_commands=None):
181 181 # type: (bytes, Optional[List[bytes]]) -> None
182 182 self.command = command
183 183 self.all_commands = all_commands
184 184 super(UnknownCommand, self).__init__()
185 185
186 186 __bytes__ = _tobytes
187 187
188 188
189 189 class AmbiguousCommand(Exception):
190 190 """Exception raised if command shortcut matches more than one command."""
191 191
192 192 def __init__(self, prefix, matches):
193 193 # type: (bytes, List[bytes]) -> None
194 194 self.prefix = prefix
195 195 self.matches = matches
196 196 super(AmbiguousCommand, self).__init__()
197 197
198 198 __bytes__ = _tobytes
199 199
200 200
201 201 class WorkerError(Exception):
202 202 """Exception raised when a worker process dies."""
203 203
204 204 def __init__(self, status_code):
205 205 # type: (int) -> None
206 206 self.status_code = status_code
207 207 # Pass status code to superclass just so it becomes part of __bytes__
208 208 super(WorkerError, self).__init__(status_code)
209 209
210 210 __bytes__ = _tobytes
211 211
212 212
213 213 class InterventionRequired(Abort):
214 214 """Exception raised when a command requires human intervention."""
215 215
216 216 coarse_exit_code = 1
217 217 detailed_exit_code = 240
218 218
219 219 def format(self):
220 220 # type: () -> bytes
221 221 from .i18n import _
222 222
223 223 message = _(b"%s\n") % self.message
224 224 if self.hint:
225 225 message += _(b"(%s)\n") % self.hint
226 226 return message
227 227
228 228
229 229 class ConflictResolutionRequired(InterventionRequired):
230 230 """Exception raised when a continuable command required merge conflict resolution."""
231 231
232 232 def __init__(self, opname):
233 233 # type: (bytes) -> None
234 234 from .i18n import _
235 235
236 236 self.opname = opname
237 237 InterventionRequired.__init__(
238 238 self,
239 239 _(
240 240 b"unresolved conflicts (see 'hg resolve', then 'hg %s --continue')"
241 241 )
242 242 % opname,
243 243 )
244 244
245 245
246 246 class InputError(Abort):
247 247 """Indicates that the user made an error in their input.
248 248
249 249 Examples: Invalid command, invalid flags, invalid revision.
250 250 """
251 251
252 252 detailed_exit_code = 10
253 253
254 254
255 255 class StateError(Abort):
256 256 """Indicates that the operation might work if retried in a different state.
257 257
258 258 Examples: Unresolved merge conflicts, unfinished operations.
259 259 """
260 260
261 261 detailed_exit_code = 20
262 262
263 263
264 264 class CanceledError(Abort):
265 265 """Indicates that the user canceled the operation.
266 266
267 267 Examples: Close commit editor with error status, quit chistedit.
268 268 """
269 269
270 270 detailed_exit_code = 250
271 271
272 272
273 273 class SecurityError(Abort):
274 274 """Indicates that some aspect of security failed.
275 275
276 276 Examples: Bad server credentials, expired local credentials for network
277 277 filesystem, mismatched GPG signature, DoS protection.
278 278 """
279 279
280 280 detailed_exit_code = 150
281 281
282 282
283 283 class HookLoadError(Abort):
284 284 """raised when loading a hook fails, aborting an operation
285 285
286 286 Exists to allow more specialized catching."""
287 287
288 288
289 289 class HookAbort(Abort):
290 290 """raised when a validation hook fails, aborting an operation
291 291
292 292 Exists to allow more specialized catching."""
293 293
294 294 detailed_exit_code = 40
295 295
296 296
297 297 class ConfigError(Abort):
298 298 """Exception raised when parsing config files"""
299 299
300 300 detailed_exit_code = 30
301 301
302 302 def __init__(self, message, location=None, hint=None):
303 303 # type: (bytes, Optional[bytes], Optional[bytes]) -> None
304 304 super(ConfigError, self).__init__(message, hint=hint)
305 305 self.location = location
306 306
307 307 def format(self):
308 308 # type: () -> bytes
309 309 from .i18n import _
310 310
311 311 if self.location is not None:
312 312 message = _(b"config error at %s: %s\n") % (
313 313 pycompat.bytestr(self.location),
314 314 self.message,
315 315 )
316 316 else:
317 317 message = _(b"config error: %s\n") % self.message
318 318 if self.hint:
319 319 message += _(b"(%s)\n") % self.hint
320 320 return message
321 321
322 322
323 323 class UpdateAbort(Abort):
324 324 """Raised when an update is aborted for destination issue"""
325 325
326 326
327 327 class MergeDestAbort(Abort):
328 328 """Raised when an update is aborted for destination issues"""
329 329
330 330
331 331 class NoMergeDestAbort(MergeDestAbort):
332 332 """Raised when an update is aborted because there is nothing to merge"""
333 333
334 334
335 335 class ManyMergeDestAbort(MergeDestAbort):
336 336 """Raised when an update is aborted because destination is ambiguous"""
337 337
338 338
339 339 class ResponseExpected(Abort):
340 340 """Raised when an EOF is received for a prompt"""
341 341
342 342 def __init__(self):
343 343 from .i18n import _
344 344
345 345 Abort.__init__(self, _(b'response expected'))
346 346
347 347
348 348 class RemoteError(Abort):
349 349 """Exception raised when interacting with a remote repo fails"""
350 350
351 351 detailed_exit_code = 100
352 352
353 353
354 354 class OutOfBandError(RemoteError):
355 355 """Exception raised when a remote repo reports failure"""
356 356
357 357 def __init__(self, message=None, hint=None):
358 358 # type: (Optional[bytes], Optional[bytes]) -> None
359 359 from .i18n import _
360 360
361 361 if message:
362 362 # Abort.format() adds a trailing newline
363 363 message = _(b"remote error:\n%s") % message.rstrip(b'\n')
364 364 else:
365 365 message = _(b"remote error")
366 366 super(OutOfBandError, self).__init__(message, hint=hint)
367 367
368 368
369 369 class ParseError(Abort):
370 370 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
371 371
372 372 detailed_exit_code = 10
373 373
374 374 def __init__(self, message, location=None, hint=None):
375 375 # type: (bytes, Optional[Union[bytes, int]], Optional[bytes]) -> None
376 376 super(ParseError, self).__init__(message, hint=hint)
377 377 self.location = location
378 378
379 379 def format(self):
380 380 # type: () -> bytes
381 381 from .i18n import _
382 382
383 383 if self.location is not None:
384 384 message = _(b"hg: parse error at %s: %s\n") % (
385 385 pycompat.bytestr(self.location),
386 386 self.message,
387 387 )
388 388 else:
389 389 message = _(b"hg: parse error: %s\n") % self.message
390 390 if self.hint:
391 391 message += _(b"(%s)\n") % self.hint
392 392 return message
393 393
394 394
395 395 class PatchError(Exception):
396 396 __bytes__ = _tobytes
397 397
398 398
399 399 class PatchParseError(PatchError):
400 400 __bytes__ = _tobytes
401 401
402 402
403 403 class PatchApplicationError(PatchError):
404 404 __bytes__ = _tobytes
405 405
406 406
407 407 def getsimilar(symbols, value):
408 408 # type: (Iterable[bytes], bytes) -> List[bytes]
409 409 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
410 410 # The cutoff for similarity here is pretty arbitrary. It should
411 411 # probably be investigated and tweaked.
412 412 return [s for s in symbols if sim(s) > 0.6]
413 413
414 414
415 415 def similarity_hint(similar):
416 416 # type: (List[bytes]) -> Optional[bytes]
417 417 from .i18n import _
418 418
419 419 if len(similar) == 1:
420 420 return _(b"did you mean %s?") % similar[0]
421 421 elif similar:
422 422 ss = b", ".join(sorted(similar))
423 423 return _(b"did you mean one of %s?") % ss
424 424 else:
425 425 return None
426 426
427 427
428 428 class UnknownIdentifier(ParseError):
429 429 """Exception raised when a {rev,file}set references an unknown identifier"""
430 430
431 431 def __init__(self, function, symbols):
432 432 # type: (bytes, Iterable[bytes]) -> None
433 433 from .i18n import _
434 434
435 435 similar = getsimilar(symbols, function)
436 436 hint = similarity_hint(similar)
437 437
438 438 ParseError.__init__(
439 439 self, _(b"unknown identifier: %s") % function, hint=hint
440 440 )
441 441
442 442
443 443 class RepoError(Hint, Exception):
444 444 __bytes__ = _tobytes
445 445
446 446
447 447 class RepoLookupError(RepoError):
448 448 pass
449 449
450 450
451 451 class FilteredRepoLookupError(RepoLookupError):
452 452 pass
453 453
454 454
455 455 class CapabilityError(RepoError):
456 456 pass
457 457
458 458
459 459 class RequirementError(RepoError):
460 460 """Exception raised if .hg/requires has an unknown entry."""
461 461
462 462
463 463 class StdioError(IOError):
464 464 """Raised if I/O to stdout or stderr fails"""
465 465
466 466 def __init__(self, err):
467 467 # type: (IOError) -> None
468 468 IOError.__init__(self, err.errno, err.strerror)
469 469
470 470 # no __bytes__() because error message is derived from the standard IOError
471 471
472 472
473 473 class UnsupportedMergeRecords(Abort):
474 474 def __init__(self, recordtypes):
475 475 # type: (Iterable[bytes]) -> None
476 476 from .i18n import _
477 477
478 478 self.recordtypes = sorted(recordtypes)
479 479 s = b' '.join(self.recordtypes)
480 480 Abort.__init__(
481 481 self,
482 482 _(b'unsupported merge state records: %s') % s,
483 483 hint=_(
484 484 b'see https://mercurial-scm.org/wiki/MergeStateRecords for '
485 485 b'more information'
486 486 ),
487 487 )
488 488
489 489
490 490 class UnknownVersion(Abort):
491 491 """generic exception for aborting from an encounter with an unknown version"""
492 492
493 493 def __init__(self, msg, hint=None, version=None):
494 494 # type: (bytes, Optional[bytes], Optional[bytes]) -> None
495 495 self.version = version
496 496 super(UnknownVersion, self).__init__(msg, hint=hint)
497 497
498 498
499 499 class LockError(IOError):
500 500 def __init__(self, errno, strerror, filename, desc):
501 # TODO: figure out if this should be bytes or str
502 # _type: (int, str, str, bytes) -> None
501 # _type: (int, str, bytes, bytes) -> None
503 502 IOError.__init__(self, errno, strerror, filename)
504 503 self.desc = desc
505 504
506 505 # no __bytes__() because error message is derived from the standard IOError
507 506
508 507
509 508 class LockHeld(LockError):
510 509 def __init__(self, errno, filename, desc, locker):
511 LockError.__init__(self, errno, b'Lock held', filename, desc)
510 LockError.__init__(self, errno, 'Lock held', filename, desc)
512 511 self.locker = locker
513 512
514 513
515 514 class LockUnavailable(LockError):
516 515 pass
517 516
518 517
519 518 # LockError is for errors while acquiring the lock -- this is unrelated
520 519 class LockInheritanceContractViolation(RuntimeError):
521 520 __bytes__ = _tobytes
522 521
523 522
524 523 class ResponseError(Exception):
525 524 """Raised to print an error with part of output and exit."""
526 525
527 526 __bytes__ = _tobytes
528 527
529 528
530 529 # derived from KeyboardInterrupt to simplify some breakout code
531 530 class SignalInterrupt(KeyboardInterrupt):
532 531 """Exception raised on SIGTERM and SIGHUP."""
533 532
534 533
535 534 class SignatureError(Exception):
536 535 __bytes__ = _tobytes
537 536
538 537
539 538 class PushRaced(RuntimeError):
540 539 """An exception raised during unbundling that indicate a push race"""
541 540
542 541 __bytes__ = _tobytes
543 542
544 543
545 544 class ProgrammingError(Hint, RuntimeError):
546 545 """Raised if a mercurial (core or extension) developer made a mistake"""
547 546
548 547 def __init__(self, msg, *args, **kwargs):
549 548 # type: (AnyStr, Any, Any) -> None
550 549 # On Python 3, turn the message back into a string since this is
551 550 # an internal-only error that won't be printed except in a
552 551 # stack traces.
553 552 msg = pycompat.sysstr(msg)
554 553 super(ProgrammingError, self).__init__(msg, *args, **kwargs)
555 554
556 555 __bytes__ = _tobytes
557 556
558 557
559 558 class WdirUnsupported(Exception):
560 559 """An exception which is raised when 'wdir()' is not supported"""
561 560
562 561 __bytes__ = _tobytes
563 562
564 563
565 564 # bundle2 related errors
566 565 class BundleValueError(ValueError):
567 566 """error raised when bundle2 cannot be processed"""
568 567
569 568 __bytes__ = _tobytes
570 569
571 570
572 571 class BundleUnknownFeatureError(BundleValueError):
573 572 def __init__(self, parttype=None, params=(), values=()):
574 573 self.parttype = parttype
575 574 self.params = params
576 575 self.values = values
577 576 if self.parttype is None:
578 577 msg = b'Stream Parameter'
579 578 else:
580 579 msg = parttype
581 580 entries = self.params
582 581 if self.params and self.values:
583 582 assert len(self.params) == len(self.values)
584 583 entries = []
585 584 for idx, par in enumerate(self.params):
586 585 val = self.values[idx]
587 586 if val is None:
588 587 entries.append(val)
589 588 else:
590 589 entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val)))
591 590 if entries:
592 591 msg = b'%s - %s' % (msg, b', '.join(entries))
593 592 ValueError.__init__(self, msg) # TODO: convert to str?
594 593
595 594
596 595 class ReadOnlyPartError(RuntimeError):
597 596 """error raised when code tries to alter a part being generated"""
598 597
599 598 __bytes__ = _tobytes
600 599
601 600
602 601 class PushkeyFailed(Abort):
603 602 """error raised when a pushkey part failed to update a value"""
604 603
605 604 def __init__(
606 605 self, partid, namespace=None, key=None, new=None, old=None, ret=None
607 606 ):
608 607 self.partid = partid
609 608 self.namespace = namespace
610 609 self.key = key
611 610 self.new = new
612 611 self.old = old
613 612 self.ret = ret
614 613 # no i18n expected to be processed into a better message
615 614 Abort.__init__(
616 615 self, b'failed to update value for "%s/%s"' % (namespace, key)
617 616 )
618 617
619 618
620 619 class CensoredNodeError(StorageError):
621 620 """error raised when content verification fails on a censored node
622 621
623 622 Also contains the tombstone data substituted for the uncensored data.
624 623 """
625 624
626 625 def __init__(self, filename, node, tombstone):
627 626 # type: (bytes, bytes, bytes) -> None
628 627 from .node import short
629 628
630 629 StorageError.__init__(self, b'%s:%s' % (filename, short(node)))
631 630 self.tombstone = tombstone
632 631
633 632
634 633 class CensoredBaseError(StorageError):
635 634 """error raised when a delta is rejected because its base is censored
636 635
637 636 A delta based on a censored revision must be formed as single patch
638 637 operation which replaces the entire base with new content. This ensures
639 638 the delta may be applied by clones which have not censored the base.
640 639 """
641 640
642 641
643 642 class InvalidBundleSpecification(Exception):
644 643 """error raised when a bundle specification is invalid.
645 644
646 645 This is used for syntax errors as opposed to support errors.
647 646 """
648 647
649 648 __bytes__ = _tobytes
650 649
651 650
652 651 class UnsupportedBundleSpecification(Exception):
653 652 """error raised when a bundle specification is not supported."""
654 653
655 654 __bytes__ = _tobytes
656 655
657 656
658 657 class CorruptedState(Exception):
659 658 """error raised when a command is not able to read its state from file"""
660 659
661 660 __bytes__ = _tobytes
662 661
663 662
664 663 class CorruptedDirstate(Exception):
665 664 """error raised the dirstate appears corrupted on-disk. It may be due to
666 665 a dirstate version mismatch (i.e. expecting v2 and finding v1 on disk)."""
667 666
668 667 __bytes__ = _tobytes
669 668
670 669
671 670 class PeerTransportError(Abort):
672 671 """Transport-level I/O error when communicating with a peer repo."""
673 672
674 673
675 674 class InMemoryMergeConflictsError(Exception):
676 675 """Exception raised when merge conflicts arose during an in-memory merge."""
677 676
678 677 __bytes__ = _tobytes
679 678
680 679
681 680 class WireprotoCommandError(Exception):
682 681 """Represents an error during execution of a wire protocol command.
683 682
684 683 Should only be thrown by wire protocol version 2 commands.
685 684
686 685 The error is a formatter string and an optional iterable of arguments.
687 686 """
688 687
689 688 def __init__(self, message, args=None):
690 689 # type: (bytes, Optional[Sequence[bytes]]) -> None
691 690 self.message = message
692 691 self.messageargs = args
@@ -1,392 +1,402 b''
1 1 # lock.py - simple advisory locking scheme for mercurial
2 2 #
3 3 # Copyright 2005, 2006 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
9 9 import contextlib
10 10 import errno
11 11 import os
12 12 import signal
13 13 import socket
14 14 import time
15 import typing
15 16 import warnings
16 17
17 18 from .i18n import _
18 19
19 20 from . import (
20 21 encoding,
21 22 error,
22 23 pycompat,
23 24 util,
24 25 )
25 26
26 27 from .utils import procutil
27 28
28 29
29 30 def _getlockprefix():
30 31 """Return a string which is used to differentiate pid namespaces
31 32
32 33 It's useful to detect "dead" processes and remove stale locks with
33 34 confidence. Typically it's just hostname. On modern linux, we include an
34 35 extra Linux-specific pid namespace identifier.
35 36 """
36 37 result = encoding.strtolocal(socket.gethostname())
37 38 if pycompat.sysplatform.startswith(b'linux'):
38 39 try:
39 40 result += b'/%x' % os.stat(b'/proc/self/ns/pid').st_ino
40 41 except (FileNotFoundError, PermissionError, NotADirectoryError):
41 42 pass
42 43 return result
43 44
44 45
45 46 @contextlib.contextmanager
46 47 def _delayedinterrupt():
47 48 """Block signal interrupt while doing something critical
48 49
49 50 This makes sure that the code block wrapped by this context manager won't
50 51 be interrupted.
51 52
52 53 For Windows developers: It appears not possible to guard time.sleep()
53 54 from CTRL_C_EVENT, so please don't use time.sleep() to test if this is
54 55 working.
55 56 """
56 57 assertedsigs = []
57 58 blocked = False
58 59 orighandlers = {}
59 60
60 61 def raiseinterrupt(num):
61 62 if num == getattr(signal, 'SIGINT', None) or num == getattr(
62 63 signal, 'CTRL_C_EVENT', None
63 64 ):
64 65 raise KeyboardInterrupt
65 66 else:
66 67 raise error.SignalInterrupt
67 68
68 69 def catchterm(num, frame):
69 70 if blocked:
70 71 assertedsigs.append(num)
71 72 else:
72 73 raiseinterrupt(num)
73 74
74 75 try:
75 76 # save handlers first so they can be restored even if a setup is
76 77 # interrupted between signal.signal() and orighandlers[] =.
77 78 for name in [
78 79 'CTRL_C_EVENT',
79 80 'SIGINT',
80 81 'SIGBREAK',
81 82 'SIGHUP',
82 83 'SIGTERM',
83 84 ]:
84 85 num = getattr(signal, name, None)
85 86 if num and num not in orighandlers:
86 87 orighandlers[num] = signal.getsignal(num)
87 88 try:
88 89 for num in orighandlers:
89 90 signal.signal(num, catchterm)
90 91 except ValueError:
91 92 pass # in a thread? no luck
92 93
93 94 blocked = True
94 95 yield
95 96 finally:
96 97 # no simple way to reliably restore all signal handlers because
97 98 # any loops, recursive function calls, except blocks, etc. can be
98 99 # interrupted. so instead, make catchterm() raise interrupt.
99 100 blocked = False
100 101 try:
101 102 for num, handler in orighandlers.items():
102 103 signal.signal(num, handler)
103 104 except ValueError:
104 105 pass # in a thread?
105 106
106 107 # re-raise interrupt exception if any, which may be shadowed by a new
107 108 # interrupt occurred while re-raising the first one
108 109 if assertedsigs:
109 110 raiseinterrupt(assertedsigs[0])
110 111
111 112
112 113 def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
113 114 """return an acquired lock or raise an a LockHeld exception
114 115
115 116 This function is responsible to issue warnings and or debug messages about
116 117 the held lock while trying to acquires it."""
117 118
118 119 def printwarning(printer, locker):
119 120 """issue the usual "waiting on lock" message through any channel"""
120 121 # show more details for new-style locks
121 122 if b':' in locker:
122 123 host, pid = locker.split(b":", 1)
123 124 msg = _(
124 125 b"waiting for lock on %s held by process %r on host %r\n"
125 126 ) % (
126 127 pycompat.bytestr(l.desc),
127 128 pycompat.bytestr(pid),
128 129 pycompat.bytestr(host),
129 130 )
130 131 else:
131 132 msg = _(b"waiting for lock on %s held by %r\n") % (
132 133 l.desc,
133 134 pycompat.bytestr(locker),
134 135 )
135 136 printer(msg)
136 137
137 138 l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
138 139
139 140 debugidx = 0 if (warntimeout and timeout) else -1
140 141 warningidx = 0
141 142 if not timeout:
142 143 warningidx = -1
143 144 elif warntimeout:
144 145 warningidx = warntimeout
145 146
146 147 delay = 0
147 148 while True:
148 149 try:
149 150 l._trylock()
150 151 break
151 152 except error.LockHeld as inst:
152 153 if delay == debugidx:
153 154 printwarning(ui.debug, inst.locker)
154 155 if delay == warningidx:
155 156 printwarning(ui.warn, inst.locker)
156 157 if timeout <= delay:
158 assert isinstance(inst.filename, bytes)
157 159 raise error.LockHeld(
158 errno.ETIMEDOUT, inst.filename, l.desc, inst.locker
160 errno.ETIMEDOUT,
161 typing.cast(bytes, inst.filename),
162 l.desc,
163 inst.locker,
159 164 )
160 165 time.sleep(1)
161 166 delay += 1
162 167
163 168 l.delay = delay
164 169 if l.delay:
165 170 if 0 <= warningidx <= l.delay:
166 171 ui.warn(_(b"got lock after %d seconds\n") % l.delay)
167 172 else:
168 173 ui.debug(b"got lock after %d seconds\n" % l.delay)
169 174 if l.acquirefn:
170 175 l.acquirefn()
171 176 return l
172 177
173 178
174 179 class lock:
175 180 """An advisory lock held by one process to control access to a set
176 181 of files. Non-cooperating processes or incorrectly written scripts
177 182 can ignore Mercurial's locking scheme and stomp all over the
178 183 repository, so don't do that.
179 184
180 185 Typically used via localrepository.lock() to lock the repository
181 186 store (.hg/store/) or localrepository.wlock() to lock everything
182 187 else under .hg/."""
183 188
184 189 # lock is symlink on platforms that support it, file on others.
185 190
186 191 # symlink is used because create of directory entry and contents
187 192 # are atomic even over nfs.
188 193
189 194 # old-style lock: symlink to pid
190 195 # new-style lock: symlink to hostname:pid
191 196
192 197 _host = None
193 198
194 199 def __init__(
195 200 self,
196 201 vfs,
197 202 fname,
198 203 timeout=-1,
199 204 releasefn=None,
200 205 acquirefn=None,
201 206 desc=None,
202 207 signalsafe=True,
203 208 dolock=True,
204 209 ):
205 210 self.vfs = vfs
206 211 self.f = fname
207 212 self.held = 0
208 213 self.timeout = timeout
209 214 self.releasefn = releasefn
210 215 self.acquirefn = acquirefn
211 216 self.desc = desc
212 217 if signalsafe:
213 218 self._maybedelayedinterrupt = _delayedinterrupt
214 219 else:
215 220 self._maybedelayedinterrupt = util.nullcontextmanager
216 221 self.postrelease = []
217 222 self.pid = self._getpid()
218 223 if dolock:
219 224 self.delay = self.lock()
220 225 if self.acquirefn:
221 226 self.acquirefn()
222 227
223 228 def __enter__(self):
224 229 return self
225 230
226 231 def __exit__(self, exc_type, exc_value, exc_tb):
227 232 success = all(a is None for a in (exc_type, exc_value, exc_tb))
228 233 self.release(success=success)
229 234
230 235 def __del__(self):
231 236 if self.held:
232 237 warnings.warn(
233 238 "use lock.release instead of del lock",
234 239 category=DeprecationWarning,
235 240 stacklevel=2,
236 241 )
237 242
238 243 # ensure the lock will be removed
239 244 # even if recursive locking did occur
240 245 self.held = 1
241 246
242 247 self.release()
243 248
244 249 def _getpid(self):
245 250 # wrapper around procutil.getpid() to make testing easier
246 251 return procutil.getpid()
247 252
248 253 def lock(self):
249 254 timeout = self.timeout
250 255 while True:
251 256 try:
252 257 self._trylock()
253 258 return self.timeout - timeout
254 259 except error.LockHeld as inst:
255 260 if timeout != 0:
256 261 time.sleep(1)
257 262 if timeout > 0:
258 263 timeout -= 1
259 264 continue
260 265 raise error.LockHeld(
261 266 errno.ETIMEDOUT, inst.filename, self.desc, inst.locker
262 267 )
263 268
264 269 def _trylock(self):
265 270 if self.held:
266 271 self.held += 1
267 272 return
268 273 if lock._host is None:
269 274 lock._host = _getlockprefix()
270 275 lockname = b'%s:%d' % (lock._host, self.pid)
271 276 retry = 5
272 277 while not self.held and retry:
273 278 retry -= 1
274 279 try:
275 280 with self._maybedelayedinterrupt():
276 281 self.vfs.makelock(lockname, self.f)
277 282 self.held = 1
278 283 except (OSError, IOError) as why:
279 284 if why.errno == errno.EEXIST:
280 285 locker = self._readlock()
281 286 if locker is None:
282 287 continue
283 288
284 289 locker = self._testlock(locker)
285 290 if locker is not None:
286 291 raise error.LockHeld(
287 292 errno.EAGAIN,
288 293 self.vfs.join(self.f),
289 294 self.desc,
290 295 locker,
291 296 )
292 297 else:
298 assert isinstance(why.filename, bytes)
299 assert isinstance(why.strerror, str)
293 300 raise error.LockUnavailable(
294 why.errno, why.strerror, why.filename, self.desc
301 why.errno,
302 why.strerror,
303 typing.cast(bytes, why.filename),
304 self.desc,
295 305 )
296 306
297 307 if not self.held:
298 308 # use empty locker to mean "busy for frequent lock/unlock
299 309 # by many processes"
300 310 raise error.LockHeld(
301 311 errno.EAGAIN, self.vfs.join(self.f), self.desc, b""
302 312 )
303 313
304 314 def _readlock(self):
305 315 """read lock and return its value
306 316
307 317 Returns None if no lock exists, pid for old-style locks, and host:pid
308 318 for new-style locks.
309 319 """
310 320 try:
311 321 return self.vfs.readlock(self.f)
312 322 except FileNotFoundError:
313 323 return None
314 324
315 325 def _lockshouldbebroken(self, locker):
316 326 if locker is None:
317 327 return False
318 328 try:
319 329 host, pid = locker.split(b":", 1)
320 330 except ValueError:
321 331 return False
322 332 if host != lock._host:
323 333 return False
324 334 try:
325 335 pid = int(pid)
326 336 except ValueError:
327 337 return False
328 338 if procutil.testpid(pid):
329 339 return False
330 340 return True
331 341
332 342 def _testlock(self, locker):
333 343 if not self._lockshouldbebroken(locker):
334 344 return locker
335 345
336 346 # if locker dead, break lock. must do this with another lock
337 347 # held, or can race and break valid lock.
338 348 try:
339 349 with lock(self.vfs, self.f + b'.break', timeout=0):
340 350 locker = self._readlock()
341 351 if not self._lockshouldbebroken(locker):
342 352 return locker
343 353 self.vfs.unlink(self.f)
344 354 except error.LockError:
345 355 return locker
346 356
347 357 def testlock(self):
348 358 """return id of locker if lock is valid, else None.
349 359
350 360 If old-style lock, we cannot tell what machine locker is on.
351 361 with new-style lock, if locker is on this machine, we can
352 362 see if locker is alive. If locker is on this machine but
353 363 not alive, we can safely break lock.
354 364
355 365 The lock file is only deleted when None is returned.
356 366
357 367 """
358 368 locker = self._readlock()
359 369 return self._testlock(locker)
360 370
361 371 def release(self, success=True):
362 372 """release the lock and execute callback function if any
363 373
364 374 If the lock has been acquired multiple times, the actual release is
365 375 delayed to the last release call."""
366 376 if self.held > 1:
367 377 self.held -= 1
368 378 elif self.held == 1:
369 379 self.held = 0
370 380 if self._getpid() != self.pid:
371 381 # we forked, and are not the parent
372 382 return
373 383 try:
374 384 if self.releasefn:
375 385 self.releasefn()
376 386 finally:
377 387 try:
378 388 self.vfs.unlink(self.f)
379 389 except OSError:
380 390 pass
381 391 # The postrelease functions typically assume the lock is not held
382 392 # at all.
383 393 for callback in self.postrelease:
384 394 callback(success)
385 395 # Prevent double usage and help clear cycles.
386 396 self.postrelease = None
387 397
388 398
389 399 def release(*locks):
390 400 for lock in locks:
391 401 if lock is not None:
392 402 lock.release()
@@ -1,271 +1,271 b''
1 1 # statichttprepo.py - simple http repository class for mercurial
2 2 #
3 3 # This provides read-only repo access to repositories exported via static http
4 4 #
5 5 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10
11 11 import errno
12 12
13 13 from .i18n import _
14 14 from .node import sha1nodeconstants
15 15 from . import (
16 16 branchmap,
17 17 changelog,
18 18 error,
19 19 localrepo,
20 20 manifest,
21 21 namespaces,
22 22 pathutil,
23 23 pycompat,
24 24 requirements as requirementsmod,
25 25 url,
26 26 util,
27 27 vfs as vfsmod,
28 28 )
29 29 from .utils import (
30 30 urlutil,
31 31 )
32 32
33 33 urlerr = util.urlerr
34 34 urlreq = util.urlreq
35 35
36 36
37 37 class httprangereader:
38 38 def __init__(self, url, opener):
39 39 # we assume opener has HTTPRangeHandler
40 40 self.url = url
41 41 self.pos = 0
42 42 self.opener = opener
43 43 self.name = url
44 44
45 45 def __enter__(self):
46 46 return self
47 47
48 48 def __exit__(self, exc_type, exc_value, traceback):
49 49 self.close()
50 50
51 51 def seek(self, pos):
52 52 self.pos = pos
53 53
54 54 def read(self, bytes=None):
55 55 req = urlreq.request(pycompat.strurl(self.url))
56 56 end = b''
57 57 if bytes:
58 58 end = self.pos + bytes - 1
59 59 if self.pos or end:
60 60 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
61 61
62 62 try:
63 63 f = self.opener.open(req)
64 64 data = f.read()
65 65 code = f.code
66 66 except urlerr.httperror as inst:
67 67 num = inst.code == 404 and errno.ENOENT or None
68 68 # Explicitly convert the exception to str as Py3 will try
69 69 # convert it to local encoding and with as the HTTPResponse
70 70 # instance doesn't support encode.
71 71 raise IOError(num, str(inst))
72 72 except urlerr.urlerror as inst:
73 73 raise IOError(None, inst.reason)
74 74
75 75 if code == 200:
76 76 # HTTPRangeHandler does nothing if remote does not support
77 77 # Range headers and returns the full entity. Let's slice it.
78 78 if bytes:
79 79 data = data[self.pos : self.pos + bytes]
80 80 else:
81 81 data = data[self.pos :]
82 82 elif bytes:
83 83 data = data[:bytes]
84 84 self.pos += len(data)
85 85 return data
86 86
87 87 def readlines(self):
88 88 return self.read().splitlines(True)
89 89
90 90 def __iter__(self):
91 91 return iter(self.readlines())
92 92
93 93 def close(self):
94 94 pass
95 95
96 96
97 97 # _RangeError and _HTTPRangeHandler were originally in byterange.py,
98 98 # which was itself extracted from urlgrabber. See the last version of
99 99 # byterange.py from history if you need more information.
100 100 class _RangeError(IOError):
101 101 """Error raised when an unsatisfiable range is requested."""
102 102
103 103
104 104 class _HTTPRangeHandler(urlreq.basehandler):
105 105 """Handler that enables HTTP Range headers.
106 106
107 107 This was extremely simple. The Range header is a HTTP feature to
108 108 begin with so all this class does is tell urllib2 that the
109 109 "206 Partial Content" response from the HTTP server is what we
110 110 expected.
111 111 """
112 112
113 113 def http_error_206(self, req, fp, code, msg, hdrs):
114 114 # 206 Partial Content Response
115 115 r = urlreq.addinfourl(fp, hdrs, req.get_full_url())
116 116 r.code = code
117 117 r.msg = msg
118 118 return r
119 119
120 120 def http_error_416(self, req, fp, code, msg, hdrs):
121 121 # HTTP's Range Not Satisfiable error
122 122 raise _RangeError('Requested Range Not Satisfiable')
123 123
124 124
125 125 def build_opener(ui, authinfo):
126 126 # urllib cannot handle URLs with embedded user or passwd
127 127 urlopener = url.opener(ui, authinfo)
128 128 urlopener.add_handler(_HTTPRangeHandler())
129 129
130 130 class statichttpvfs(vfsmod.abstractvfs):
131 131 def __init__(self, base):
132 132 self.base = base
133 133 self.options = {}
134 134
135 135 def __call__(self, path, mode=b'r', *args, **kw):
136 136 if mode not in (b'r', b'rb'):
137 137 raise IOError('Permission denied')
138 138 f = b"/".join((self.base, urlreq.quote(path)))
139 139 return httprangereader(f, urlopener)
140 140
141 141 def join(self, path, *insidef):
142 142 if path:
143 143 return pathutil.join(self.base, path, *insidef)
144 144 else:
145 145 return self.base
146 146
147 147 return statichttpvfs
148 148
149 149
150 150 class statichttppeer(localrepo.localpeer):
151 151 def local(self):
152 152 return None
153 153
154 154 def canpush(self):
155 155 return False
156 156
157 157
158 158 class statichttprepository(
159 159 localrepo.localrepository, localrepo.revlogfilestorage
160 160 ):
161 161 supported = localrepo.localrepository._basesupported
162 162
163 163 def __init__(self, ui, path):
164 164 self._url = path
165 165 self.ui = ui
166 166
167 167 self.root = path
168 168 u = urlutil.url(path.rstrip(b'/') + b"/.hg")
169 169 self.path, authinfo = u.authinfo()
170 170
171 171 vfsclass = build_opener(ui, authinfo)
172 172 self.vfs = vfsclass(self.path)
173 173 self.cachevfs = vfsclass(self.vfs.join(b'cache'))
174 174 self._phasedefaults = []
175 175
176 176 self.names = namespaces.namespaces()
177 177 self.filtername = None
178 178 self._extrafilterid = None
179 179 self._wanted_sidedata = set()
180 180 self.features = set()
181 181
182 182 try:
183 183 requirements = set(self.vfs.read(b'requires').splitlines())
184 184 except FileNotFoundError:
185 185 requirements = set()
186 186
187 187 # check if it is a non-empty old-style repository
188 188 try:
189 189 fp = self.vfs(b"00changelog.i")
190 190 fp.read(1)
191 191 fp.close()
192 192 except FileNotFoundError:
193 193 # we do not care about empty old-style repositories here
194 194 msg = _(b"'%s' does not appear to be an hg repository") % path
195 195 raise error.RepoError(msg)
196 196 if requirementsmod.SHARESAFE_REQUIREMENT in requirements:
197 197 storevfs = vfsclass(self.vfs.join(b'store'))
198 198 requirements |= set(storevfs.read(b'requires').splitlines())
199 199
200 200 supportedrequirements = localrepo.gathersupportedrequirements(ui)
201 201 localrepo.ensurerequirementsrecognized(
202 202 requirements, supportedrequirements
203 203 )
204 204 localrepo.ensurerequirementscompatible(ui, requirements)
205 205 self.nodeconstants = sha1nodeconstants
206 206 self.nullid = self.nodeconstants.nullid
207 207
208 208 # setup store
209 209 self.store = localrepo.makestore(requirements, self.path, vfsclass)
210 210 self.spath = self.store.path
211 211 self.svfs = self.store.opener
212 212 self.sjoin = self.store.join
213 213 self._filecache = {}
214 214 self.requirements = requirements
215 215
216 216 rootmanifest = manifest.manifestrevlog(self.nodeconstants, self.svfs)
217 217 self.manifestlog = manifest.manifestlog(
218 218 self.svfs, self, rootmanifest, self.narrowmatch()
219 219 )
220 220 self.changelog = changelog.changelog(self.svfs)
221 221 self._tags = None
222 222 self.nodetagscache = None
223 223 self._branchcaches = branchmap.BranchMapCache()
224 224 self._revbranchcache = None
225 225 self.encodepats = None
226 226 self.decodepats = None
227 227 self._transref = None
228 228 self._dirstate = None
229 229
230 230 def _restrictcapabilities(self, caps):
231 231 caps = super(statichttprepository, self)._restrictcapabilities(caps)
232 232 return caps.difference([b"pushkey"])
233 233
234 234 def url(self):
235 235 return self._url
236 236
237 237 def local(self):
238 238 return False
239 239
240 240 def peer(self, path=None, remotehidden=False):
241 241 return statichttppeer(self, path=path, remotehidden=remotehidden)
242 242
243 243 def wlock(self, wait=True):
244 244 raise error.LockUnavailable(
245 245 0,
246 _(b'lock not available'),
246 pycompat.sysstr(_(b'lock not available')),
247 247 b'lock',
248 248 _(b'cannot lock static-http repository'),
249 249 )
250 250
251 251 def lock(self, wait=True):
252 252 raise error.LockUnavailable(
253 253 0,
254 _(b'lock not available'),
254 pycompat.sysstr(_(b'lock not available')),
255 255 b'lock',
256 256 _(b'cannot lock static-http repository'),
257 257 )
258 258
259 259 def _writecaches(self):
260 260 pass # statichttprepository are read only
261 261
262 262
263 263 def make_peer(
264 264 ui, path, create, intents=None, createopts=None, remotehidden=False
265 265 ):
266 266 if create:
267 267 raise error.Abort(_(b'cannot create new static-http repository'))
268 268 url = path.loc[7:]
269 269 return statichttprepository(ui, url).peer(
270 270 path=path, remotehidden=remotehidden
271 271 )
General Comments 0
You need to be logged in to leave comments. Login now