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