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, |
|
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, |
|
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, |
|
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