##// END OF EJS Templates
manifest: add many type annotations to the manifest module...
marmoute -
r52667:79e0ee35 default
parent child Browse files
Show More
@@ -1,2436 +1,2530 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 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 heapq
9 import heapq
10 import itertools
10 import itertools
11 import struct
11 import struct
12 import weakref
12 import weakref
13
13
14 from typing import (
14 from typing import (
15 ByteString,
16 Callable,
17 Dict,
15 Iterable,
18 Iterable,
19 Iterator,
20 List,
21 Optional,
22 Set,
23 Tuple,
24 Union,
25 cast,
16 )
26 )
17
27
18 from .i18n import _
28 from .i18n import _
19 from .node import (
29 from .node import (
20 bin,
30 bin,
21 hex,
31 hex,
22 nullrev,
32 nullrev,
23 )
33 )
24 from . import (
34 from . import (
25 encoding,
35 encoding,
26 error,
36 error,
27 match as matchmod,
37 match as matchmod,
28 mdiff,
38 mdiff,
29 pathutil,
39 pathutil,
30 policy,
40 policy,
31 pycompat,
41 pycompat,
32 revlog,
42 revlog,
33 util,
43 util,
34 )
44 )
35 from .interfaces import (
45 from .interfaces import (
36 repository,
46 repository,
37 util as interfaceutil,
47 util as interfaceutil,
38 )
48 )
39 from .revlogutils import (
49 from .revlogutils import (
40 constants as revlog_constants,
50 constants as revlog_constants,
41 )
51 )
42
52
43 parsers = policy.importmod('parsers')
53 parsers = policy.importmod('parsers')
44 propertycache = util.propertycache
54 propertycache = util.propertycache
45
55
46 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
56 # Allow tests to more easily test the alternate path in manifestdict.fastdelta()
47 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
57 FASTDELTA_TEXTDIFF_THRESHOLD = 1000
48
58
49
59
50 def _parse(nodelen, data):
60 def _parse(nodelen, data: bytes):
51 # This method does a little bit of excessive-looking
61 # This method does a little bit of excessive-looking
52 # precondition checking. This is so that the behavior of this
62 # precondition checking. This is so that the behavior of this
53 # class exactly matches its C counterpart to try and help
63 # class exactly matches its C counterpart to try and help
54 # prevent surprise breakage for anyone that develops against
64 # prevent surprise breakage for anyone that develops against
55 # the pure version.
65 # the pure version.
56 if data and data[-1:] != b'\n':
66 if data and data[-1:] != b'\n':
57 raise ValueError(b'Manifest did not end in a newline.')
67 raise ValueError(b'Manifest did not end in a newline.')
58 prev = None
68 prev = None
59 for l in data.splitlines():
69 for l in data.splitlines():
60 if prev is not None and prev > l:
70 if prev is not None and prev > l:
61 raise ValueError(b'Manifest lines not in sorted order.')
71 raise ValueError(b'Manifest lines not in sorted order.')
62 prev = l
72 prev = l
63 f, n = l.split(b'\0')
73 f, n = l.split(b'\0')
64 nl = len(n)
74 nl = len(n)
65 flags = n[-1:]
75 flags = n[-1:]
66 if flags in _manifestflags:
76 if flags in _manifestflags:
67 n = n[:-1]
77 n = n[:-1]
68 nl -= 1
78 nl -= 1
69 else:
79 else:
70 flags = b''
80 flags = b''
71 if nl != 2 * nodelen:
81 if nl != 2 * nodelen:
72 raise ValueError(b'Invalid manifest line')
82 raise ValueError(b'Invalid manifest line')
73
83
74 yield f, bin(n), flags
84 yield f, bin(n), flags
75
85
76
86
77 def _text(it):
87 def _text(it):
78 files = []
88 files = []
79 lines = []
89 lines = []
80 for f, n, fl in it:
90 for f, n, fl in it:
81 files.append(f)
91 files.append(f)
82 # if this is changed to support newlines in filenames,
92 # if this is changed to support newlines in filenames,
83 # be sure to check the templates/ dir again (especially *-raw.tmpl)
93 # be sure to check the templates/ dir again (especially *-raw.tmpl)
84 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
94 lines.append(b"%s\0%s%s\n" % (f, hex(n), fl))
85
95
86 _checkforbidden(files)
96 _checkforbidden(files)
87 return b''.join(lines)
97 return b''.join(lines)
88
98
89
99
90 class lazymanifestiter:
100 class lazymanifestiter:
91 def __init__(self, lm):
101 def __init__(self, lm: '_LazyManifest') -> None:
92 self.pos = 0
102 self.pos = 0
93 self.lm = lm
103 self.lm = lm
94
104
95 def __iter__(self):
105 def __iter__(self) -> 'lazymanifestiter':
96 return self
106 return self
97
107
98 def next(self):
108 def next(self) -> bytes:
99 try:
109 try:
100 data, pos = self.lm._get(self.pos)
110 data, pos = self.lm._get(self.pos)
101 except IndexError:
111 except IndexError:
102 raise StopIteration
112 raise StopIteration
103 if pos == -1:
113 if pos == -1:
114 assert isinstance(data, tuple)
104 self.pos += 1
115 self.pos += 1
105 return data[0]
116 return data[0]
117 assert isinstance(data, bytes)
106 self.pos += 1
118 self.pos += 1
107 zeropos = data.find(b'\x00', pos)
119 zeropos = data.find(b'\x00', pos)
108 return data[pos:zeropos]
120 return data[pos:zeropos]
109
121
110 __next__ = next
122 __next__ = next
111
123
112
124
113 class lazymanifestiterentries:
125 class lazymanifestiterentries:
114 def __init__(self, lm):
126 def __init__(self, lm: '_LazyManifest') -> None:
115 self.lm = lm
127 self.lm = lm
116 self.pos = 0
128 self.pos = 0
117
129
118 def __iter__(self):
130 def __iter__(self) -> 'lazymanifestiterentries':
119 return self
131 return self
120
132
121 def next(self):
133 def next(self) -> Tuple[bytes, bytes, bytes]:
122 try:
134 try:
123 data, pos = self.lm._get(self.pos)
135 data, pos = self.lm._get(self.pos)
124 except IndexError:
136 except IndexError:
125 raise StopIteration
137 raise StopIteration
126 if pos == -1:
138 if pos == -1:
139 assert isinstance(data, tuple)
127 self.pos += 1
140 self.pos += 1
128 return data
141 return data
142 assert isinstance(data, bytes)
129 zeropos = data.find(b'\x00', pos)
143 zeropos = data.find(b'\x00', pos)
130 nlpos = data.find(b'\n', pos)
144 nlpos = data.find(b'\n', pos)
131 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
145 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
132 raise error.StorageError(b'Invalid manifest line')
146 raise error.StorageError(b'Invalid manifest line')
133 flags = data[nlpos - 1 : nlpos]
147 flags = data[nlpos - 1 : nlpos]
134 if flags in _manifestflags:
148 if flags in _manifestflags:
135 hlen = nlpos - zeropos - 2
149 hlen = nlpos - zeropos - 2
136 else:
150 else:
137 hlen = nlpos - zeropos - 1
151 hlen = nlpos - zeropos - 1
138 flags = b''
152 flags = b''
139 if hlen != 2 * self.lm._nodelen:
153 if hlen != 2 * self.lm._nodelen:
140 raise error.StorageError(b'Invalid manifest line')
154 raise error.StorageError(b'Invalid manifest line')
141 hashval = unhexlify(
155 hashval = unhexlify(
142 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
156 data, self.lm.extrainfo[self.pos], zeropos + 1, hlen
143 )
157 )
144 self.pos += 1
158 self.pos += 1
145 return (data[pos:zeropos], hashval, flags)
159 return (data[pos:zeropos], hashval, flags)
146
160
147 __next__ = next
161 __next__ = next
148
162
149
163
150 def unhexlify(data: bytes, extra: int, pos, length: int):
164 def unhexlify(data: bytes, extra: int, pos, length: int):
151 s = bin(data[pos : pos + length])
165 s = bin(data[pos : pos + length])
152 if extra:
166 if extra:
153 s += bytes([extra & 0xFF])
167 s += bytes([extra & 0xFF])
154 return s
168 return s
155
169
156
170
157 def _cmp(a, b):
171 def _cmp(a, b):
158 return (a > b) - (a < b)
172 return (a > b) - (a < b)
159
173
160
174
161 _manifestflags = {b'', b'l', b't', b'x'}
175 _manifestflags = {b'', b'l', b't', b'x'}
162
176
163
177
164 class _LazyManifest:
178 class _LazyManifest:
165 """A pure python manifest backed by a byte string. It is supplimented with
179 """A pure python manifest backed by a byte string. It is supplimented with
166 internal lists as it is modified, until it is compacted back to a pure byte
180 internal lists as it is modified, until it is compacted back to a pure byte
167 string.
181 string.
168
182
169 ``data`` is the initial manifest data.
183 ``data`` is the initial manifest data.
170
184
171 ``positions`` is a list of offsets, one per manifest entry. Positive
185 ``positions`` is a list of offsets, one per manifest entry. Positive
172 values are offsets into ``data``, negative values are offsets into the
186 values are offsets into ``data``, negative values are offsets into the
173 ``extradata`` list. When an entry is removed, its entry is dropped from
187 ``extradata`` list. When an entry is removed, its entry is dropped from
174 ``positions``. The values are encoded such that when walking the list and
188 ``positions``. The values are encoded such that when walking the list and
175 indexing into ``data`` or ``extradata`` as appropriate, the entries are
189 indexing into ``data`` or ``extradata`` as appropriate, the entries are
176 sorted by filename.
190 sorted by filename.
177
191
178 ``extradata`` is a list of (key, hash, flags) for entries that were added or
192 ``extradata`` is a list of (key, hash, flags) for entries that were added or
179 modified since the manifest was created or compacted.
193 modified since the manifest was created or compacted.
180 """
194 """
181
195
182 def __init__(
196 def __init__(
183 self,
197 self,
184 nodelen,
198 nodelen: int,
185 data,
199 data: bytes,
186 positions=None,
200 positions=None,
187 extrainfo=None,
201 extrainfo=None,
188 extradata=None,
202 extradata=None,
189 hasremovals=False,
203 hasremovals: bool = False,
190 ):
204 ):
191 self._nodelen = nodelen
205 self._nodelen = nodelen
192 if positions is None:
206 if positions is None:
193 self.positions = self.findlines(data)
207 self.positions = self.findlines(data)
194 self.extrainfo = [0] * len(self.positions)
208 self.extrainfo = [0] * len(self.positions)
195 self.data = data
209 self.data = data
196 self.extradata = []
210 self.extradata = []
197 self.hasremovals = False
211 self.hasremovals = False
198 else:
212 else:
199 self.positions = positions[:]
213 self.positions = positions[:]
200 self.extrainfo = extrainfo[:]
214 self.extrainfo = extrainfo[:]
201 self.extradata = extradata[:]
215 self.extradata = extradata[:]
202 self.data = data
216 self.data = data
203 self.hasremovals = hasremovals
217 self.hasremovals = hasremovals
204
218
205 def findlines(self, data):
219 def findlines(self, data: bytes) -> List[int]:
206 if not data:
220 if not data:
207 return []
221 return []
208 pos = data.find(b"\n")
222 pos = data.find(b"\n")
209 if pos == -1 or data[-1:] != b'\n':
223 if pos == -1 or data[-1:] != b'\n':
210 raise ValueError(b"Manifest did not end in a newline.")
224 raise ValueError(b"Manifest did not end in a newline.")
211 positions = [0]
225 positions = [0]
212 prev = data[: data.find(b'\x00')]
226 prev = data[: data.find(b'\x00')]
213 while pos < len(data) - 1 and pos != -1:
227 while pos < len(data) - 1 and pos != -1:
214 positions.append(pos + 1)
228 positions.append(pos + 1)
215 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
229 nexts = data[pos + 1 : data.find(b'\x00', pos + 1)]
216 if nexts < prev:
230 if nexts < prev:
217 raise ValueError(b"Manifest lines not in sorted order.")
231 raise ValueError(b"Manifest lines not in sorted order.")
218 prev = nexts
232 prev = nexts
219 pos = data.find(b"\n", pos + 1)
233 pos = data.find(b"\n", pos + 1)
220 return positions
234 return positions
221
235
222 def _get(self, index):
236 def _get(
237 self, index: int
238 ) -> Tuple[Union[bytes, Tuple[bytes, bytes, bytes]], int]:
223 # get the position encoded in pos:
239 # get the position encoded in pos:
224 # positive number is an index in 'data'
240 # positive number is an index in 'data'
225 # negative number is in extrapieces
241 # negative number is in extrapieces
226 pos = self.positions[index]
242 pos = self.positions[index]
227 if pos >= 0:
243 if pos >= 0:
228 return self.data, pos
244 return self.data, pos
229 return self.extradata[-pos - 1], -1
245 return self.extradata[-pos - 1], -1
230
246
231 def _getkey(self, pos):
247 def _getkey(self, pos) -> bytes:
232 if pos >= 0:
248 if pos >= 0:
233 return self.data[pos : self.data.find(b'\x00', pos + 1)]
249 return self.data[pos : self.data.find(b'\x00', pos + 1)]
234 return self.extradata[-pos - 1][0]
250 return self.extradata[-pos - 1][0]
235
251
236 def bsearch(self, key):
252 def bsearch(self, key: bytes) -> int:
237 first = 0
253 first = 0
238 last = len(self.positions) - 1
254 last = len(self.positions) - 1
239
255
240 while first <= last:
256 while first <= last:
241 midpoint = (first + last) // 2
257 midpoint = (first + last) // 2
242 nextpos = self.positions[midpoint]
258 nextpos = self.positions[midpoint]
243 candidate = self._getkey(nextpos)
259 candidate = self._getkey(nextpos)
244 r = _cmp(key, candidate)
260 r = _cmp(key, candidate)
245 if r == 0:
261 if r == 0:
246 return midpoint
262 return midpoint
247 else:
263 else:
248 if r < 0:
264 if r < 0:
249 last = midpoint - 1
265 last = midpoint - 1
250 else:
266 else:
251 first = midpoint + 1
267 first = midpoint + 1
252 return -1
268 return -1
253
269
254 def bsearch2(self, key):
270 def bsearch2(self, key: bytes) -> Tuple[int, bool]:
255 # same as the above, but will always return the position
271 # same as the above, but will always return the position
256 # done for performance reasons
272 # done for performance reasons
257 first = 0
273 first = 0
258 last = len(self.positions) - 1
274 last = len(self.positions) - 1
259
275
260 while first <= last:
276 while first <= last:
261 midpoint = (first + last) // 2
277 midpoint = (first + last) // 2
262 nextpos = self.positions[midpoint]
278 nextpos = self.positions[midpoint]
263 candidate = self._getkey(nextpos)
279 candidate = self._getkey(nextpos)
264 r = _cmp(key, candidate)
280 r = _cmp(key, candidate)
265 if r == 0:
281 if r == 0:
266 return (midpoint, True)
282 return (midpoint, True)
267 else:
283 else:
268 if r < 0:
284 if r < 0:
269 last = midpoint - 1
285 last = midpoint - 1
270 else:
286 else:
271 first = midpoint + 1
287 first = midpoint + 1
272 return (first, False)
288 return (first, False)
273
289
274 def __contains__(self, key):
290 def __contains__(self, key: bytes) -> bool:
275 return self.bsearch(key) != -1
291 return self.bsearch(key) != -1
276
292
277 def __getitem__(self, key):
293 def __getitem__(self, key: bytes) -> Tuple[bytes, bytes]:
278 if not isinstance(key, bytes):
294 if not isinstance(key, bytes):
279 raise TypeError(b"getitem: manifest keys must be a bytes.")
295 raise TypeError(b"getitem: manifest keys must be a bytes.")
280 needle = self.bsearch(key)
296 needle = self.bsearch(key)
281 if needle == -1:
297 if needle == -1:
282 raise KeyError
298 raise KeyError
283 data, pos = self._get(needle)
299 data, pos = self._get(needle)
284 if pos == -1:
300 if pos == -1:
301 assert isinstance(data, tuple)
285 return (data[1], data[2])
302 return (data[1], data[2])
303
304 assert isinstance(data, bytes)
286 zeropos = data.find(b'\x00', pos)
305 zeropos = data.find(b'\x00', pos)
287 nlpos = data.find(b'\n', zeropos)
306 nlpos = data.find(b'\n', zeropos)
288 assert 0 <= needle <= len(self.positions)
307 assert 0 <= needle <= len(self.positions)
289 assert len(self.extrainfo) == len(self.positions)
308 assert len(self.extrainfo) == len(self.positions)
290 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
309 if zeropos == -1 or nlpos == -1 or nlpos < zeropos:
291 raise error.StorageError(b'Invalid manifest line')
310 raise error.StorageError(b'Invalid manifest line')
292 hlen = nlpos - zeropos - 1
311 hlen = nlpos - zeropos - 1
293 flags = data[nlpos - 1 : nlpos]
312 flags = data[nlpos - 1 : nlpos]
294 if flags in _manifestflags:
313 if flags in _manifestflags:
295 hlen -= 1
314 hlen -= 1
296 else:
315 else:
297 flags = b''
316 flags = b''
298 if hlen != 2 * self._nodelen:
317 if hlen != 2 * self._nodelen:
299 raise error.StorageError(b'Invalid manifest line')
318 raise error.StorageError(b'Invalid manifest line')
300 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
319 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen)
301 return (hashval, flags)
320 return (hashval, flags)
302
321
303 def __delitem__(self, key):
322 def __delitem__(self, key: bytes) -> None:
304 needle, found = self.bsearch2(key)
323 needle, found = self.bsearch2(key)
305 if not found:
324 if not found:
306 raise KeyError
325 raise KeyError
307 cur = self.positions[needle]
326 cur = self.positions[needle]
308 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
327 self.positions = self.positions[:needle] + self.positions[needle + 1 :]
309 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
328 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :]
310 if cur >= 0:
329 if cur >= 0:
311 # This does NOT unsort the list as far as the search functions are
330 # This does NOT unsort the list as far as the search functions are
312 # concerned, as they only examine lines mapped by self.positions.
331 # concerned, as they only examine lines mapped by self.positions.
313 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
332 self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :]
314 self.hasremovals = True
333 self.hasremovals = True
315
334
316 def __setitem__(self, key, value):
335 def __setitem__(self, key: bytes, value: Tuple[bytes, bytes]):
317 if not isinstance(key, bytes):
336 if not isinstance(key, bytes):
318 raise TypeError(b"setitem: manifest keys must be a byte string.")
337 raise TypeError(b"setitem: manifest keys must be a byte string.")
319 if not isinstance(value, tuple) or len(value) != 2:
338 if not isinstance(value, tuple) or len(value) != 2:
320 raise TypeError(
339 raise TypeError(
321 b"Manifest values must be a tuple of (node, flags)."
340 b"Manifest values must be a tuple of (node, flags)."
322 )
341 )
323 hashval = value[0]
342 hashval = value[0]
324 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
343 if not isinstance(hashval, bytes) or len(hashval) not in (20, 32):
325 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
344 raise TypeError(b"node must be a 20-byte or 32-byte byte string")
326 flags = value[1]
345 flags = value[1]
327 if not isinstance(flags, bytes) or len(flags) > 1:
346 if not isinstance(flags, bytes) or len(flags) > 1:
328 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
347 raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags)
329 needle, found = self.bsearch2(key)
348 needle, found = self.bsearch2(key)
330 if found:
349 if found:
331 # put the item
350 # put the item
332 pos = self.positions[needle]
351 pos = self.positions[needle]
333 if pos < 0:
352 if pos < 0:
334 self.extradata[-pos - 1] = (key, hashval, value[1])
353 self.extradata[-pos - 1] = (key, hashval, value[1])
335 else:
354 else:
336 # just don't bother
355 # just don't bother
337 self.extradata.append((key, hashval, value[1]))
356 self.extradata.append((key, hashval, value[1]))
338 self.positions[needle] = -len(self.extradata)
357 self.positions[needle] = -len(self.extradata)
339 else:
358 else:
340 # not found, put it in with extra positions
359 # not found, put it in with extra positions
341 self.extradata.append((key, hashval, value[1]))
360 self.extradata.append((key, hashval, value[1]))
342 self.positions = (
361 self.positions = (
343 self.positions[:needle]
362 self.positions[:needle]
344 + [-len(self.extradata)]
363 + [-len(self.extradata)]
345 + self.positions[needle:]
364 + self.positions[needle:]
346 )
365 )
347 self.extrainfo = (
366 self.extrainfo = (
348 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
367 self.extrainfo[:needle] + [0] + self.extrainfo[needle:]
349 )
368 )
350
369
351 def copy(self):
370 def copy(self) -> '_LazyManifest':
352 # XXX call _compact like in C?
371 # XXX call _compact like in C?
353 return _lazymanifest(
372 return _lazymanifest(
354 self._nodelen,
373 self._nodelen,
355 self.data,
374 self.data,
356 self.positions,
375 self.positions,
357 self.extrainfo,
376 self.extrainfo,
358 self.extradata,
377 self.extradata,
359 self.hasremovals,
378 self.hasremovals,
360 )
379 )
361
380
362 def _compact(self):
381 def _compact(self) -> None:
363 # hopefully not called TOO often
382 # hopefully not called TOO often
364 if len(self.extradata) == 0 and not self.hasremovals:
383 if len(self.extradata) == 0 and not self.hasremovals:
365 return
384 return
366 l = []
385 l = []
367 i = 0
386 i = 0
368 offset = 0
387 offset = 0
369 self.extrainfo = [0] * len(self.positions)
388 self.extrainfo = [0] * len(self.positions)
370 while i < len(self.positions):
389 while i < len(self.positions):
371 if self.positions[i] >= 0:
390 if self.positions[i] >= 0:
372 cur = self.positions[i]
391 cur = self.positions[i]
373 last_cut = cur
392 last_cut = cur
374
393
375 # Collect all contiguous entries in the buffer at the current
394 # Collect all contiguous entries in the buffer at the current
376 # offset, breaking out only for added/modified items held in
395 # offset, breaking out only for added/modified items held in
377 # extradata, or a deleted line prior to the next position.
396 # extradata, or a deleted line prior to the next position.
378 while True:
397 while True:
379 self.positions[i] = offset
398 self.positions[i] = offset
380 i += 1
399 i += 1
381 if i == len(self.positions) or self.positions[i] < 0:
400 if i == len(self.positions) or self.positions[i] < 0:
382 break
401 break
383
402
384 # A removed file has no positions[] entry, but does have an
403 # A removed file has no positions[] entry, but does have an
385 # overwritten first byte. Break out and find the end of the
404 # overwritten first byte. Break out and find the end of the
386 # current good entry/entries if there is a removed file
405 # current good entry/entries if there is a removed file
387 # before the next position.
406 # before the next position.
388 if (
407 if (
389 self.hasremovals
408 self.hasremovals
390 and self.data.find(b'\n\x00', cur, self.positions[i])
409 and self.data.find(b'\n\x00', cur, self.positions[i])
391 != -1
410 != -1
392 ):
411 ):
393 break
412 break
394
413
395 offset += self.positions[i] - cur
414 offset += self.positions[i] - cur
396 cur = self.positions[i]
415 cur = self.positions[i]
397 end_cut = self.data.find(b'\n', cur)
416 end_cut = self.data.find(b'\n', cur)
398 if end_cut != -1:
417 if end_cut != -1:
399 end_cut += 1
418 end_cut += 1
400 offset += end_cut - cur
419 offset += end_cut - cur
401 l.append(self.data[last_cut:end_cut])
420 l.append(self.data[last_cut:end_cut])
402 else:
421 else:
403 while i < len(self.positions) and self.positions[i] < 0:
422 while i < len(self.positions) and self.positions[i] < 0:
404 cur = self.positions[i]
423 cur = self.positions[i]
405 t = self.extradata[-cur - 1]
424 t = self.extradata[-cur - 1]
406 l.append(self._pack(t))
425 l.append(self._pack(t))
407 self.positions[i] = offset
426 self.positions[i] = offset
408 # Hashes are either 20 bytes (old sha1s) or 32
427 # Hashes are either 20 bytes (old sha1s) or 32
409 # bytes (new non-sha1).
428 # bytes (new non-sha1).
410 hlen = 20
429 hlen = 20
411 if len(t[1]) > 25:
430 if len(t[1]) > 25:
412 hlen = 32
431 hlen = 32
413 if len(t[1]) > hlen:
432 if len(t[1]) > hlen:
414 self.extrainfo[i] = ord(t[1][hlen + 1])
433 self.extrainfo[i] = ord(t[1][hlen + 1])
415 offset += len(l[-1])
434 offset += len(l[-1])
416 i += 1
435 i += 1
417 self.data = b''.join(l)
436 self.data = b''.join(l)
418 self.hasremovals = False
437 self.hasremovals = False
419 self.extradata = []
438 self.extradata = []
420
439
421 def _pack(self, d):
440 def _pack(self, d: Tuple[bytes, bytes, bytes]) -> bytes:
422 n = d[1]
441 n = d[1]
423 assert len(n) in (20, 32)
442 assert len(n) in (20, 32)
424 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
443 return d[0] + b'\x00' + hex(n) + d[2] + b'\n'
425
444
426 def text(self):
445 def text(self) -> ByteString:
427 self._compact()
446 self._compact()
428 return self.data
447 return self.data
429
448
430 def diff(self, m2, clean=False):
449 def diff(
450 self, m2: '_LazyManifest', clean: bool = False
451 ) -> Dict[
452 bytes,
453 Optional[
454 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
455 ],
456 ]:
431 '''Finds changes between the current manifest and m2.'''
457 '''Finds changes between the current manifest and m2.'''
432 # XXX think whether efficiency matters here
458 # XXX think whether efficiency matters here
433 diff = {}
459 diff = {}
434
460
435 for fn, e1, flags in self.iterentries():
461 for fn, e1, flags in self.iterentries():
436 if fn not in m2:
462 if fn not in m2:
437 diff[fn] = (e1, flags), (None, b'')
463 diff[fn] = (e1, flags), (None, b'')
438 else:
464 else:
439 e2 = m2[fn]
465 e2 = m2[fn]
440 if (e1, flags) != e2:
466 if (e1, flags) != e2:
441 diff[fn] = (e1, flags), e2
467 diff[fn] = (e1, flags), e2
442 elif clean:
468 elif clean:
443 diff[fn] = None
469 diff[fn] = None
444
470
445 for fn, e2, flags in m2.iterentries():
471 for fn, e2, flags in m2.iterentries():
446 if fn not in self:
472 if fn not in self:
447 diff[fn] = (None, b''), (e2, flags)
473 diff[fn] = (None, b''), (e2, flags)
448
474
449 return diff
475 return diff
450
476
451 def iterentries(self):
477 def iterentries(self) -> lazymanifestiterentries:
452 return lazymanifestiterentries(self)
478 return lazymanifestiterentries(self)
453
479
454 def iterkeys(self):
480 def iterkeys(self) -> lazymanifestiter:
455 return lazymanifestiter(self)
481 return lazymanifestiter(self)
456
482
457 def __iter__(self):
483 def __iter__(self) -> lazymanifestiter:
458 return lazymanifestiter(self)
484 return lazymanifestiter(self)
459
485
460 def __len__(self):
486 def __len__(self) -> int:
461 return len(self.positions)
487 return len(self.positions)
462
488
463 def filtercopy(self, filterfn):
489 def filtercopy(self, filterfn: Callable[[bytes], bool]) -> '_LazyManifest':
464 # XXX should be optimized
490 # XXX should be optimized
465 c = _lazymanifest(self._nodelen, b'')
491 c = _lazymanifest(self._nodelen, b'')
466 for f, n, fl in self.iterentries():
492 for f, n, fl in self.iterentries():
467 if filterfn(f):
493 if filterfn(f):
468 c[f] = n, fl
494 c[f] = n, fl
469 return c
495 return c
470
496
471
497
472 try:
498 try:
473 _lazymanifest = parsers.lazymanifest
499 _lazymanifest = parsers.lazymanifest
474 except AttributeError:
500 except AttributeError:
475 _lazymanifest = _LazyManifest
501 _lazymanifest = _LazyManifest
476
502
477
503
478 class ManifestDict:
504 class ManifestDict:
479 def __init__(self, nodelen, data=b''):
505 def __init__(self, nodelen: int, data: ByteString = b''):
480 self._nodelen = nodelen
506 self._nodelen = nodelen
481 self._lm = _lazymanifest(nodelen, data)
507 self._lm = _lazymanifest(nodelen, data)
482
508
483 def __getitem__(self, key):
509 def __getitem__(self, key: bytes) -> bytes:
484 return self._lm[key][0]
510 return self._lm[key][0]
485
511
486 def find(self, key):
512 def find(self, key: bytes) -> Tuple[bytes, bytes]:
487 return self._lm[key]
513 return self._lm[key]
488
514
489 def __len__(self):
515 def __len__(self) -> int:
490 return len(self._lm)
516 return len(self._lm)
491
517
492 def __nonzero__(self):
518 def __nonzero__(self) -> bool:
493 # nonzero is covered by the __len__ function, but implementing it here
519 # nonzero is covered by the __len__ function, but implementing it here
494 # makes it easier for extensions to override.
520 # makes it easier for extensions to override.
495 return len(self._lm) != 0
521 return len(self._lm) != 0
496
522
497 __bool__ = __nonzero__
523 __bool__ = __nonzero__
498
524
499 def set(self, key, node, flags):
525 def set(self, key: bytes, node: bytes, flags: bytes) -> None:
500 self._lm[key] = node, flags
526 self._lm[key] = node, flags
501
527
502 def __setitem__(self, key, node):
528 def __setitem__(self, key: bytes, node: bytes) -> None:
503 self._lm[key] = node, self.flags(key)
529 self._lm[key] = node, self.flags(key)
504
530
505 def __contains__(self, key):
531 def __contains__(self, key: bytes) -> bool:
506 if key is None:
532 if key is None:
507 return False
533 return False
508 return key in self._lm
534 return key in self._lm
509
535
510 def __delitem__(self, key):
536 def __delitem__(self, key: bytes) -> bool:
511 del self._lm[key]
537 del self._lm[key]
512
538
513 def __iter__(self):
539 def __iter__(self) -> Iterator[bytes]:
514 return self._lm.__iter__()
540 return self._lm.__iter__()
515
541
516 def iterkeys(self):
542 def iterkeys(self) -> Iterator[bytes]:
517 return self._lm.iterkeys()
543 return self._lm.iterkeys()
518
544
519 def keys(self):
545 def keys(self) -> List[bytes]:
520 return list(self.iterkeys())
546 return list(self.iterkeys())
521
547
522 def filesnotin(self, m2, match=None):
548 def filesnotin(self, m2, match=None) -> Set[bytes]:
523 '''Set of files in this manifest that are not in the other'''
549 '''Set of files in this manifest that are not in the other'''
524 if match is not None:
550 if match is not None:
525 match = matchmod.badmatch(match, lambda path, msg: None)
551 match = matchmod.badmatch(match, lambda path, msg: None)
526 sm2 = set(m2.walk(match))
552 sm2 = set(m2.walk(match))
527 return {f for f in self.walk(match) if f not in sm2}
553 return {f for f in self.walk(match) if f not in sm2}
528 return {f for f in self if f not in m2}
554 return {f for f in self if f not in m2}
529
555
530 @propertycache
556 @propertycache
531 def _dirs(self):
557 def _dirs(self) -> pathutil.dirs:
532 return pathutil.dirs(self)
558 return pathutil.dirs(self)
533
559
534 def dirs(self):
560 def dirs(self) -> pathutil.dirs:
535 return self._dirs
561 return self._dirs
536
562
537 def hasdir(self, dir):
563 def hasdir(self, dir: bytes) -> bool:
538 return dir in self._dirs
564 return dir in self._dirs
539
565
540 def _filesfastpath(self, match):
566 def _filesfastpath(self, match: matchmod.basematcher) -> bool:
541 """Checks whether we can correctly and quickly iterate over matcher
567 """Checks whether we can correctly and quickly iterate over matcher
542 files instead of over manifest files."""
568 files instead of over manifest files."""
543 files = match.files()
569 files = match.files()
544 return len(files) < 100 and (
570 return len(files) < 100 and (
545 match.isexact()
571 match.isexact()
546 or (match.prefix() and all(fn in self for fn in files))
572 or (match.prefix() and all(fn in self for fn in files))
547 )
573 )
548
574
549 def walk(self, match):
575 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
550 """Generates matching file names.
576 """Generates matching file names.
551
577
552 Equivalent to manifest.matches(match).iterkeys(), but without creating
578 Equivalent to manifest.matches(match).iterkeys(), but without creating
553 an entirely new manifest.
579 an entirely new manifest.
554
580
555 It also reports nonexistent files by marking them bad with match.bad().
581 It also reports nonexistent files by marking them bad with match.bad().
556 """
582 """
557 if match.always():
583 if match.always():
558 for f in iter(self):
584 for f in iter(self):
559 yield f
585 yield f
560 return
586 return
561
587
562 fset = set(match.files())
588 fset = set(match.files())
563
589
564 # avoid the entire walk if we're only looking for specific files
590 # avoid the entire walk if we're only looking for specific files
565 if self._filesfastpath(match):
591 if self._filesfastpath(match):
566 for fn in sorted(fset):
592 for fn in sorted(fset):
567 if fn in self:
593 if fn in self:
568 yield fn
594 yield fn
569 return
595 return
570
596
571 for fn in self:
597 for fn in self:
572 if fn in fset:
598 if fn in fset:
573 # specified pattern is the exact name
599 # specified pattern is the exact name
574 fset.remove(fn)
600 fset.remove(fn)
575 if match(fn):
601 if match(fn):
576 yield fn
602 yield fn
577
603
578 # for dirstate.walk, files=[''] means "walk the whole tree".
604 # for dirstate.walk, files=[''] means "walk the whole tree".
579 # follow that here, too
605 # follow that here, too
580 fset.discard(b'')
606 fset.discard(b'')
581
607
582 for fn in sorted(fset):
608 for fn in sorted(fset):
583 if not self.hasdir(fn):
609 if not self.hasdir(fn):
584 match.bad(fn, None)
610 match.bad(fn, None)
585
611
586 def _matches(self, match):
612 def _matches(self, match: matchmod.basematcher) -> 'ManifestDict':
587 '''generate a new manifest filtered by the match argument'''
613 '''generate a new manifest filtered by the match argument'''
588 if match.always():
614 if match.always():
589 return self.copy()
615 return self.copy()
590
616
591 if self._filesfastpath(match):
617 if self._filesfastpath(match):
592 m = manifestdict(self._nodelen)
618 m = manifestdict(self._nodelen)
593 lm = self._lm
619 lm = self._lm
594 for fn in match.files():
620 for fn in match.files():
595 if fn in lm:
621 if fn in lm:
596 m._lm[fn] = lm[fn]
622 m._lm[fn] = lm[fn]
597 return m
623 return m
598
624
599 m = manifestdict(self._nodelen)
625 m = manifestdict(self._nodelen)
600 m._lm = self._lm.filtercopy(match)
626 m._lm = self._lm.filtercopy(match)
601 return m
627 return m
602
628
603 def diff(self, m2, match=None, clean=False):
629 def diff(
630 self,
631 m2: 'ManifestDict',
632 match: Optional[matchmod.basematcher] = None,
633 clean: bool = False,
634 ) -> Dict[
635 bytes,
636 Optional[
637 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
638 ],
639 ]:
604 """Finds changes between the current manifest and m2.
640 """Finds changes between the current manifest and m2.
605
641
606 Args:
642 Args:
607 m2: the manifest to which this manifest should be compared.
643 m2: the manifest to which this manifest should be compared.
608 clean: if true, include files unchanged between these manifests
644 clean: if true, include files unchanged between these manifests
609 with a None value in the returned dictionary.
645 with a None value in the returned dictionary.
610
646
611 The result is returned as a dict with filename as key and
647 The result is returned as a dict with filename as key and
612 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
648 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
613 nodeid in the current/other manifest and fl1/fl2 is the flag
649 nodeid in the current/other manifest and fl1/fl2 is the flag
614 in the current/other manifest. Where the file does not exist,
650 in the current/other manifest. Where the file does not exist,
615 the nodeid will be None and the flags will be the empty
651 the nodeid will be None and the flags will be the empty
616 string.
652 string.
617 """
653 """
618 if match:
654 if match:
619 m1 = self._matches(match)
655 m1 = self._matches(match)
620 m2 = m2._matches(match)
656 m2 = m2._matches(match)
621 return m1.diff(m2, clean=clean)
657 return m1.diff(m2, clean=clean)
622 return self._lm.diff(m2._lm, clean)
658 return self._lm.diff(m2._lm, clean)
623
659
624 def setflag(self, key, flag):
660 def setflag(self, key: bytes, flag: bytes) -> None:
625 if flag not in _manifestflags:
661 if flag not in _manifestflags:
626 raise TypeError(b"Invalid manifest flag set.")
662 raise TypeError(b"Invalid manifest flag set.")
627 self._lm[key] = self[key], flag
663 self._lm[key] = self[key], flag
628
664
629 def get(self, key, default=None):
665 def get(self, key: bytes, default=None) -> Optional[bytes]:
630 try:
666 try:
631 return self._lm[key][0]
667 return self._lm[key][0]
632 except KeyError:
668 except KeyError:
633 return default
669 return default
634
670
635 def flags(self, key):
671 def flags(self, key: bytes) -> bytes:
636 try:
672 try:
637 return self._lm[key][1]
673 return self._lm[key][1]
638 except KeyError:
674 except KeyError:
639 return b''
675 return b''
640
676
641 def copy(self):
677 def copy(self) -> 'ManifestDict':
642 c = manifestdict(self._nodelen)
678 c = manifestdict(self._nodelen)
643 c._lm = self._lm.copy()
679 c._lm = self._lm.copy()
644 return c
680 return c
645
681
646 def items(self):
682 def items(self) -> Iterator[Tuple[bytes, bytes]]:
647 return (x[:2] for x in self._lm.iterentries())
683 return (x[:2] for x in self._lm.iterentries())
648
684
649 def iteritems(self):
685 def iteritems(self) -> Iterator[Tuple[bytes, bytes]]:
650 return (x[:2] for x in self._lm.iterentries())
686 return (x[:2] for x in self._lm.iterentries())
651
687
652 def iterentries(self):
688 def iterentries(self) -> Iterator[Tuple[bytes, bytes, bytes]]:
653 return self._lm.iterentries()
689 return self._lm.iterentries()
654
690
655 def text(self):
691 def text(self) -> ByteString:
656 # most likely uses native version
692 # most likely uses native version
657 return self._lm.text()
693 return self._lm.text()
658
694
659 def fastdelta(self, base, changes):
695 def fastdelta(
696 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
697 ) -> Tuple[ByteString, ByteString]:
660 """Given a base manifest text as a bytearray and a list of changes
698 """Given a base manifest text as a bytearray and a list of changes
661 relative to that text, compute a delta that can be used by revlog.
699 relative to that text, compute a delta that can be used by revlog.
662 """
700 """
663 delta = []
701 delta = []
664 dstart = None
702 dstart = None
665 dend = None
703 dend = None
666 dline = [b""]
704 dline = [b""]
667 start = 0
705 start = 0
668 # zero copy representation of base as a buffer
706 # zero copy representation of base as a buffer
669 addbuf = util.buffer(base)
707 addbuf = util.buffer(base)
670
708
671 changes = list(changes)
709 changes = list(changes)
672 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
710 if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD:
673 # start with a readonly loop that finds the offset of
711 # start with a readonly loop that finds the offset of
674 # each line and creates the deltas
712 # each line and creates the deltas
675 for f, todelete in changes:
713 for f, todelete in changes:
676 # bs will either be the index of the item or the insert point
714 # bs will either be the index of the item or the insert point
677 start, end = _msearch(addbuf, f, start)
715 start, end = _msearch(addbuf, f, start)
678 if not todelete:
716 if not todelete:
679 h, fl = self._lm[f]
717 h, fl = self._lm[f]
680 l = b"%s\0%s%s\n" % (f, hex(h), fl)
718 l = b"%s\0%s%s\n" % (f, hex(h), fl)
681 else:
719 else:
682 if start == end:
720 if start == end:
683 # item we want to delete was not found, error out
721 # item we want to delete was not found, error out
684 raise AssertionError(
722 raise AssertionError(
685 _(b"failed to remove %s from manifest") % f
723 _(b"failed to remove %s from manifest") % f
686 )
724 )
687 l = b""
725 l = b""
688 if dstart is not None and dstart <= start and dend >= start:
726 if dstart is not None and dstart <= start and dend >= start:
689 if dend < end:
727 if dend < end:
690 dend = end
728 dend = end
691 if l:
729 if l:
692 dline.append(l)
730 dline.append(l)
693 else:
731 else:
694 if dstart is not None:
732 if dstart is not None:
695 delta.append((dstart, dend, b"".join(dline)))
733 delta.append((dstart, dend, b"".join(dline)))
696 dstart = start
734 dstart = start
697 dend = end
735 dend = end
698 dline = [l]
736 dline = [l]
699
737
700 if dstart is not None:
738 if dstart is not None:
701 delta.append((dstart, dend, b"".join(dline)))
739 delta.append((dstart, dend, b"".join(dline)))
702 # apply the delta to the base, and get a delta for addrevision
740 # apply the delta to the base, and get a delta for addrevision
703 deltatext, arraytext = _addlistdelta(base, delta)
741 deltatext, arraytext = _addlistdelta(base, delta)
704 else:
742 else:
705 # For large changes, it's much cheaper to just build the text and
743 # For large changes, it's much cheaper to just build the text and
706 # diff it.
744 # diff it.
707 arraytext = bytearray(self.text())
745 arraytext = bytearray(self.text())
708 deltatext = mdiff.textdiff(
746 deltatext = mdiff.textdiff(
709 util.buffer(base), util.buffer(arraytext)
747 util.buffer(base), util.buffer(arraytext)
710 )
748 )
711
749
712 return arraytext, deltatext
750 return arraytext, deltatext
713
751
714
752
715 manifestdict = interfaceutil.implementer(repository.imanifestdict)(ManifestDict)
753 manifestdict = interfaceutil.implementer(repository.imanifestdict)(ManifestDict)
716
754
717
755
718 def _msearch(m, s, lo=0, hi=None):
756 def _msearch(
757 m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None
758 ) -> Tuple[int, int]:
719 """return a tuple (start, end) that says where to find s within m.
759 """return a tuple (start, end) that says where to find s within m.
720
760
721 If the string is found m[start:end] are the line containing
761 If the string is found m[start:end] are the line containing
722 that string. If start == end the string was not found and
762 that string. If start == end the string was not found and
723 they indicate the proper sorted insertion point.
763 they indicate the proper sorted insertion point.
724
764 """
725 m should be a buffer, a memoryview or a byte string.
765
726 s is a byte string"""
766 def advance(i: int, c: bytes):
727
728 def advance(i, c):
729 while i < lenm and m[i : i + 1] != c:
767 while i < lenm and m[i : i + 1] != c:
730 i += 1
768 i += 1
731 return i
769 return i
732
770
733 if not s:
771 if not s:
734 return (lo, lo)
772 return (lo, lo)
735 lenm = len(m)
773 lenm = len(m)
736 if not hi:
774 if not hi:
737 hi = lenm
775 hi = lenm
738 while lo < hi:
776 while lo < hi:
739 mid = (lo + hi) // 2
777 mid = (lo + hi) // 2
740 start = mid
778 start = mid
741 while start > 0 and m[start - 1 : start] != b'\n':
779 while start > 0 and m[start - 1 : start] != b'\n':
742 start -= 1
780 start -= 1
743 end = advance(start, b'\0')
781 end = advance(start, b'\0')
744 if bytes(m[start:end]) < s:
782 if bytes(m[start:end]) < s:
745 # we know that after the null there are 40 bytes of sha1
783 # we know that after the null there are 40 bytes of sha1
746 # this translates to the bisect lo = mid + 1
784 # this translates to the bisect lo = mid + 1
747 lo = advance(end + 40, b'\n') + 1
785 lo = advance(end + 40, b'\n') + 1
748 else:
786 else:
749 # this translates to the bisect hi = mid
787 # this translates to the bisect hi = mid
750 hi = start
788 hi = start
751 end = advance(lo, b'\0')
789 end = advance(lo, b'\0')
752 found = m[lo:end]
790 found = m[lo:end]
753 if s == found:
791 if s == found:
754 # we know that after the null there are 40 bytes of sha1
792 # we know that after the null there are 40 bytes of sha1
755 end = advance(end + 40, b'\n')
793 end = advance(end + 40, b'\n')
756 return (lo, end + 1)
794 return (lo, end + 1)
757 else:
795 else:
758 return (lo, lo)
796 return (lo, lo)
759
797
760
798
761 def _checkforbidden(l):
799 def _checkforbidden(l: Iterable[bytes]) -> None:
762 """Check filenames for illegal characters."""
800 """Check filenames for illegal characters."""
763 for f in l:
801 for f in l:
764 if b'\n' in f or b'\r' in f:
802 if b'\n' in f or b'\r' in f:
765 raise error.StorageError(
803 raise error.StorageError(
766 _(b"'\\n' and '\\r' disallowed in filenames: %r")
804 _(b"'\\n' and '\\r' disallowed in filenames: %r")
767 % pycompat.bytestr(f)
805 % pycompat.bytestr(f)
768 )
806 )
769
807
770
808
771 # apply the changes collected during the bisect loop to our addlist
809 # apply the changes collected during the bisect loop to our addlist
772 # return a delta suitable for addrevision
810 # return a delta suitable for addrevision
773 def _addlistdelta(addlist, x):
811 def _addlistdelta(
812 addlist: ByteString,
813 x: Iterable[Tuple[int, int, bytes]],
814 ) -> Tuple[bytes, ByteString]:
774 # for large addlist arrays, building a new array is cheaper
815 # for large addlist arrays, building a new array is cheaper
775 # than repeatedly modifying the existing one
816 # than repeatedly modifying the existing one
776 currentposition = 0
817 currentposition = 0
777 newaddlist = bytearray()
818 newaddlist = bytearray()
778
819
779 for start, end, content in x:
820 for start, end, content in x:
780 newaddlist += addlist[currentposition:start]
821 newaddlist += addlist[currentposition:start]
781 if content:
822 if content:
782 newaddlist += bytearray(content)
823 newaddlist += bytearray(content)
783
824
784 currentposition = end
825 currentposition = end
785
826
786 newaddlist += addlist[currentposition:]
827 newaddlist += addlist[currentposition:]
787
828
788 deltatext = b"".join(
829 deltatext = b"".join(
789 struct.pack(b">lll", start, end, len(content)) + content
830 struct.pack(b">lll", start, end, len(content)) + content
790 for start, end, content in x
831 for start, end, content in x
791 )
832 )
792 return deltatext, newaddlist
833 return deltatext, newaddlist
793
834
794
835
795 def _splittopdir(f):
836 def _splittopdir(f: bytes) -> Tuple[bytes, bytes]:
796 if b'/' in f:
837 if b'/' in f:
797 dir, subpath = f.split(b'/', 1)
838 dir, subpath = f.split(b'/', 1)
798 return dir + b'/', subpath
839 return dir + b'/', subpath
799 else:
840 else:
800 return b'', f
841 return b'', f
801
842
802
843
803 _noop = lambda s: None
844 _noop = lambda s: None
804
845
805
846
806 class TreeManifest:
847 class TreeManifest:
807 def __init__(self, nodeconstants, dir=b'', text=b''):
848 def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''):
808 self._dir = dir
849 self._dir = dir
809 self.nodeconstants = nodeconstants
850 self.nodeconstants = nodeconstants
810 self._node = self.nodeconstants.nullid
851 self._node = self.nodeconstants.nullid
811 self._nodelen = self.nodeconstants.nodelen
852 self._nodelen = self.nodeconstants.nodelen
812 self._loadfunc = _noop
853 self._loadfunc = _noop
813 self._copyfunc = _noop
854 self._copyfunc = _noop
814 self._dirty = False
855 self._dirty = False
815 self._dirs = {}
856 self._dirs: Dict[bytes, 'TreeManifest'] = {}
816 self._lazydirs = {}
857 self._lazydirs: Dict[
858 bytes,
859 Tuple[bytes, Callable[[bytes, bytes], 'TreeManifest'], bool],
860 ] = {}
817 # Using _lazymanifest here is a little slower than plain old dicts
861 # Using _lazymanifest here is a little slower than plain old dicts
818 self._files = {}
862 self._files: Dict[bytes, bytes] = {}
819 self._flags = {}
863 self._flags = {}
820 if text:
864 if text:
821
865
822 def readsubtree(subdir, subm):
866 def readsubtree(subdir, subm):
823 raise AssertionError(
867 raise AssertionError(
824 b'treemanifest constructor only accepts flat manifests'
868 b'treemanifest constructor only accepts flat manifests'
825 )
869 )
826
870
827 self.parse(text, readsubtree)
871 self.parse(text, readsubtree)
828 self._dirty = True # Mark flat manifest dirty after parsing
872 self._dirty = True # Mark flat manifest dirty after parsing
829
873
830 def _subpath(self, path):
874 def _subpath(self, path: bytes) -> bytes:
831 return self._dir + path
875 return self._dir + path
832
876
833 def _loadalllazy(self):
877 def _loadalllazy(self) -> None:
834 selfdirs = self._dirs
878 selfdirs = self._dirs
835 subpath = self._subpath
879 subpath = self._subpath
836 for d, (node, readsubtree, docopy) in self._lazydirs.items():
880 for d, (node, readsubtree, docopy) in self._lazydirs.items():
837 if docopy:
881 if docopy:
838 selfdirs[d] = readsubtree(subpath(d), node).copy()
882 selfdirs[d] = readsubtree(subpath(d), node).copy()
839 else:
883 else:
840 selfdirs[d] = readsubtree(subpath(d), node)
884 selfdirs[d] = readsubtree(subpath(d), node)
841 self._lazydirs.clear()
885 self._lazydirs.clear()
842
886
843 def _loadlazy(self, d):
887 def _loadlazy(self, d: bytes) -> None:
844 v = self._lazydirs.get(d)
888 v = self._lazydirs.get(d)
845 if v is not None:
889 if v is not None:
846 node, readsubtree, docopy = v
890 node, readsubtree, docopy = v
847 if docopy:
891 if docopy:
848 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
892 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
849 else:
893 else:
850 self._dirs[d] = readsubtree(self._subpath(d), node)
894 self._dirs[d] = readsubtree(self._subpath(d), node)
851 del self._lazydirs[d]
895 del self._lazydirs[d]
852
896
853 def _loadchildrensetlazy(self, visit):
897 def _loadchildrensetlazy(
898 self, visit: Union[Set[bytes], bytes]
899 ) -> Optional[Set[bytes]]:
854 if not visit:
900 if not visit:
855 return None
901 return None
856 if visit == b'all' or visit == b'this':
902 if visit == b'all' or visit == b'this':
857 self._loadalllazy()
903 self._loadalllazy()
858 return None
904 return None
859
905
906 visit = cast(Set[bytes], visit)
907
860 loadlazy = self._loadlazy
908 loadlazy = self._loadlazy
861 for k in visit:
909 for k in visit:
862 loadlazy(k + b'/')
910 loadlazy(k + b'/')
863 return visit
911 return visit
864
912
865 def _loaddifflazy(self, t1, t2):
913 def _loaddifflazy(self, t1: 'TreeManifest', t2: 'TreeManifest'):
866 """load items in t1 and t2 if they're needed for diffing.
914 """load items in t1 and t2 if they're needed for diffing.
867
915
868 The criteria currently is:
916 The criteria currently is:
869 - if it's not present in _lazydirs in either t1 or t2, load it in the
917 - if it's not present in _lazydirs in either t1 or t2, load it in the
870 other (it may already be loaded or it may not exist, doesn't matter)
918 other (it may already be loaded or it may not exist, doesn't matter)
871 - if it's present in _lazydirs in both, compare the nodeid; if it
919 - if it's present in _lazydirs in both, compare the nodeid; if it
872 differs, load it in both
920 differs, load it in both
873 """
921 """
874 toloadlazy = []
922 toloadlazy = []
875 for d, v1 in t1._lazydirs.items():
923 for d, v1 in t1._lazydirs.items():
876 v2 = t2._lazydirs.get(d)
924 v2 = t2._lazydirs.get(d)
877 if v2 is None or v2[0] != v1[0]:
925 if v2 is None or v2[0] != v1[0]:
878 toloadlazy.append(d)
926 toloadlazy.append(d)
879 for d, v1 in t2._lazydirs.items():
927 for d, v1 in t2._lazydirs.items():
880 if d not in t1._lazydirs:
928 if d not in t1._lazydirs:
881 toloadlazy.append(d)
929 toloadlazy.append(d)
882
930
883 for d in toloadlazy:
931 for d in toloadlazy:
884 t1._loadlazy(d)
932 t1._loadlazy(d)
885 t2._loadlazy(d)
933 t2._loadlazy(d)
886
934
887 def __len__(self):
935 def __len__(self) -> int:
888 self._load()
936 self._load()
889 size = len(self._files)
937 size = len(self._files)
890 self._loadalllazy()
938 self._loadalllazy()
891 for m in self._dirs.values():
939 for m in self._dirs.values():
892 size += m.__len__()
940 size += m.__len__()
893 return size
941 return size
894
942
895 def __nonzero__(self):
943 def __nonzero__(self) -> bool:
896 # Faster than "__len() != 0" since it avoids loading sub-manifests
944 # Faster than "__len__() != 0" since it avoids loading sub-manifests
897 return not self._isempty()
945 return not self._isempty()
898
946
899 __bool__ = __nonzero__
947 __bool__ = __nonzero__
900
948
901 def _isempty(self):
949 def _isempty(self) -> bool:
902 self._load() # for consistency; already loaded by all callers
950 self._load() # for consistency; already loaded by all callers
903 # See if we can skip loading everything.
951 # See if we can skip loading everything.
904 if self._files or (
952 if self._files or (
905 self._dirs and any(not m._isempty() for m in self._dirs.values())
953 self._dirs and any(not m._isempty() for m in self._dirs.values())
906 ):
954 ):
907 return False
955 return False
908 self._loadalllazy()
956 self._loadalllazy()
909 return not self._dirs or all(m._isempty() for m in self._dirs.values())
957 return not self._dirs or all(m._isempty() for m in self._dirs.values())
910
958
911 @encoding.strmethod
959 @encoding.strmethod
912 def __repr__(self):
960 def __repr__(self) -> bytes:
913 return (
961 return (
914 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
962 b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>'
915 % (
963 % (
916 self._dir,
964 self._dir,
917 hex(self._node),
965 hex(self._node),
918 bool(self._loadfunc is _noop),
966 bool(self._loadfunc is _noop),
919 self._dirty,
967 self._dirty,
920 id(self),
968 id(self),
921 )
969 )
922 )
970 )
923
971
924 def dir(self):
972 def dir(self) -> bytes:
925 """The directory that this tree manifest represents, including a
973 """The directory that this tree manifest represents, including a
926 trailing '/'. Empty string for the repo root directory."""
974 trailing '/'. Empty string for the repo root directory."""
927 return self._dir
975 return self._dir
928
976
929 def node(self):
977 def node(self) -> bytes:
930 """This node of this instance. nullid for unsaved instances. Should
978 """This node of this instance. nullid for unsaved instances. Should
931 be updated when the instance is read or written from a revlog.
979 be updated when the instance is read or written from a revlog.
932 """
980 """
933 assert not self._dirty
981 assert not self._dirty
934 return self._node
982 return self._node
935
983
936 def setnode(self, node):
984 def setnode(self, node: bytes) -> None:
937 self._node = node
985 self._node = node
938 self._dirty = False
986 self._dirty = False
939
987
940 def iterentries(self):
988 def iterentries(
989 self,
990 ) -> Iterator[Tuple[bytes, Union[bytes, 'TreeManifest'], bytes]]:
941 self._load()
991 self._load()
942 self._loadalllazy()
992 self._loadalllazy()
943 for p, n in sorted(
993 for p, n in sorted(
944 itertools.chain(self._dirs.items(), self._files.items())
994 itertools.chain(self._dirs.items(), self._files.items())
945 ):
995 ):
946 if p in self._files:
996 if p in self._files:
947 yield self._subpath(p), n, self._flags.get(p, b'')
997 yield self._subpath(p), n, self._flags.get(p, b'')
948 else:
998 else:
949 for x in n.iterentries():
999 for x in n.iterentries():
950 yield x
1000 yield x
951
1001
952 def items(self):
1002 def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'TreeManifest']]]:
953 self._load()
1003 self._load()
954 self._loadalllazy()
1004 self._loadalllazy()
955 for p, n in sorted(
1005 for p, n in sorted(
956 itertools.chain(self._dirs.items(), self._files.items())
1006 itertools.chain(self._dirs.items(), self._files.items())
957 ):
1007 ):
958 if p in self._files:
1008 if p in self._files:
959 yield self._subpath(p), n
1009 yield self._subpath(p), n
960 else:
1010 else:
961 for f, sn in n.items():
1011 for f, sn in n.items():
962 yield f, sn
1012 yield f, sn
963
1013
964 iteritems = items
1014 iteritems = items
965
1015
966 def iterkeys(self):
1016 def iterkeys(self) -> Iterator[bytes]:
967 self._load()
1017 self._load()
968 self._loadalllazy()
1018 self._loadalllazy()
969 for p in sorted(itertools.chain(self._dirs, self._files)):
1019 for p in sorted(itertools.chain(self._dirs, self._files)):
970 if p in self._files:
1020 if p in self._files:
971 yield self._subpath(p)
1021 yield self._subpath(p)
972 else:
1022 else:
973 for f in self._dirs[p]:
1023 for f in self._dirs[p]:
974 yield f
1024 yield f
975
1025
976 def keys(self):
1026 def keys(self) -> List[bytes]:
977 return list(self.iterkeys())
1027 return list(self.iterkeys())
978
1028
979 def __iter__(self):
1029 def __iter__(self) -> Iterator[bytes]:
980 return self.iterkeys()
1030 return self.iterkeys()
981
1031
982 def __contains__(self, f):
1032 def __contains__(self, f: bytes) -> bool:
983 if f is None:
1033 if f is None:
984 return False
1034 return False
985 self._load()
1035 self._load()
986 dir, subpath = _splittopdir(f)
1036 dir, subpath = _splittopdir(f)
987 if dir:
1037 if dir:
988 self._loadlazy(dir)
1038 self._loadlazy(dir)
989
1039
990 if dir not in self._dirs:
1040 if dir not in self._dirs:
991 return False
1041 return False
992
1042
993 return self._dirs[dir].__contains__(subpath)
1043 return self._dirs[dir].__contains__(subpath)
994 else:
1044 else:
995 return f in self._files
1045 return f in self._files
996
1046
997 def get(self, f, default=None):
1047 def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]:
998 self._load()
1048 self._load()
999 dir, subpath = _splittopdir(f)
1049 dir, subpath = _splittopdir(f)
1000 if dir:
1050 if dir:
1001 self._loadlazy(dir)
1051 self._loadlazy(dir)
1002
1052
1003 if dir not in self._dirs:
1053 if dir not in self._dirs:
1004 return default
1054 return default
1005 return self._dirs[dir].get(subpath, default)
1055 return self._dirs[dir].get(subpath, default)
1006 else:
1056 else:
1007 return self._files.get(f, default)
1057 return self._files.get(f, default)
1008
1058
1009 def __getitem__(self, f):
1059 def __getitem__(self, f: bytes) -> bytes:
1010 self._load()
1060 self._load()
1011 dir, subpath = _splittopdir(f)
1061 dir, subpath = _splittopdir(f)
1012 if dir:
1062 if dir:
1013 self._loadlazy(dir)
1063 self._loadlazy(dir)
1014
1064
1015 return self._dirs[dir].__getitem__(subpath)
1065 return self._dirs[dir].__getitem__(subpath)
1016 else:
1066 else:
1017 return self._files[f]
1067 return self._files[f]
1018
1068
1019 def flags(self, f):
1069 def flags(self, f: bytes) -> bytes:
1020 self._load()
1070 self._load()
1021 dir, subpath = _splittopdir(f)
1071 dir, subpath = _splittopdir(f)
1022 if dir:
1072 if dir:
1023 self._loadlazy(dir)
1073 self._loadlazy(dir)
1024
1074
1025 if dir not in self._dirs:
1075 if dir not in self._dirs:
1026 return b''
1076 return b''
1027 return self._dirs[dir].flags(subpath)
1077 return self._dirs[dir].flags(subpath)
1028 else:
1078 else:
1029 if f in self._lazydirs or f in self._dirs:
1079 if f in self._lazydirs or f in self._dirs:
1030 return b''
1080 return b''
1031 return self._flags.get(f, b'')
1081 return self._flags.get(f, b'')
1032
1082
1033 def find(self, f):
1083 def find(self, f: bytes) -> Tuple[bytes, bytes]:
1034 self._load()
1084 self._load()
1035 dir, subpath = _splittopdir(f)
1085 dir, subpath = _splittopdir(f)
1036 if dir:
1086 if dir:
1037 self._loadlazy(dir)
1087 self._loadlazy(dir)
1038
1088
1039 return self._dirs[dir].find(subpath)
1089 return self._dirs[dir].find(subpath)
1040 else:
1090 else:
1041 return self._files[f], self._flags.get(f, b'')
1091 return self._files[f], self._flags.get(f, b'')
1042
1092
1043 def __delitem__(self, f):
1093 def __delitem__(self, f: bytes) -> None:
1044 self._load()
1094 self._load()
1045 dir, subpath = _splittopdir(f)
1095 dir, subpath = _splittopdir(f)
1046 if dir:
1096 if dir:
1047 self._loadlazy(dir)
1097 self._loadlazy(dir)
1048
1098
1049 self._dirs[dir].__delitem__(subpath)
1099 self._dirs[dir].__delitem__(subpath)
1050 # If the directory is now empty, remove it
1100 # If the directory is now empty, remove it
1051 if self._dirs[dir]._isempty():
1101 if self._dirs[dir]._isempty():
1052 del self._dirs[dir]
1102 del self._dirs[dir]
1053 else:
1103 else:
1054 del self._files[f]
1104 del self._files[f]
1055 if f in self._flags:
1105 if f in self._flags:
1056 del self._flags[f]
1106 del self._flags[f]
1057 self._dirty = True
1107 self._dirty = True
1058
1108
1059 def set(self, f, node, flags):
1109 def set(self, f: bytes, node: bytes, flags: bytes) -> None:
1060 """Set both the node and the flags for path f."""
1110 """Set both the node and the flags for path f."""
1061 assert node is not None
1111 assert node is not None
1062 if flags not in _manifestflags:
1112 if flags not in _manifestflags:
1063 raise TypeError(b"Invalid manifest flag set.")
1113 raise TypeError(b"Invalid manifest flag set.")
1064 self._load()
1114 self._load()
1065 dir, subpath = _splittopdir(f)
1115 dir, subpath = _splittopdir(f)
1066 if dir:
1116 if dir:
1067 self._loadlazy(dir)
1117 self._loadlazy(dir)
1068 if dir not in self._dirs:
1118 if dir not in self._dirs:
1069 self._dirs[dir] = treemanifest(
1119 self._dirs[dir] = treemanifest(
1070 self.nodeconstants, self._subpath(dir)
1120 self.nodeconstants, self._subpath(dir)
1071 )
1121 )
1072 self._dirs[dir].set(subpath, node, flags)
1122 self._dirs[dir].set(subpath, node, flags)
1073 else:
1123 else:
1074 assert len(node) in (20, 32)
1124 assert len(node) in (20, 32)
1075 self._files[f] = node
1125 self._files[f] = node
1076 self._flags[f] = flags
1126 self._flags[f] = flags
1077 self._dirty = True
1127 self._dirty = True
1078
1128
1079 def __setitem__(self, f, n):
1129 def __setitem__(self, f: bytes, n: bytes) -> None:
1080 assert n is not None
1130 assert n is not None
1081 self._load()
1131 self._load()
1082 dir, subpath = _splittopdir(f)
1132 dir, subpath = _splittopdir(f)
1083 if dir:
1133 if dir:
1084 self._loadlazy(dir)
1134 self._loadlazy(dir)
1085 if dir not in self._dirs:
1135 if dir not in self._dirs:
1086 self._dirs[dir] = treemanifest(
1136 self._dirs[dir] = treemanifest(
1087 self.nodeconstants, self._subpath(dir)
1137 self.nodeconstants, self._subpath(dir)
1088 )
1138 )
1089 self._dirs[dir].__setitem__(subpath, n)
1139 self._dirs[dir].__setitem__(subpath, n)
1090 else:
1140 else:
1091 # manifest nodes are either 20 bytes or 32 bytes,
1141 # manifest nodes are either 20 bytes or 32 bytes,
1092 # depending on the hash in use. Assert this as historically
1142 # depending on the hash in use. Assert this as historically
1093 # sometimes extra bytes were added.
1143 # sometimes extra bytes were added.
1094 assert len(n) in (20, 32)
1144 assert len(n) in (20, 32)
1095 self._files[f] = n
1145 self._files[f] = n
1096 self._dirty = True
1146 self._dirty = True
1097
1147
1098 def _load(self):
1148 def _load(self) -> None:
1099 if self._loadfunc is not _noop:
1149 if self._loadfunc is not _noop:
1100 lf, self._loadfunc = self._loadfunc, _noop
1150 lf, self._loadfunc = self._loadfunc, _noop
1101 lf(self)
1151 lf(self)
1102 elif self._copyfunc is not _noop:
1152 elif self._copyfunc is not _noop:
1103 cf, self._copyfunc = self._copyfunc, _noop
1153 cf, self._copyfunc = self._copyfunc, _noop
1104 cf(self)
1154 cf(self)
1105
1155
1106 def setflag(self, f, flags):
1156 def setflag(self, f: bytes, flags: bytes) -> None:
1107 """Set the flags (symlink, executable) for path f."""
1157 """Set the flags (symlink, executable) for path f."""
1108 if flags not in _manifestflags:
1158 if flags not in _manifestflags:
1109 raise TypeError(b"Invalid manifest flag set.")
1159 raise TypeError(b"Invalid manifest flag set.")
1110 self._load()
1160 self._load()
1111 dir, subpath = _splittopdir(f)
1161 dir, subpath = _splittopdir(f)
1112 if dir:
1162 if dir:
1113 self._loadlazy(dir)
1163 self._loadlazy(dir)
1114 if dir not in self._dirs:
1164 if dir not in self._dirs:
1115 self._dirs[dir] = treemanifest(
1165 self._dirs[dir] = treemanifest(
1116 self.nodeconstants, self._subpath(dir)
1166 self.nodeconstants, self._subpath(dir)
1117 )
1167 )
1118 self._dirs[dir].setflag(subpath, flags)
1168 self._dirs[dir].setflag(subpath, flags)
1119 else:
1169 else:
1120 self._flags[f] = flags
1170 self._flags[f] = flags
1121 self._dirty = True
1171 self._dirty = True
1122
1172
1123 def copy(self):
1173 def copy(self) -> 'TreeManifest':
1124 copy = treemanifest(self.nodeconstants, self._dir)
1174 copy = treemanifest(self.nodeconstants, self._dir)
1125 copy._node = self._node
1175 copy._node = self._node
1126 copy._dirty = self._dirty
1176 copy._dirty = self._dirty
1127 if self._copyfunc is _noop:
1177 if self._copyfunc is _noop:
1128
1178
1129 def _copyfunc(s):
1179 def _copyfunc(s):
1130 self._load()
1180 self._load()
1131 s._lazydirs = {
1181 s._lazydirs = {
1132 d: (n, r, True) for d, (n, r, c) in self._lazydirs.items()
1182 d: (n, r, True) for d, (n, r, c) in self._lazydirs.items()
1133 }
1183 }
1134 sdirs = s._dirs
1184 sdirs = s._dirs
1135 for d, v in self._dirs.items():
1185 for d, v in self._dirs.items():
1136 sdirs[d] = v.copy()
1186 sdirs[d] = v.copy()
1137 s._files = dict.copy(self._files)
1187 s._files = dict.copy(self._files)
1138 s._flags = dict.copy(self._flags)
1188 s._flags = dict.copy(self._flags)
1139
1189
1140 if self._loadfunc is _noop:
1190 if self._loadfunc is _noop:
1141 _copyfunc(copy)
1191 _copyfunc(copy)
1142 else:
1192 else:
1143 copy._copyfunc = _copyfunc
1193 copy._copyfunc = _copyfunc
1144 else:
1194 else:
1145 copy._copyfunc = self._copyfunc
1195 copy._copyfunc = self._copyfunc
1146 return copy
1196 return copy
1147
1197
1148 def filesnotin(self, m2, match=None):
1198 def filesnotin(
1199 self, m2: 'TreeManifest', match: Optional[matchmod.basematcher] = None
1200 ) -> Set[bytes]:
1149 '''Set of files in this manifest that are not in the other'''
1201 '''Set of files in this manifest that are not in the other'''
1150 if match and not match.always():
1202 if match and not match.always():
1151 m1 = self._matches(match)
1203 m1 = self._matches(match)
1152 m2 = m2._matches(match)
1204 m2 = m2._matches(match)
1153 return m1.filesnotin(m2)
1205 return m1.filesnotin(m2)
1154
1206
1155 files = set()
1207 files = set()
1156
1208
1157 def _filesnotin(t1, t2):
1209 def _filesnotin(t1, t2):
1158 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1210 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1159 return
1211 return
1160 t1._load()
1212 t1._load()
1161 t2._load()
1213 t2._load()
1162 self._loaddifflazy(t1, t2)
1214 self._loaddifflazy(t1, t2)
1163 for d, m1 in t1._dirs.items():
1215 for d, m1 in t1._dirs.items():
1164 if d in t2._dirs:
1216 if d in t2._dirs:
1165 m2 = t2._dirs[d]
1217 m2 = t2._dirs[d]
1166 _filesnotin(m1, m2)
1218 _filesnotin(m1, m2)
1167 else:
1219 else:
1168 files.update(m1.iterkeys())
1220 files.update(m1.iterkeys())
1169
1221
1170 for fn in t1._files:
1222 for fn in t1._files:
1171 if fn not in t2._files:
1223 if fn not in t2._files:
1172 files.add(t1._subpath(fn))
1224 files.add(t1._subpath(fn))
1173
1225
1174 _filesnotin(self, m2)
1226 _filesnotin(self, m2)
1175 return files
1227 return files
1176
1228
1177 @propertycache
1229 @propertycache
1178 def _alldirs(self):
1230 def _alldirs(self) -> pathutil.dirs:
1179 return pathutil.dirs(self)
1231 return pathutil.dirs(self)
1180
1232
1181 def dirs(self):
1233 def dirs(self) -> pathutil.dirs:
1182 return self._alldirs
1234 return self._alldirs
1183
1235
1184 def hasdir(self, dir):
1236 def hasdir(self, dir: bytes) -> bool:
1185 self._load()
1237 self._load()
1186 topdir, subdir = _splittopdir(dir)
1238 topdir, subdir = _splittopdir(dir)
1187 if topdir:
1239 if topdir:
1188 self._loadlazy(topdir)
1240 self._loadlazy(topdir)
1189 if topdir in self._dirs:
1241 if topdir in self._dirs:
1190 return self._dirs[topdir].hasdir(subdir)
1242 return self._dirs[topdir].hasdir(subdir)
1191 return False
1243 return False
1192 dirslash = dir + b'/'
1244 dirslash = dir + b'/'
1193 return dirslash in self._dirs or dirslash in self._lazydirs
1245 return dirslash in self._dirs or dirslash in self._lazydirs
1194
1246
1195 def walk(self, match):
1247 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1196 """Generates matching file names.
1248 """Generates matching file names.
1197
1249
1198 It also reports nonexistent files by marking them bad with match.bad().
1250 It also reports nonexistent files by marking them bad with match.bad().
1199 """
1251 """
1200 if match.always():
1252 if match.always():
1201 for f in iter(self):
1253 for f in iter(self):
1202 yield f
1254 yield f
1203 return
1255 return
1204
1256
1205 fset = set(match.files())
1257 fset = set(match.files())
1206
1258
1207 for fn in self._walk(match):
1259 for fn in self._walk(match):
1208 if fn in fset:
1260 if fn in fset:
1209 # specified pattern is the exact name
1261 # specified pattern is the exact name
1210 fset.remove(fn)
1262 fset.remove(fn)
1211 yield fn
1263 yield fn
1212
1264
1213 # for dirstate.walk, files=[''] means "walk the whole tree".
1265 # for dirstate.walk, files=[''] means "walk the whole tree".
1214 # follow that here, too
1266 # follow that here, too
1215 fset.discard(b'')
1267 fset.discard(b'')
1216
1268
1217 for fn in sorted(fset):
1269 for fn in sorted(fset):
1218 if not self.hasdir(fn):
1270 if not self.hasdir(fn):
1219 match.bad(fn, None)
1271 match.bad(fn, None)
1220
1272
1221 def _walk(self, match):
1273 def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1222 '''Recursively generates matching file names for walk().'''
1274 '''Recursively generates matching file names for walk().'''
1223 visit = match.visitchildrenset(self._dir[:-1])
1275 visit = match.visitchildrenset(self._dir[:-1])
1224 if not visit:
1276 if not visit:
1225 return
1277 return
1226
1278
1227 # yield this dir's files and walk its submanifests
1279 # yield this dir's files and walk its submanifests
1228 self._load()
1280 self._load()
1229 visit = self._loadchildrensetlazy(visit)
1281 visit = self._loadchildrensetlazy(visit)
1230 for p in sorted(list(self._dirs) + list(self._files)):
1282 for p in sorted(list(self._dirs) + list(self._files)):
1231 if p in self._files:
1283 if p in self._files:
1232 fullp = self._subpath(p)
1284 fullp = self._subpath(p)
1233 if match(fullp):
1285 if match(fullp):
1234 yield fullp
1286 yield fullp
1235 else:
1287 else:
1236 if not visit or p[:-1] in visit:
1288 if not visit or p[:-1] in visit:
1237 for f in self._dirs[p]._walk(match):
1289 for f in self._dirs[p]._walk(match):
1238 yield f
1290 yield f
1239
1291
1240 def _matches(self, match):
1292 def _matches(self, match: matchmod.basematcher) -> 'TreeManifest':
1241 """recursively generate a new manifest filtered by the match argument."""
1293 """recursively generate a new manifest filtered by the match argument."""
1242 if match.always():
1294 if match.always():
1243 return self.copy()
1295 return self.copy()
1244 return self._matches_inner(match)
1296 return self._matches_inner(match)
1245
1297
1246 def _matches_inner(self, match):
1298 def _matches_inner(self, match: matchmod.basematcher) -> 'TreeManifest':
1247 if match.always():
1299 if match.always():
1248 return self.copy()
1300 return self.copy()
1249
1301
1250 visit = match.visitchildrenset(self._dir[:-1])
1302 visit = match.visitchildrenset(self._dir[:-1])
1251 if visit == b'all':
1303 if visit == b'all':
1252 return self.copy()
1304 return self.copy()
1253 ret = treemanifest(self.nodeconstants, self._dir)
1305 ret = treemanifest(self.nodeconstants, self._dir)
1254 if not visit:
1306 if not visit:
1255 return ret
1307 return ret
1256
1308
1257 self._load()
1309 self._load()
1258 for fn in self._files:
1310 for fn in self._files:
1259 # While visitchildrenset *usually* lists only subdirs, this is
1311 # While visitchildrenset *usually* lists only subdirs, this is
1260 # actually up to the matcher and may have some files in the set().
1312 # actually up to the matcher and may have some files in the set().
1261 # If visit == 'this', we should obviously look at the files in this
1313 # If visit == 'this', we should obviously look at the files in this
1262 # directory; if visit is a set, and fn is in it, we should inspect
1314 # directory; if visit is a set, and fn is in it, we should inspect
1263 # fn (but no need to inspect things not in the set).
1315 # fn (but no need to inspect things not in the set).
1264 if visit != b'this' and fn not in visit:
1316 if visit != b'this' and fn not in visit:
1265 continue
1317 continue
1266 fullp = self._subpath(fn)
1318 fullp = self._subpath(fn)
1267 # visitchildrenset isn't perfect, we still need to call the regular
1319 # visitchildrenset isn't perfect, we still need to call the regular
1268 # matcher code to further filter results.
1320 # matcher code to further filter results.
1269 if not match(fullp):
1321 if not match(fullp):
1270 continue
1322 continue
1271 ret._files[fn] = self._files[fn]
1323 ret._files[fn] = self._files[fn]
1272 if fn in self._flags:
1324 if fn in self._flags:
1273 ret._flags[fn] = self._flags[fn]
1325 ret._flags[fn] = self._flags[fn]
1274
1326
1275 visit = self._loadchildrensetlazy(visit)
1327 visit = self._loadchildrensetlazy(visit)
1276 for dir, subm in self._dirs.items():
1328 for dir, subm in self._dirs.items():
1277 if visit and dir[:-1] not in visit:
1329 if visit and dir[:-1] not in visit:
1278 continue
1330 continue
1279 m = subm._matches_inner(match)
1331 m = subm._matches_inner(match)
1280 if not m._isempty():
1332 if not m._isempty():
1281 ret._dirs[dir] = m
1333 ret._dirs[dir] = m
1282
1334
1283 if not ret._isempty():
1335 if not ret._isempty():
1284 ret._dirty = True
1336 ret._dirty = True
1285 return ret
1337 return ret
1286
1338
1287 def fastdelta(self, base, changes):
1339 def fastdelta(
1340 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
1341 ) -> ByteString:
1288 raise FastdeltaUnavailable()
1342 raise FastdeltaUnavailable()
1289
1343
1290 def diff(self, m2, match=None, clean=False):
1344 def diff(
1345 self,
1346 m2: 'TreeManifest',
1347 match: Optional[matchmod.basematcher] = None,
1348 clean: bool = False,
1349 ) -> Dict[
1350 bytes,
1351 Optional[
1352 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
1353 ],
1354 ]:
1291 """Finds changes between the current manifest and m2.
1355 """Finds changes between the current manifest and m2.
1292
1356
1293 Args:
1357 Args:
1294 m2: the manifest to which this manifest should be compared.
1358 m2: the manifest to which this manifest should be compared.
1295 clean: if true, include files unchanged between these manifests
1359 clean: if true, include files unchanged between these manifests
1296 with a None value in the returned dictionary.
1360 with a None value in the returned dictionary.
1297
1361
1298 The result is returned as a dict with filename as key and
1362 The result is returned as a dict with filename as key and
1299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1363 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1300 nodeid in the current/other manifest and fl1/fl2 is the flag
1364 nodeid in the current/other manifest and fl1/fl2 is the flag
1301 in the current/other manifest. Where the file does not exist,
1365 in the current/other manifest. Where the file does not exist,
1302 the nodeid will be None and the flags will be the empty
1366 the nodeid will be None and the flags will be the empty
1303 string.
1367 string.
1304 """
1368 """
1305 if match and not match.always():
1369 if match and not match.always():
1306 m1 = self._matches(match)
1370 m1 = self._matches(match)
1307 m2 = m2._matches(match)
1371 m2 = m2._matches(match)
1308 return m1.diff(m2, clean=clean)
1372 return m1.diff(m2, clean=clean)
1309 result = {}
1373 result = {}
1310 emptytree = treemanifest(self.nodeconstants)
1374 emptytree = treemanifest(self.nodeconstants)
1311
1375
1312 def _iterativediff(t1, t2, stack):
1376 def _iterativediff(t1, t2, stack):
1313 """compares two tree manifests and append new tree-manifests which
1377 """compares two tree manifests and append new tree-manifests which
1314 needs to be compared to stack"""
1378 needs to be compared to stack"""
1315 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1379 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1316 return
1380 return
1317 t1._load()
1381 t1._load()
1318 t2._load()
1382 t2._load()
1319 self._loaddifflazy(t1, t2)
1383 self._loaddifflazy(t1, t2)
1320
1384
1321 for d, m1 in t1._dirs.items():
1385 for d, m1 in t1._dirs.items():
1322 m2 = t2._dirs.get(d, emptytree)
1386 m2 = t2._dirs.get(d, emptytree)
1323 stack.append((m1, m2))
1387 stack.append((m1, m2))
1324
1388
1325 for d, m2 in t2._dirs.items():
1389 for d, m2 in t2._dirs.items():
1326 if d not in t1._dirs:
1390 if d not in t1._dirs:
1327 stack.append((emptytree, m2))
1391 stack.append((emptytree, m2))
1328
1392
1329 for fn, n1 in t1._files.items():
1393 for fn, n1 in t1._files.items():
1330 fl1 = t1._flags.get(fn, b'')
1394 fl1 = t1._flags.get(fn, b'')
1331 n2 = t2._files.get(fn, None)
1395 n2 = t2._files.get(fn, None)
1332 fl2 = t2._flags.get(fn, b'')
1396 fl2 = t2._flags.get(fn, b'')
1333 if n1 != n2 or fl1 != fl2:
1397 if n1 != n2 or fl1 != fl2:
1334 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1398 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1335 elif clean:
1399 elif clean:
1336 result[t1._subpath(fn)] = None
1400 result[t1._subpath(fn)] = None
1337
1401
1338 for fn, n2 in t2._files.items():
1402 for fn, n2 in t2._files.items():
1339 if fn not in t1._files:
1403 if fn not in t1._files:
1340 fl2 = t2._flags.get(fn, b'')
1404 fl2 = t2._flags.get(fn, b'')
1341 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1405 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1342
1406
1343 stackls = []
1407 stackls = []
1344 _iterativediff(self, m2, stackls)
1408 _iterativediff(self, m2, stackls)
1345 while stackls:
1409 while stackls:
1346 t1, t2 = stackls.pop()
1410 t1, t2 = stackls.pop()
1347 # stackls is populated in the function call
1411 # stackls is populated in the function call
1348 _iterativediff(t1, t2, stackls)
1412 _iterativediff(t1, t2, stackls)
1349 return result
1413 return result
1350
1414
1351 def unmodifiedsince(self, m2):
1415 def unmodifiedsince(self, m2: 'TreeManifest') -> bool:
1352 return not self._dirty and not m2._dirty and self._node == m2._node
1416 return not self._dirty and not m2._dirty and self._node == m2._node
1353
1417
1354 def parse(self, text, readsubtree):
1418 def parse(
1419 self,
1420 text: bytes,
1421 readsubtree: Callable[[bytes, bytes], 'TreeManifest'],
1422 ) -> None:
1355 selflazy = self._lazydirs
1423 selflazy = self._lazydirs
1356 for f, n, fl in _parse(self._nodelen, text):
1424 for f, n, fl in _parse(self._nodelen, text):
1357 if fl == b't':
1425 if fl == b't':
1358 f = f + b'/'
1426 f = f + b'/'
1359 # False below means "doesn't need to be copied" and can use the
1427 # False below means "doesn't need to be copied" and can use the
1360 # cached value from readsubtree directly.
1428 # cached value from readsubtree directly.
1361 selflazy[f] = (n, readsubtree, False)
1429 selflazy[f] = (n, readsubtree, False)
1362 elif b'/' in f:
1430 elif b'/' in f:
1363 # This is a flat manifest, so use __setitem__ and setflag rather
1431 # This is a flat manifest, so use __setitem__ and setflag rather
1364 # than assigning directly to _files and _flags, so we can
1432 # than assigning directly to _files and _flags, so we can
1365 # assign a path in a subdirectory, and to mark dirty (compared
1433 # assign a path in a subdirectory, and to mark dirty (compared
1366 # to nullid).
1434 # to nullid).
1367 self[f] = n
1435 self[f] = n
1368 if fl:
1436 if fl:
1369 self.setflag(f, fl)
1437 self.setflag(f, fl)
1370 else:
1438 else:
1371 # Assigning to _files and _flags avoids marking as dirty,
1439 # Assigning to _files and _flags avoids marking as dirty,
1372 # and should be a little faster.
1440 # and should be a little faster.
1373 self._files[f] = n
1441 self._files[f] = n
1374 if fl:
1442 if fl:
1375 self._flags[f] = fl
1443 self._flags[f] = fl
1376
1444
1377 def text(self):
1445 def text(self) -> ByteString:
1378 """Get the full data of this manifest as a bytestring."""
1446 """Get the full data of this manifest as a bytestring."""
1379 self._load()
1447 self._load()
1380 return _text(self.iterentries())
1448 return _text(self.iterentries())
1381
1449
1382 def dirtext(self):
1450 def dirtext(self) -> ByteString:
1383 """Get the full data of this directory as a bytestring. Make sure that
1451 """Get the full data of this directory as a bytestring. Make sure that
1384 any submanifests have been written first, so their nodeids are correct.
1452 any submanifests have been written first, so their nodeids are correct.
1385 """
1453 """
1386 self._load()
1454 self._load()
1387 flags = self.flags
1455 flags = self.flags
1388 lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()]
1456 lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()]
1389 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1457 dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
1390 files = [(f, self._files[f], flags(f)) for f in self._files]
1458 files = [(f, self._files[f], flags(f)) for f in self._files]
1391 return _text(sorted(dirs + files + lazydirs))
1459 return _text(sorted(dirs + files + lazydirs))
1392
1460
1393 def read(self, gettext, readsubtree):
1461 def read(
1462 self,
1463 gettext: Callable[[], ByteString],
1464 readsubtree: Callable[[bytes, bytes], 'TreeManifest'],
1465 ) -> None:
1394 def _load_for_read(s):
1466 def _load_for_read(s):
1395 s.parse(gettext(), readsubtree)
1467 s.parse(gettext(), readsubtree)
1396 s._dirty = False
1468 s._dirty = False
1397
1469
1398 self._loadfunc = _load_for_read
1470 self._loadfunc = _load_for_read
1399
1471
1400 def writesubtrees(self, m1, m2, writesubtree, match):
1472 def writesubtrees(
1473 self,
1474 m1: 'TreeManifest',
1475 m2: 'TreeManifest',
1476 writesubtree: Callable[
1477 [
1478 Callable[['TreeManifest'], None],
1479 bytes,
1480 bytes,
1481 matchmod.basematcher,
1482 ],
1483 None,
1484 ],
1485 match: matchmod.basematcher,
1486 ) -> None:
1401 self._load() # for consistency; should never have any effect here
1487 self._load() # for consistency; should never have any effect here
1402 m1._load()
1488 m1._load()
1403 m2._load()
1489 m2._load()
1404 emptytree = treemanifest(self.nodeconstants)
1490 emptytree = treemanifest(self.nodeconstants)
1405
1491
1406 def getnode(m, d):
1492 def getnode(m, d):
1407 ld = m._lazydirs.get(d)
1493 ld = m._lazydirs.get(d)
1408 if ld:
1494 if ld:
1409 return ld[0]
1495 return ld[0]
1410 tree = m._dirs.get(d, emptytree)
1496 tree = m._dirs.get(d, emptytree)
1411 assert tree is not None # helps pytype
1497 assert tree is not None # helps pytype
1412 return tree._node
1498 return tree._node
1413
1499
1414 # let's skip investigating things that `match` says we do not need.
1500 # let's skip investigating things that `match` says we do not need.
1415 visit = match.visitchildrenset(self._dir[:-1])
1501 visit = match.visitchildrenset(self._dir[:-1])
1416 visit = self._loadchildrensetlazy(visit)
1502 visit = self._loadchildrensetlazy(visit)
1417 if visit == b'this' or visit == b'all':
1503 if visit == b'this' or visit == b'all':
1418 visit = None
1504 visit = None
1419 for d, subm in self._dirs.items():
1505 for d, subm in self._dirs.items():
1420 if visit and d[:-1] not in visit:
1506 if visit and d[:-1] not in visit:
1421 continue
1507 continue
1422 subp1 = getnode(m1, d)
1508 subp1 = getnode(m1, d)
1423 subp2 = getnode(m2, d)
1509 subp2 = getnode(m2, d)
1424 if subp1 == self.nodeconstants.nullid:
1510 if subp1 == self.nodeconstants.nullid:
1425 subp1, subp2 = subp2, subp1
1511 subp1, subp2 = subp2, subp1
1426 writesubtree(subm, subp1, subp2, match)
1512 writesubtree(subm, subp1, subp2, match)
1427
1513
1428 def walksubtrees(self, matcher=None):
1514 def walksubtrees(
1515 self, matcher: Optional[matchmod.basematcher] = None
1516 ) -> Iterator['TreeManifest']:
1429 """Returns an iterator of the subtrees of this manifest, including this
1517 """Returns an iterator of the subtrees of this manifest, including this
1430 manifest itself.
1518 manifest itself.
1431
1519
1432 If `matcher` is provided, it only returns subtrees that match.
1520 If `matcher` is provided, it only returns subtrees that match.
1433 """
1521 """
1434 if matcher and not matcher.visitdir(self._dir[:-1]):
1522 if matcher and not matcher.visitdir(self._dir[:-1]):
1435 return
1523 return
1436 if not matcher or matcher(self._dir[:-1]):
1524 if not matcher or matcher(self._dir[:-1]):
1437 yield self
1525 yield self
1438
1526
1439 self._load()
1527 self._load()
1440 # OPT: use visitchildrenset to avoid loading everything.
1528 # OPT: use visitchildrenset to avoid loading everything.
1441 self._loadalllazy()
1529 self._loadalllazy()
1442 for d, subm in self._dirs.items():
1530 for d, subm in self._dirs.items():
1443 for subtree in subm.walksubtrees(matcher=matcher):
1531 for subtree in subm.walksubtrees(matcher=matcher):
1444 yield subtree
1532 yield subtree
1445
1533
1446
1534
1447 treemanifest = interfaceutil.implementer(repository.imanifestdict)(TreeManifest)
1535 treemanifest = interfaceutil.implementer(repository.imanifestdict)(TreeManifest)
1448
1536
1449
1537
1450 class manifestfulltextcache(util.lrucachedict):
1538 class manifestfulltextcache(util.lrucachedict):
1451 """File-backed LRU cache for the manifest cache
1539 """File-backed LRU cache for the manifest cache
1452
1540
1453 File consists of entries, up to EOF:
1541 File consists of entries, up to EOF:
1454
1542
1455 - 20 bytes node, 4 bytes length, <length> manifest data
1543 - 20 bytes node, 4 bytes length, <length> manifest data
1456
1544
1457 These are written in reverse cache order (oldest to newest).
1545 These are written in reverse cache order (oldest to newest).
1458
1546
1459 """
1547 """
1460
1548
1461 _file = b'manifestfulltextcache'
1549 _file = b'manifestfulltextcache'
1462
1550
1463 def __init__(self, max):
1551 def __init__(self, max):
1464 super(manifestfulltextcache, self).__init__(max)
1552 super(manifestfulltextcache, self).__init__(max)
1465 self._dirty = False
1553 self._dirty = False
1466 self._read = False
1554 self._read = False
1467 self._opener = None
1555 self._opener = None
1468
1556
1469 def read(self):
1557 def read(self):
1470 if self._read or self._opener is None:
1558 if self._read or self._opener is None:
1471 return
1559 return
1472
1560
1473 try:
1561 try:
1474 with self._opener(self._file) as fp:
1562 with self._opener(self._file) as fp:
1475 set = super(manifestfulltextcache, self).__setitem__
1563 set = super(manifestfulltextcache, self).__setitem__
1476 # ignore trailing data, this is a cache, corruption is skipped
1564 # ignore trailing data, this is a cache, corruption is skipped
1477 while True:
1565 while True:
1478 # TODO do we need to do work here for sha1 portability?
1566 # TODO do we need to do work here for sha1 portability?
1479 node = fp.read(20)
1567 node = fp.read(20)
1480 if len(node) < 20:
1568 if len(node) < 20:
1481 break
1569 break
1482 try:
1570 try:
1483 size = struct.unpack(b'>L', fp.read(4))[0]
1571 size = struct.unpack(b'>L', fp.read(4))[0]
1484 except struct.error:
1572 except struct.error:
1485 break
1573 break
1486 value = bytearray(fp.read(size))
1574 value = bytearray(fp.read(size))
1487 if len(value) != size:
1575 if len(value) != size:
1488 break
1576 break
1489 set(node, value)
1577 set(node, value)
1490 except IOError:
1578 except IOError:
1491 # the file is allowed to be missing
1579 # the file is allowed to be missing
1492 pass
1580 pass
1493
1581
1494 self._read = True
1582 self._read = True
1495 self._dirty = False
1583 self._dirty = False
1496
1584
1497 def write(self):
1585 def write(self):
1498 if not self._dirty or self._opener is None:
1586 if not self._dirty or self._opener is None:
1499 return
1587 return
1500 # rotate backwards to the first used node
1588 # rotate backwards to the first used node
1501 try:
1589 try:
1502 with self._opener(
1590 with self._opener(
1503 self._file, b'w', atomictemp=True, checkambig=True
1591 self._file, b'w', atomictemp=True, checkambig=True
1504 ) as fp:
1592 ) as fp:
1505 node = self._head.prev
1593 node = self._head.prev
1506 while True:
1594 while True:
1507 if node.key in self._cache:
1595 if node.key in self._cache:
1508 fp.write(node.key)
1596 fp.write(node.key)
1509 fp.write(struct.pack(b'>L', len(node.value)))
1597 fp.write(struct.pack(b'>L', len(node.value)))
1510 fp.write(node.value)
1598 fp.write(node.value)
1511 if node is self._head:
1599 if node is self._head:
1512 break
1600 break
1513 node = node.prev
1601 node = node.prev
1514 except IOError:
1602 except IOError:
1515 # We could not write the cache (eg: permission error)
1603 # We could not write the cache (eg: permission error)
1516 # the content can be missing.
1604 # the content can be missing.
1517 #
1605 #
1518 # We could try harder and see if we could recreate a wcache
1606 # We could try harder and see if we could recreate a wcache
1519 # directory were we coudl write too.
1607 # directory were we coudl write too.
1520 #
1608 #
1521 # XXX the error pass silently, having some way to issue an error
1609 # XXX the error pass silently, having some way to issue an error
1522 # log `ui.log` would be nice.
1610 # log `ui.log` would be nice.
1523 pass
1611 pass
1524
1612
1525 def __len__(self):
1613 def __len__(self):
1526 if not self._read:
1614 if not self._read:
1527 self.read()
1615 self.read()
1528 return super(manifestfulltextcache, self).__len__()
1616 return super(manifestfulltextcache, self).__len__()
1529
1617
1530 def __contains__(self, k):
1618 def __contains__(self, k):
1531 if not self._read:
1619 if not self._read:
1532 self.read()
1620 self.read()
1533 return super(manifestfulltextcache, self).__contains__(k)
1621 return super(manifestfulltextcache, self).__contains__(k)
1534
1622
1535 def __iter__(self):
1623 def __iter__(self):
1536 if not self._read:
1624 if not self._read:
1537 self.read()
1625 self.read()
1538 return super(manifestfulltextcache, self).__iter__()
1626 return super(manifestfulltextcache, self).__iter__()
1539
1627
1540 def __getitem__(self, k):
1628 def __getitem__(self, k):
1541 if not self._read:
1629 if not self._read:
1542 self.read()
1630 self.read()
1543 # the cache lru order can change on read
1631 # the cache lru order can change on read
1544 setdirty = self._cache.get(k) is not self._head
1632 setdirty = self._cache.get(k) is not self._head
1545 value = super(manifestfulltextcache, self).__getitem__(k)
1633 value = super(manifestfulltextcache, self).__getitem__(k)
1546 if setdirty:
1634 if setdirty:
1547 self._dirty = True
1635 self._dirty = True
1548 return value
1636 return value
1549
1637
1550 def __setitem__(self, k, v):
1638 def __setitem__(self, k, v):
1551 if not self._read:
1639 if not self._read:
1552 self.read()
1640 self.read()
1553 super(manifestfulltextcache, self).__setitem__(k, v)
1641 super(manifestfulltextcache, self).__setitem__(k, v)
1554 self._dirty = True
1642 self._dirty = True
1555
1643
1556 def __delitem__(self, k):
1644 def __delitem__(self, k):
1557 if not self._read:
1645 if not self._read:
1558 self.read()
1646 self.read()
1559 super(manifestfulltextcache, self).__delitem__(k)
1647 super(manifestfulltextcache, self).__delitem__(k)
1560 self._dirty = True
1648 self._dirty = True
1561
1649
1562 def get(self, k, default=None):
1650 def get(self, k, default=None):
1563 if not self._read:
1651 if not self._read:
1564 self.read()
1652 self.read()
1565 return super(manifestfulltextcache, self).get(k, default=default)
1653 return super(manifestfulltextcache, self).get(k, default=default)
1566
1654
1567 def clear(self, clear_persisted_data=False):
1655 def clear(self, clear_persisted_data=False):
1568 super(manifestfulltextcache, self).clear()
1656 super(manifestfulltextcache, self).clear()
1569 if clear_persisted_data:
1657 if clear_persisted_data:
1570 self._dirty = True
1658 self._dirty = True
1571 self.write()
1659 self.write()
1572 self._read = False
1660 self._read = False
1573
1661
1574
1662
1575 # and upper bound of what we expect from compression
1663 # and upper bound of what we expect from compression
1576 # (real live value seems to be "3")
1664 # (real live value seems to be "3")
1577 MAXCOMPRESSION = 3
1665 MAXCOMPRESSION = 3
1578
1666
1579
1667
1580 class FastdeltaUnavailable(Exception):
1668 class FastdeltaUnavailable(Exception):
1581 """Exception raised when fastdelta isn't usable on a manifest."""
1669 """Exception raised when fastdelta isn't usable on a manifest."""
1582
1670
1583
1671
1584 class ManifestRevlog:
1672 class ManifestRevlog:
1585 """A revlog that stores manifest texts. This is responsible for caching the
1673 """A revlog that stores manifest texts. This is responsible for caching the
1586 full-text manifest contents.
1674 full-text manifest contents.
1587 """
1675 """
1588
1676
1589 def __init__(
1677 def __init__(
1590 self,
1678 self,
1591 nodeconstants,
1679 nodeconstants,
1592 opener,
1680 opener,
1593 tree=b'',
1681 tree=b'',
1594 dirlogcache=None,
1682 dirlogcache=None,
1595 treemanifest=False,
1683 treemanifest=False,
1596 ):
1684 ):
1597 """Constructs a new manifest revlog
1685 """Constructs a new manifest revlog
1598
1686
1599 `indexfile` - used by extensions to have two manifests at once, like
1687 `indexfile` - used by extensions to have two manifests at once, like
1600 when transitioning between flatmanifeset and treemanifests.
1688 when transitioning between flatmanifeset and treemanifests.
1601
1689
1602 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1690 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1603 options can also be used to make this a tree manifest revlog. The opener
1691 options can also be used to make this a tree manifest revlog. The opener
1604 option takes precedence, so if it is set to True, we ignore whatever
1692 option takes precedence, so if it is set to True, we ignore whatever
1605 value is passed in to the constructor.
1693 value is passed in to the constructor.
1606 """
1694 """
1607 self.nodeconstants = nodeconstants
1695 self.nodeconstants = nodeconstants
1608 # During normal operations, we expect to deal with not more than four
1696 # During normal operations, we expect to deal with not more than four
1609 # revs at a time (such as during commit --amend). When rebasing large
1697 # revs at a time (such as during commit --amend). When rebasing large
1610 # stacks of commits, the number can go up, hence the config knob below.
1698 # stacks of commits, the number can go up, hence the config knob below.
1611 cachesize = 4
1699 cachesize = 4
1612 optiontreemanifest = False
1700 optiontreemanifest = False
1613 persistentnodemap = False
1701 persistentnodemap = False
1614 opts = getattr(opener, 'options', None)
1702 opts = getattr(opener, 'options', None)
1615 if opts is not None:
1703 if opts is not None:
1616 cachesize = opts.get(b'manifestcachesize', cachesize)
1704 cachesize = opts.get(b'manifestcachesize', cachesize)
1617 optiontreemanifest = opts.get(b'treemanifest', False)
1705 optiontreemanifest = opts.get(b'treemanifest', False)
1618 persistentnodemap = opts.get(b'persistent-nodemap', False)
1706 persistentnodemap = opts.get(b'persistent-nodemap', False)
1619
1707
1620 self._treeondisk = optiontreemanifest or treemanifest
1708 self._treeondisk = optiontreemanifest or treemanifest
1621
1709
1622 self._fulltextcache = manifestfulltextcache(cachesize)
1710 self._fulltextcache = manifestfulltextcache(cachesize)
1623
1711
1624 if tree:
1712 if tree:
1625 assert self._treeondisk, (tree, b'opts is %r' % opts)
1713 assert self._treeondisk, (tree, b'opts is %r' % opts)
1626
1714
1627 radix = b'00manifest'
1715 radix = b'00manifest'
1628 if tree:
1716 if tree:
1629 radix = b"meta/" + tree + radix
1717 radix = b"meta/" + tree + radix
1630
1718
1631 self.tree = tree
1719 self.tree = tree
1632
1720
1633 # The dirlogcache is kept on the root manifest log
1721 # The dirlogcache is kept on the root manifest log
1634 if tree:
1722 if tree:
1635 self._dirlogcache = dirlogcache
1723 self._dirlogcache = dirlogcache
1636 else:
1724 else:
1637 self._dirlogcache = {b'': self}
1725 self._dirlogcache = {b'': self}
1638
1726
1639 self._revlog = revlog.revlog(
1727 self._revlog = revlog.revlog(
1640 opener,
1728 opener,
1641 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1729 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1642 radix=radix,
1730 radix=radix,
1643 # only root indexfile is cached
1731 # only root indexfile is cached
1644 checkambig=not bool(tree),
1732 checkambig=not bool(tree),
1645 mmaplargeindex=True,
1733 mmaplargeindex=True,
1646 upperboundcomp=MAXCOMPRESSION,
1734 upperboundcomp=MAXCOMPRESSION,
1647 persistentnodemap=persistentnodemap,
1735 persistentnodemap=persistentnodemap,
1648 )
1736 )
1649
1737
1650 self.index = self._revlog.index
1738 self.index = self._revlog.index
1651
1739
1652 def get_revlog(self):
1740 def get_revlog(self):
1653 """return an actual revlog instance if any
1741 """return an actual revlog instance if any
1654
1742
1655 This exist because a lot of code leverage the fact the underlying
1743 This exist because a lot of code leverage the fact the underlying
1656 storage is a revlog for optimization, so giving simple way to access
1744 storage is a revlog for optimization, so giving simple way to access
1657 the revlog instance helps such code.
1745 the revlog instance helps such code.
1658 """
1746 """
1659 return self._revlog
1747 return self._revlog
1660
1748
1661 def _setupmanifestcachehooks(self, repo):
1749 def _setupmanifestcachehooks(self, repo):
1662 """Persist the manifestfulltextcache on lock release"""
1750 """Persist the manifestfulltextcache on lock release"""
1663 if not hasattr(repo, '_wlockref'):
1751 if not hasattr(repo, '_wlockref'):
1664 return
1752 return
1665
1753
1666 self._fulltextcache._opener = repo.wcachevfs
1754 self._fulltextcache._opener = repo.wcachevfs
1667 if repo._currentlock(repo._wlockref) is None:
1755 if repo._currentlock(repo._wlockref) is None:
1668 return
1756 return
1669
1757
1670 reporef = weakref.ref(repo)
1758 reporef = weakref.ref(repo)
1671 manifestrevlogref = weakref.ref(self)
1759 manifestrevlogref = weakref.ref(self)
1672
1760
1673 def persistmanifestcache(success):
1761 def persistmanifestcache(success):
1674 # Repo is in an unknown state, do not persist.
1762 # Repo is in an unknown state, do not persist.
1675 if not success:
1763 if not success:
1676 return
1764 return
1677
1765
1678 repo = reporef()
1766 repo = reporef()
1679 self = manifestrevlogref()
1767 self = manifestrevlogref()
1680 if repo is None or self is None:
1768 if repo is None or self is None:
1681 return
1769 return
1682 if repo.manifestlog.getstorage(b'') is not self:
1770 if repo.manifestlog.getstorage(b'') is not self:
1683 # there's a different manifest in play now, abort
1771 # there's a different manifest in play now, abort
1684 return
1772 return
1685 self._fulltextcache.write()
1773 self._fulltextcache.write()
1686
1774
1687 repo._afterlock(persistmanifestcache)
1775 repo._afterlock(persistmanifestcache)
1688
1776
1689 @property
1777 @property
1690 def fulltextcache(self):
1778 def fulltextcache(self):
1691 return self._fulltextcache
1779 return self._fulltextcache
1692
1780
1693 def clearcaches(self, clear_persisted_data=False):
1781 def clearcaches(self, clear_persisted_data=False):
1694 self._revlog.clearcaches()
1782 self._revlog.clearcaches()
1695 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1783 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1696 self._dirlogcache = {self.tree: self}
1784 self._dirlogcache = {self.tree: self}
1697
1785
1698 def dirlog(self, d):
1786 def dirlog(self, d):
1699 if d:
1787 if d:
1700 assert self._treeondisk
1788 assert self._treeondisk
1701 if d not in self._dirlogcache:
1789 if d not in self._dirlogcache:
1702 mfrevlog = manifestrevlog(
1790 mfrevlog = manifestrevlog(
1703 self.nodeconstants,
1791 self.nodeconstants,
1704 self.opener,
1792 self.opener,
1705 d,
1793 d,
1706 self._dirlogcache,
1794 self._dirlogcache,
1707 treemanifest=self._treeondisk,
1795 treemanifest=self._treeondisk,
1708 )
1796 )
1709 self._dirlogcache[d] = mfrevlog
1797 self._dirlogcache[d] = mfrevlog
1710 return self._dirlogcache[d]
1798 return self._dirlogcache[d]
1711
1799
1712 def add(
1800 def add(
1713 self,
1801 self,
1714 m,
1802 m,
1715 transaction,
1803 transaction,
1716 link,
1804 link,
1717 p1,
1805 p1,
1718 p2,
1806 p2,
1719 added: Iterable[Iterable],
1807 added: Iterable[bytes],
1720 removed: Iterable[Iterable],
1808 removed: Iterable[bytes],
1721 readtree=None,
1809 readtree=None,
1722 match=None,
1810 match=None,
1723 ):
1811 ):
1724 """add some manifest entry in to the manifest log
1812 """add some manifest entry in to the manifest log
1725
1813
1726 input:
1814 input:
1727
1815
1728 m: the manifest dict we want to store
1816 m: the manifest dict we want to store
1729 transaction: the open transaction
1817 transaction: the open transaction
1730 p1: manifest-node of p1
1818 p1: manifest-node of p1
1731 p2: manifest-node of p2
1819 p2: manifest-node of p2
1732 added: file added/changed compared to parent
1820 added: file added/changed compared to parent
1733 removed: file removed compared to parent
1821 removed: file removed compared to parent
1734
1822
1735 tree manifest input:
1823 tree manifest input:
1736
1824
1737 readtree: a function to read a subtree
1825 readtree: a function to read a subtree
1738 match: a filematcher for the subpart of the tree manifest
1826 match: a filematcher for the subpart of the tree manifest
1739 """
1827 """
1740 try:
1828 try:
1741 if p1 not in self.fulltextcache:
1829 if p1 not in self.fulltextcache:
1742 raise FastdeltaUnavailable()
1830 raise FastdeltaUnavailable()
1743 # If our first parent is in the manifest cache, we can
1831 # If our first parent is in the manifest cache, we can
1744 # compute a delta here using properties we know about the
1832 # compute a delta here using properties we know about the
1745 # manifest up-front, which may save time later for the
1833 # manifest up-front, which may save time later for the
1746 # revlog layer.
1834 # revlog layer.
1747
1835
1748 _checkforbidden(added)
1836 _checkforbidden(added)
1749 # combine the changed lists into one sorted iterator
1837 # combine the changed lists into one sorted iterator
1750 work = heapq.merge(
1838 work = heapq.merge(
1751 [(x, False) for x in sorted(added)],
1839 [(x, False) for x in sorted(added)],
1752 [(x, True) for x in sorted(removed)],
1840 [(x, True) for x in sorted(removed)],
1753 )
1841 )
1754
1842
1755 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1843 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1756 cachedelta = self._revlog.rev(p1), deltatext
1844 cachedelta = self._revlog.rev(p1), deltatext
1757 text = util.buffer(arraytext)
1845 text = util.buffer(arraytext)
1758 rev = self._revlog.addrevision(
1846 rev = self._revlog.addrevision(
1759 text, transaction, link, p1, p2, cachedelta
1847 text, transaction, link, p1, p2, cachedelta
1760 )
1848 )
1761 n = self._revlog.node(rev)
1849 n = self._revlog.node(rev)
1762 except FastdeltaUnavailable:
1850 except FastdeltaUnavailable:
1763 # The first parent manifest isn't already loaded or the
1851 # The first parent manifest isn't already loaded or the
1764 # manifest implementation doesn't support fastdelta, so
1852 # manifest implementation doesn't support fastdelta, so
1765 # we'll just encode a fulltext of the manifest and pass
1853 # we'll just encode a fulltext of the manifest and pass
1766 # that through to the revlog layer, and let it handle the
1854 # that through to the revlog layer, and let it handle the
1767 # delta process.
1855 # delta process.
1768 if self._treeondisk:
1856 if self._treeondisk:
1769 assert readtree, b"readtree must be set for treemanifest writes"
1857 assert readtree, b"readtree must be set for treemanifest writes"
1770 assert match, b"match must be specified for treemanifest writes"
1858 assert match, b"match must be specified for treemanifest writes"
1771 m1 = readtree(self.tree, p1)
1859 m1 = readtree(self.tree, p1)
1772 m2 = readtree(self.tree, p2)
1860 m2 = readtree(self.tree, p2)
1773 n = self._addtree(
1861 n = self._addtree(
1774 m, transaction, link, m1, m2, readtree, match=match
1862 m, transaction, link, m1, m2, readtree, match=match
1775 )
1863 )
1776 arraytext = None
1864 arraytext = None
1777 else:
1865 else:
1778 text = m.text()
1866 text = m.text()
1779 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1867 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1780 n = self._revlog.node(rev)
1868 n = self._revlog.node(rev)
1781 arraytext = bytearray(text)
1869 arraytext = bytearray(text)
1782
1870
1783 if arraytext is not None:
1871 if arraytext is not None:
1784 self.fulltextcache[n] = arraytext
1872 self.fulltextcache[n] = arraytext
1785
1873
1786 return n
1874 return n
1787
1875
1788 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1876 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1789 # If the manifest is unchanged compared to one parent,
1877 # If the manifest is unchanged compared to one parent,
1790 # don't write a new revision
1878 # don't write a new revision
1791 if self.tree != b'' and (
1879 if self.tree != b'' and (
1792 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1880 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1793 ):
1881 ):
1794 return m.node()
1882 return m.node()
1795
1883
1796 def writesubtree(subm, subp1, subp2, match):
1884 def writesubtree(subm, subp1, subp2, match):
1797 sublog = self.dirlog(subm.dir())
1885 sublog = self.dirlog(subm.dir())
1798 sublog.add(
1886 sublog.add(
1799 subm,
1887 subm,
1800 transaction,
1888 transaction,
1801 link,
1889 link,
1802 subp1,
1890 subp1,
1803 subp2,
1891 subp2,
1804 None,
1892 None,
1805 None,
1893 None,
1806 readtree=readtree,
1894 readtree=readtree,
1807 match=match,
1895 match=match,
1808 )
1896 )
1809
1897
1810 m.writesubtrees(m1, m2, writesubtree, match)
1898 m.writesubtrees(m1, m2, writesubtree, match)
1811 text = m.dirtext()
1899 text = m.dirtext()
1812 n = None
1900 n = None
1813 if self.tree != b'':
1901 if self.tree != b'':
1814 # Double-check whether contents are unchanged to one parent
1902 # Double-check whether contents are unchanged to one parent
1815 if text == m1.dirtext():
1903 if text == m1.dirtext():
1816 n = m1.node()
1904 n = m1.node()
1817 elif text == m2.dirtext():
1905 elif text == m2.dirtext():
1818 n = m2.node()
1906 n = m2.node()
1819
1907
1820 if not n:
1908 if not n:
1821 rev = self._revlog.addrevision(
1909 rev = self._revlog.addrevision(
1822 text, transaction, link, m1.node(), m2.node()
1910 text, transaction, link, m1.node(), m2.node()
1823 )
1911 )
1824 n = self._revlog.node(rev)
1912 n = self._revlog.node(rev)
1825
1913
1826 # Save nodeid so parent manifest can calculate its nodeid
1914 # Save nodeid so parent manifest can calculate its nodeid
1827 m.setnode(n)
1915 m.setnode(n)
1828 return n
1916 return n
1829
1917
1830 def __len__(self):
1918 def __len__(self):
1831 return len(self._revlog)
1919 return len(self._revlog)
1832
1920
1833 def __iter__(self):
1921 def __iter__(self):
1834 return self._revlog.__iter__()
1922 return self._revlog.__iter__()
1835
1923
1836 def rev(self, node):
1924 def rev(self, node):
1837 return self._revlog.rev(node)
1925 return self._revlog.rev(node)
1838
1926
1839 def node(self, rev):
1927 def node(self, rev):
1840 return self._revlog.node(rev)
1928 return self._revlog.node(rev)
1841
1929
1842 def lookup(self, value):
1930 def lookup(self, value):
1843 return self._revlog.lookup(value)
1931 return self._revlog.lookup(value)
1844
1932
1845 def parentrevs(self, rev):
1933 def parentrevs(self, rev):
1846 return self._revlog.parentrevs(rev)
1934 return self._revlog.parentrevs(rev)
1847
1935
1848 def parents(self, node):
1936 def parents(self, node):
1849 return self._revlog.parents(node)
1937 return self._revlog.parents(node)
1850
1938
1851 def linkrev(self, rev):
1939 def linkrev(self, rev):
1852 return self._revlog.linkrev(rev)
1940 return self._revlog.linkrev(rev)
1853
1941
1854 def checksize(self):
1942 def checksize(self):
1855 return self._revlog.checksize()
1943 return self._revlog.checksize()
1856
1944
1857 def revision(self, node):
1945 def revision(self, node):
1858 return self._revlog.revision(node)
1946 return self._revlog.revision(node)
1859
1947
1860 def rawdata(self, node):
1948 def rawdata(self, node):
1861 return self._revlog.rawdata(node)
1949 return self._revlog.rawdata(node)
1862
1950
1863 def revdiff(self, rev1, rev2):
1951 def revdiff(self, rev1, rev2):
1864 return self._revlog.revdiff(rev1, rev2)
1952 return self._revlog.revdiff(rev1, rev2)
1865
1953
1866 def cmp(self, node, text):
1954 def cmp(self, node, text):
1867 return self._revlog.cmp(node, text)
1955 return self._revlog.cmp(node, text)
1868
1956
1869 def deltaparent(self, rev):
1957 def deltaparent(self, rev):
1870 return self._revlog.deltaparent(rev)
1958 return self._revlog.deltaparent(rev)
1871
1959
1872 def emitrevisions(
1960 def emitrevisions(
1873 self,
1961 self,
1874 nodes,
1962 nodes,
1875 nodesorder=None,
1963 nodesorder=None,
1876 revisiondata=False,
1964 revisiondata=False,
1877 assumehaveparentrevisions=False,
1965 assumehaveparentrevisions=False,
1878 deltamode=repository.CG_DELTAMODE_STD,
1966 deltamode=repository.CG_DELTAMODE_STD,
1879 sidedata_helpers=None,
1967 sidedata_helpers=None,
1880 debug_info=None,
1968 debug_info=None,
1881 ):
1969 ):
1882 return self._revlog.emitrevisions(
1970 return self._revlog.emitrevisions(
1883 nodes,
1971 nodes,
1884 nodesorder=nodesorder,
1972 nodesorder=nodesorder,
1885 revisiondata=revisiondata,
1973 revisiondata=revisiondata,
1886 assumehaveparentrevisions=assumehaveparentrevisions,
1974 assumehaveparentrevisions=assumehaveparentrevisions,
1887 deltamode=deltamode,
1975 deltamode=deltamode,
1888 sidedata_helpers=sidedata_helpers,
1976 sidedata_helpers=sidedata_helpers,
1889 debug_info=debug_info,
1977 debug_info=debug_info,
1890 )
1978 )
1891
1979
1892 def addgroup(
1980 def addgroup(
1893 self,
1981 self,
1894 deltas,
1982 deltas,
1895 linkmapper,
1983 linkmapper,
1896 transaction,
1984 transaction,
1897 alwayscache=False,
1985 alwayscache=False,
1898 addrevisioncb=None,
1986 addrevisioncb=None,
1899 duplicaterevisioncb=None,
1987 duplicaterevisioncb=None,
1900 debug_info=None,
1988 debug_info=None,
1901 delta_base_reuse_policy=None,
1989 delta_base_reuse_policy=None,
1902 ):
1990 ):
1903 return self._revlog.addgroup(
1991 return self._revlog.addgroup(
1904 deltas,
1992 deltas,
1905 linkmapper,
1993 linkmapper,
1906 transaction,
1994 transaction,
1907 alwayscache=alwayscache,
1995 alwayscache=alwayscache,
1908 addrevisioncb=addrevisioncb,
1996 addrevisioncb=addrevisioncb,
1909 duplicaterevisioncb=duplicaterevisioncb,
1997 duplicaterevisioncb=duplicaterevisioncb,
1910 debug_info=debug_info,
1998 debug_info=debug_info,
1911 delta_base_reuse_policy=delta_base_reuse_policy,
1999 delta_base_reuse_policy=delta_base_reuse_policy,
1912 )
2000 )
1913
2001
1914 def rawsize(self, rev):
2002 def rawsize(self, rev):
1915 return self._revlog.rawsize(rev)
2003 return self._revlog.rawsize(rev)
1916
2004
1917 def getstrippoint(self, minlink):
2005 def getstrippoint(self, minlink):
1918 return self._revlog.getstrippoint(minlink)
2006 return self._revlog.getstrippoint(minlink)
1919
2007
1920 def strip(self, minlink, transaction):
2008 def strip(self, minlink, transaction):
1921 return self._revlog.strip(minlink, transaction)
2009 return self._revlog.strip(minlink, transaction)
1922
2010
1923 def files(self):
2011 def files(self):
1924 return self._revlog.files()
2012 return self._revlog.files()
1925
2013
1926 def clone(self, tr, destrevlog, **kwargs):
2014 def clone(self, tr, destrevlog, **kwargs):
1927 if not isinstance(destrevlog, manifestrevlog):
2015 if not isinstance(destrevlog, manifestrevlog):
1928 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
2016 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
1929
2017
1930 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
2018 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
1931
2019
1932 def storageinfo(
2020 def storageinfo(
1933 self,
2021 self,
1934 exclusivefiles=False,
2022 exclusivefiles=False,
1935 sharedfiles=False,
2023 sharedfiles=False,
1936 revisionscount=False,
2024 revisionscount=False,
1937 trackedsize=False,
2025 trackedsize=False,
1938 storedsize=False,
2026 storedsize=False,
1939 ):
2027 ):
1940 return self._revlog.storageinfo(
2028 return self._revlog.storageinfo(
1941 exclusivefiles=exclusivefiles,
2029 exclusivefiles=exclusivefiles,
1942 sharedfiles=sharedfiles,
2030 sharedfiles=sharedfiles,
1943 revisionscount=revisionscount,
2031 revisionscount=revisionscount,
1944 trackedsize=trackedsize,
2032 trackedsize=trackedsize,
1945 storedsize=storedsize,
2033 storedsize=storedsize,
1946 )
2034 )
1947
2035
1948 @property
2036 @property
1949 def opener(self):
2037 def opener(self):
1950 return self._revlog.opener
2038 return self._revlog.opener
1951
2039
1952 @opener.setter
2040 @opener.setter
1953 def opener(self, value):
2041 def opener(self, value):
1954 self._revlog.opener = value
2042 self._revlog.opener = value
1955
2043
1956
2044
1957 manifestrevlog = interfaceutil.implementer(repository.imanifeststorage)(
2045 manifestrevlog = interfaceutil.implementer(repository.imanifeststorage)(
1958 ManifestRevlog
2046 ManifestRevlog
1959 )
2047 )
1960
2048
1961
2049
2050 AnyManifestCtx = Union['ManifestCtx', 'TreeManifestCtx']
2051 AnyManifestDict = Union[ManifestDict, TreeManifest]
2052
2053
1962 @interfaceutil.implementer(repository.imanifestlog)
2054 @interfaceutil.implementer(repository.imanifestlog)
1963 class manifestlog:
2055 class manifestlog:
1964 """A collection class representing the collection of manifest snapshots
2056 """A collection class representing the collection of manifest snapshots
1965 referenced by commits in the repository.
2057 referenced by commits in the repository.
1966
2058
1967 In this situation, 'manifest' refers to the abstract concept of a snapshot
2059 In this situation, 'manifest' refers to the abstract concept of a snapshot
1968 of the list of files in the given commit. Consumers of the output of this
2060 of the list of files in the given commit. Consumers of the output of this
1969 class do not care about the implementation details of the actual manifests
2061 class do not care about the implementation details of the actual manifests
1970 they receive (i.e. tree or flat or lazily loaded, etc)."""
2062 they receive (i.e. tree or flat or lazily loaded, etc)."""
1971
2063
1972 def __init__(self, opener, repo, rootstore, narrowmatch):
2064 def __init__(self, opener, repo, rootstore, narrowmatch):
1973 self.nodeconstants = repo.nodeconstants
2065 self.nodeconstants = repo.nodeconstants
1974 usetreemanifest = False
2066 usetreemanifest = False
1975 cachesize = 4
2067 cachesize = 4
1976
2068
1977 opts = getattr(opener, 'options', None)
2069 opts = getattr(opener, 'options', None)
1978 if opts is not None:
2070 if opts is not None:
1979 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
2071 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
1980 cachesize = opts.get(b'manifestcachesize', cachesize)
2072 cachesize = opts.get(b'manifestcachesize', cachesize)
1981
2073
1982 self._treemanifests = usetreemanifest
2074 self._treemanifests = usetreemanifest
1983
2075
1984 self._rootstore = rootstore
2076 self._rootstore = rootstore
1985 self._rootstore._setupmanifestcachehooks(repo)
2077 self._rootstore._setupmanifestcachehooks(repo)
1986 self._narrowmatch = narrowmatch
2078 self._narrowmatch = narrowmatch
1987
2079
1988 # A cache of the manifestctx or treemanifestctx for each directory
2080 # A cache of the manifestctx or treemanifestctx for each directory
1989 self._dirmancache = {}
2081 self._dirmancache = {}
1990 self._dirmancache[b''] = util.lrucachedict(cachesize)
2082 self._dirmancache[b''] = util.lrucachedict(cachesize)
1991
2083
1992 self._cachesize = cachesize
2084 self._cachesize = cachesize
1993
2085
1994 def __getitem__(self, node):
2086 def __getitem__(self, node):
1995 """Retrieves the manifest instance for the given node. Throws a
2087 """Retrieves the manifest instance for the given node. Throws a
1996 LookupError if not found.
2088 LookupError if not found.
1997 """
2089 """
1998 return self.get(b'', node)
2090 return self.get(b'', node)
1999
2091
2000 def get(self, tree, node, verify=True):
2092 def get(
2093 self, tree: bytes, node: bytes, verify: bool = True
2094 ) -> AnyManifestCtx:
2001 """Retrieves the manifest instance for the given node. Throws a
2095 """Retrieves the manifest instance for the given node. Throws a
2002 LookupError if not found.
2096 LookupError if not found.
2003
2097
2004 `verify` - if True an exception will be thrown if the node is not in
2098 `verify` - if True an exception will be thrown if the node is not in
2005 the revlog
2099 the revlog
2006 """
2100 """
2007 if node in self._dirmancache.get(tree, ()):
2101 if node in self._dirmancache.get(tree, ()):
2008 return self._dirmancache[tree][node]
2102 return self._dirmancache[tree][node]
2009
2103
2010 if not self._narrowmatch.always():
2104 if not self._narrowmatch.always():
2011 if not self._narrowmatch.visitdir(tree[:-1]):
2105 if not self._narrowmatch.visitdir(tree[:-1]):
2012 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2106 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2013 if tree:
2107 if tree:
2014 if self._rootstore._treeondisk:
2108 if self._rootstore._treeondisk:
2015 if verify:
2109 if verify:
2016 # Side-effect is LookupError is raised if node doesn't
2110 # Side-effect is LookupError is raised if node doesn't
2017 # exist.
2111 # exist.
2018 self.getstorage(tree).rev(node)
2112 self.getstorage(tree).rev(node)
2019
2113
2020 m = treemanifestctx(self, tree, node)
2114 m = treemanifestctx(self, tree, node)
2021 else:
2115 else:
2022 raise error.Abort(
2116 raise error.Abort(
2023 _(
2117 _(
2024 b"cannot ask for manifest directory '%s' in a flat "
2118 b"cannot ask for manifest directory '%s' in a flat "
2025 b"manifest"
2119 b"manifest"
2026 )
2120 )
2027 % tree
2121 % tree
2028 )
2122 )
2029 else:
2123 else:
2030 if verify:
2124 if verify:
2031 # Side-effect is LookupError is raised if node doesn't exist.
2125 # Side-effect is LookupError is raised if node doesn't exist.
2032 self._rootstore.rev(node)
2126 self._rootstore.rev(node)
2033
2127
2034 if self._treemanifests:
2128 if self._treemanifests:
2035 m = treemanifestctx(self, b'', node)
2129 m = treemanifestctx(self, b'', node)
2036 else:
2130 else:
2037 m = manifestctx(self, node)
2131 m = manifestctx(self, node)
2038
2132
2039 if node != self.nodeconstants.nullid:
2133 if node != self.nodeconstants.nullid:
2040 mancache = self._dirmancache.get(tree)
2134 mancache = self._dirmancache.get(tree)
2041 if not mancache:
2135 if not mancache:
2042 mancache = util.lrucachedict(self._cachesize)
2136 mancache = util.lrucachedict(self._cachesize)
2043 self._dirmancache[tree] = mancache
2137 self._dirmancache[tree] = mancache
2044 mancache[node] = m
2138 mancache[node] = m
2045 return m
2139 return m
2046
2140
2047 def getstorage(self, tree):
2141 def getstorage(self, tree):
2048 return self._rootstore.dirlog(tree)
2142 return self._rootstore.dirlog(tree)
2049
2143
2050 def clearcaches(self, clear_persisted_data=False):
2144 def clearcaches(self, clear_persisted_data: bool = False) -> None:
2051 self._dirmancache.clear()
2145 self._dirmancache.clear()
2052 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2146 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2053
2147
2054 def rev(self, node):
2148 def rev(self, node) -> int:
2055 return self._rootstore.rev(node)
2149 return self._rootstore.rev(node)
2056
2150
2057 def update_caches(self, transaction):
2151 def update_caches(self, transaction) -> None:
2058 return self._rootstore._revlog.update_caches(transaction=transaction)
2152 return self._rootstore._revlog.update_caches(transaction=transaction)
2059
2153
2060
2154
2061 class MemManifestCtx:
2155 class MemManifestCtx:
2062 def __init__(self, manifestlog):
2156 def __init__(self, manifestlog):
2063 self._manifestlog = manifestlog
2157 self._manifestlog = manifestlog
2064 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2158 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2065
2159
2066 def _storage(self):
2160 def _storage(self) -> ManifestRevlog:
2067 return self._manifestlog.getstorage(b'')
2161 return self._manifestlog.getstorage(b'')
2068
2162
2069 def copy(self):
2163 def copy(self) -> 'MemManifestCtx':
2070 memmf = memmanifestctx(self._manifestlog)
2164 memmf = memmanifestctx(self._manifestlog)
2071 memmf._manifestdict = self.read().copy()
2165 memmf._manifestdict = self.read().copy()
2072 return memmf
2166 return memmf
2073
2167
2074 def read(self):
2168 def read(self) -> 'ManifestDict':
2075 return self._manifestdict
2169 return self._manifestdict
2076
2170
2077 def write(self, transaction, link, p1, p2, added, removed, match=None):
2171 def write(self, transaction, link, p1, p2, added, removed, match=None):
2078 return self._storage().add(
2172 return self._storage().add(
2079 self._manifestdict,
2173 self._manifestdict,
2080 transaction,
2174 transaction,
2081 link,
2175 link,
2082 p1,
2176 p1,
2083 p2,
2177 p2,
2084 added,
2178 added,
2085 removed,
2179 removed,
2086 match=match,
2180 match=match,
2087 )
2181 )
2088
2182
2089
2183
2090 memmanifestctx = interfaceutil.implementer(
2184 memmanifestctx = interfaceutil.implementer(
2091 repository.imanifestrevisionwritable
2185 repository.imanifestrevisionwritable
2092 )(MemManifestCtx)
2186 )(MemManifestCtx)
2093
2187
2094
2188
2095 class ManifestCtx:
2189 class ManifestCtx:
2096 """A class representing a single revision of a manifest, including its
2190 """A class representing a single revision of a manifest, including its
2097 contents, its parent revs, and its linkrev.
2191 contents, its parent revs, and its linkrev.
2098 """
2192 """
2099
2193
2100 def __init__(self, manifestlog, node):
2194 def __init__(self, manifestlog, node):
2101 self._manifestlog = manifestlog
2195 self._manifestlog = manifestlog
2102 self._data = None
2196 self._data = None
2103
2197
2104 self._node = node
2198 self._node = node
2105
2199
2106 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2200 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2107 # but let's add it later when something needs it and we can load it
2201 # but let's add it later when something needs it and we can load it
2108 # lazily.
2202 # lazily.
2109 # self.p1, self.p2 = store.parents(node)
2203 # self.p1, self.p2 = store.parents(node)
2110 # rev = store.rev(node)
2204 # rev = store.rev(node)
2111 # self.linkrev = store.linkrev(rev)
2205 # self.linkrev = store.linkrev(rev)
2112
2206
2113 def _storage(self):
2207 def _storage(self) -> 'ManifestRevlog':
2114 return self._manifestlog.getstorage(b'')
2208 return self._manifestlog.getstorage(b'')
2115
2209
2116 def node(self):
2210 def node(self) -> bytes:
2117 return self._node
2211 return self._node
2118
2212
2119 def copy(self):
2213 def copy(self) -> MemManifestCtx:
2120 memmf = memmanifestctx(self._manifestlog)
2214 memmf = memmanifestctx(self._manifestlog)
2121 memmf._manifestdict = self.read().copy()
2215 memmf._manifestdict = self.read().copy()
2122 return memmf
2216 return memmf
2123
2217
2124 @propertycache
2218 @propertycache
2125 def parents(self):
2219 def parents(self) -> Tuple[bytes, bytes]:
2126 return self._storage().parents(self._node)
2220 return self._storage().parents(self._node)
2127
2221
2128 def read(self):
2222 def read(self) -> 'ManifestDict':
2129 if self._data is None:
2223 if self._data is None:
2130 nc = self._manifestlog.nodeconstants
2224 nc = self._manifestlog.nodeconstants
2131 if self._node == nc.nullid:
2225 if self._node == nc.nullid:
2132 self._data = manifestdict(nc.nodelen)
2226 self._data = manifestdict(nc.nodelen)
2133 else:
2227 else:
2134 store = self._storage()
2228 store = self._storage()
2135 if self._node in store.fulltextcache:
2229 if self._node in store.fulltextcache:
2136 text = pycompat.bytestr(store.fulltextcache[self._node])
2230 text = pycompat.bytestr(store.fulltextcache[self._node])
2137 else:
2231 else:
2138 text = store.revision(self._node)
2232 text = store.revision(self._node)
2139 arraytext = bytearray(text)
2233 arraytext = bytearray(text)
2140 store.fulltextcache[self._node] = arraytext
2234 store.fulltextcache[self._node] = arraytext
2141 self._data = manifestdict(nc.nodelen, text)
2235 self._data = manifestdict(nc.nodelen, text)
2142 return self._data
2236 return self._data
2143
2237
2144 def readfast(self, shallow=False):
2238 def readfast(self, shallow: bool = False) -> 'ManifestDict':
2145 """Calls either readdelta or read, based on which would be less work.
2239 """Calls either readdelta or read, based on which would be less work.
2146 readdelta is called if the delta is against the p1, and therefore can be
2240 readdelta is called if the delta is against the p1, and therefore can be
2147 read quickly.
2241 read quickly.
2148
2242
2149 If `shallow` is True, nothing changes since this is a flat manifest.
2243 If `shallow` is True, nothing changes since this is a flat manifest.
2150 """
2244 """
2151 store = self._storage()
2245 store = self._storage()
2152 r = store.rev(self._node)
2246 r = store.rev(self._node)
2153 deltaparent = store.deltaparent(r)
2247 deltaparent = store.deltaparent(r)
2154 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2248 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2155 return self.readdelta()
2249 return self.readdelta()
2156 return self.read()
2250 return self.read()
2157
2251
2158 def readdelta(self, shallow=False):
2252 def readdelta(self, shallow: bool = False) -> 'ManifestDict':
2159 """Returns a manifest containing just the entries that are present
2253 """Returns a manifest containing just the entries that are present
2160 in this manifest, but not in its p1 manifest. This is efficient to read
2254 in this manifest, but not in its p1 manifest. This is efficient to read
2161 if the revlog delta is already p1.
2255 if the revlog delta is already p1.
2162
2256
2163 Changing the value of `shallow` has no effect on flat manifests.
2257 Changing the value of `shallow` has no effect on flat manifests.
2164 """
2258 """
2165 store = self._storage()
2259 store = self._storage()
2166 r = store.rev(self._node)
2260 r = store.rev(self._node)
2167 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2261 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2168 return manifestdict(store.nodeconstants.nodelen, d)
2262 return manifestdict(store.nodeconstants.nodelen, d)
2169
2263
2170 def find(self, key):
2264 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2171 return self.read().find(key)
2265 return self.read().find(key)
2172
2266
2173
2267
2174 manifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2268 manifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2175 ManifestCtx
2269 ManifestCtx
2176 )
2270 )
2177
2271
2178
2272
2179 class MemTreeManifestCtx:
2273 class MemTreeManifestCtx:
2180 def __init__(self, manifestlog, dir=b''):
2274 def __init__(self, manifestlog, dir=b''):
2181 self._manifestlog = manifestlog
2275 self._manifestlog = manifestlog
2182 self._dir = dir
2276 self._dir = dir
2183 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2277 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2184
2278
2185 def _storage(self):
2279 def _storage(self) -> ManifestRevlog:
2186 return self._manifestlog.getstorage(b'')
2280 return self._manifestlog.getstorage(b'')
2187
2281
2188 def copy(self):
2282 def copy(self) -> 'MemTreeManifestCtx':
2189 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2283 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2190 memmf._treemanifest = self._treemanifest.copy()
2284 memmf._treemanifest = self._treemanifest.copy()
2191 return memmf
2285 return memmf
2192
2286
2193 def read(self):
2287 def read(self) -> 'TreeManifest':
2194 return self._treemanifest
2288 return self._treemanifest
2195
2289
2196 def write(self, transaction, link, p1, p2, added, removed, match=None):
2290 def write(self, transaction, link, p1, p2, added, removed, match=None):
2197 def readtree(dir, node):
2291 def readtree(dir, node):
2198 return self._manifestlog.get(dir, node).read()
2292 return self._manifestlog.get(dir, node).read()
2199
2293
2200 return self._storage().add(
2294 return self._storage().add(
2201 self._treemanifest,
2295 self._treemanifest,
2202 transaction,
2296 transaction,
2203 link,
2297 link,
2204 p1,
2298 p1,
2205 p2,
2299 p2,
2206 added,
2300 added,
2207 removed,
2301 removed,
2208 readtree=readtree,
2302 readtree=readtree,
2209 match=match,
2303 match=match,
2210 )
2304 )
2211
2305
2212
2306
2213 memtreemanifestctx = interfaceutil.implementer(
2307 memtreemanifestctx = interfaceutil.implementer(
2214 repository.imanifestrevisionwritable
2308 repository.imanifestrevisionwritable
2215 )(MemTreeManifestCtx)
2309 )(MemTreeManifestCtx)
2216
2310
2217
2311
2218 class TreeManifestCtx:
2312 class TreeManifestCtx:
2219 def __init__(self, manifestlog, dir, node):
2313 def __init__(self, manifestlog, dir, node):
2220 self._manifestlog = manifestlog
2314 self._manifestlog = manifestlog
2221 self._dir = dir
2315 self._dir = dir
2222 self._data = None
2316 self._data = None
2223
2317
2224 self._node = node
2318 self._node = node
2225
2319
2226 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2320 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2227 # we can instantiate treemanifestctx objects for directories we don't
2321 # we can instantiate treemanifestctx objects for directories we don't
2228 # have on disk.
2322 # have on disk.
2229 # self.p1, self.p2 = store.parents(node)
2323 # self.p1, self.p2 = store.parents(node)
2230 # rev = store.rev(node)
2324 # rev = store.rev(node)
2231 # self.linkrev = store.linkrev(rev)
2325 # self.linkrev = store.linkrev(rev)
2232
2326
2233 def _storage(self):
2327 def _storage(self) -> ManifestRevlog:
2234 narrowmatch = self._manifestlog._narrowmatch
2328 narrowmatch = self._manifestlog._narrowmatch
2235 if not narrowmatch.always():
2329 if not narrowmatch.always():
2236 if not narrowmatch.visitdir(self._dir[:-1]):
2330 if not narrowmatch.visitdir(self._dir[:-1]):
2237 return excludedmanifestrevlog(
2331 return excludedmanifestrevlog(
2238 self._manifestlog.nodeconstants, self._dir
2332 self._manifestlog.nodeconstants, self._dir
2239 )
2333 )
2240 return self._manifestlog.getstorage(self._dir)
2334 return self._manifestlog.getstorage(self._dir)
2241
2335
2242 def read(self):
2336 def read(self) -> 'TreeManifest':
2243 if self._data is None:
2337 if self._data is None:
2244 store = self._storage()
2338 store = self._storage()
2245 if self._node == self._manifestlog.nodeconstants.nullid:
2339 if self._node == self._manifestlog.nodeconstants.nullid:
2246 self._data = treemanifest(self._manifestlog.nodeconstants)
2340 self._data = treemanifest(self._manifestlog.nodeconstants)
2247 # TODO accessing non-public API
2341 # TODO accessing non-public API
2248 elif store._treeondisk:
2342 elif store._treeondisk:
2249 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2343 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2250
2344
2251 def gettext():
2345 def gettext():
2252 return store.revision(self._node)
2346 return store.revision(self._node)
2253
2347
2254 def readsubtree(dir, subm):
2348 def readsubtree(dir, subm):
2255 # Set verify to False since we need to be able to create
2349 # Set verify to False since we need to be able to create
2256 # subtrees for trees that don't exist on disk.
2350 # subtrees for trees that don't exist on disk.
2257 return self._manifestlog.get(dir, subm, verify=False).read()
2351 return self._manifestlog.get(dir, subm, verify=False).read()
2258
2352
2259 m.read(gettext, readsubtree)
2353 m.read(gettext, readsubtree)
2260 m.setnode(self._node)
2354 m.setnode(self._node)
2261 self._data = m
2355 self._data = m
2262 else:
2356 else:
2263 if self._node in store.fulltextcache:
2357 if self._node in store.fulltextcache:
2264 text = pycompat.bytestr(store.fulltextcache[self._node])
2358 text = pycompat.bytestr(store.fulltextcache[self._node])
2265 else:
2359 else:
2266 text = store.revision(self._node)
2360 text = store.revision(self._node)
2267 arraytext = bytearray(text)
2361 arraytext = bytearray(text)
2268 store.fulltextcache[self._node] = arraytext
2362 store.fulltextcache[self._node] = arraytext
2269 self._data = treemanifest(
2363 self._data = treemanifest(
2270 self._manifestlog.nodeconstants, dir=self._dir, text=text
2364 self._manifestlog.nodeconstants, dir=self._dir, text=text
2271 )
2365 )
2272
2366
2273 return self._data
2367 return self._data
2274
2368
2275 def node(self):
2369 def node(self) -> bytes:
2276 return self._node
2370 return self._node
2277
2371
2278 def copy(self):
2372 def copy(self) -> 'MemTreeManifestCtx':
2279 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2373 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2280 memmf._treemanifest = self.read().copy()
2374 memmf._treemanifest = self.read().copy()
2281 return memmf
2375 return memmf
2282
2376
2283 @propertycache
2377 @propertycache
2284 def parents(self):
2378 def parents(self) -> Tuple[bytes, bytes]:
2285 return self._storage().parents(self._node)
2379 return self._storage().parents(self._node)
2286
2380
2287 def readdelta(self, shallow=False):
2381 def readdelta(self, shallow: bool = False) -> AnyManifestDict:
2288 """Returns a manifest containing just the entries that are present
2382 """Returns a manifest containing just the entries that are present
2289 in this manifest, but not in its p1 manifest. This is efficient to read
2383 in this manifest, but not in its p1 manifest. This is efficient to read
2290 if the revlog delta is already p1.
2384 if the revlog delta is already p1.
2291
2385
2292 If `shallow` is True, this will read the delta for this directory,
2386 If `shallow` is True, this will read the delta for this directory,
2293 without recursively reading subdirectory manifests. Instead, any
2387 without recursively reading subdirectory manifests. Instead, any
2294 subdirectory entry will be reported as it appears in the manifest, i.e.
2388 subdirectory entry will be reported as it appears in the manifest, i.e.
2295 the subdirectory will be reported among files and distinguished only by
2389 the subdirectory will be reported among files and distinguished only by
2296 its 't' flag.
2390 its 't' flag.
2297 """
2391 """
2298 store = self._storage()
2392 store = self._storage()
2299 if shallow:
2393 if shallow:
2300 r = store.rev(self._node)
2394 r = store.rev(self._node)
2301 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2395 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2302 return manifestdict(store.nodeconstants.nodelen, d)
2396 return manifestdict(store.nodeconstants.nodelen, d)
2303 else:
2397 else:
2304 # Need to perform a slow delta
2398 # Need to perform a slow delta
2305 r0 = store.deltaparent(store.rev(self._node))
2399 r0 = store.deltaparent(store.rev(self._node))
2306 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2400 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2307 m1 = self.read()
2401 m1 = self.read()
2308 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2402 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2309 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2403 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2310 if n1:
2404 if n1:
2311 md[f] = n1
2405 md[f] = n1
2312 if fl1:
2406 if fl1:
2313 md.setflag(f, fl1)
2407 md.setflag(f, fl1)
2314 return md
2408 return md
2315
2409
2316 def readfast(self, shallow=False):
2410 def readfast(self, shallow=False) -> AnyManifestDict:
2317 """Calls either readdelta or read, based on which would be less work.
2411 """Calls either readdelta or read, based on which would be less work.
2318 readdelta is called if the delta is against the p1, and therefore can be
2412 readdelta is called if the delta is against the p1, and therefore can be
2319 read quickly.
2413 read quickly.
2320
2414
2321 If `shallow` is True, it only returns the entries from this manifest,
2415 If `shallow` is True, it only returns the entries from this manifest,
2322 and not any submanifests.
2416 and not any submanifests.
2323 """
2417 """
2324 store = self._storage()
2418 store = self._storage()
2325 r = store.rev(self._node)
2419 r = store.rev(self._node)
2326 deltaparent = store.deltaparent(r)
2420 deltaparent = store.deltaparent(r)
2327 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2421 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2328 return self.readdelta(shallow=shallow)
2422 return self.readdelta(shallow=shallow)
2329
2423
2330 if shallow:
2424 if shallow:
2331 return manifestdict(
2425 return manifestdict(
2332 store.nodeconstants.nodelen, store.revision(self._node)
2426 store.nodeconstants.nodelen, store.revision(self._node)
2333 )
2427 )
2334 else:
2428 else:
2335 return self.read()
2429 return self.read()
2336
2430
2337 def find(self, key):
2431 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2338 return self.read().find(key)
2432 return self.read().find(key)
2339
2433
2340
2434
2341 treemanifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2435 treemanifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2342 TreeManifestCtx
2436 TreeManifestCtx
2343 )
2437 )
2344
2438
2345
2439
2346 class excludeddir(treemanifest):
2440 class excludeddir(treemanifest):
2347 """Stand-in for a directory that is excluded from the repository.
2441 """Stand-in for a directory that is excluded from the repository.
2348
2442
2349 With narrowing active on a repository that uses treemanifests,
2443 With narrowing active on a repository that uses treemanifests,
2350 some of the directory revlogs will be excluded from the resulting
2444 some of the directory revlogs will be excluded from the resulting
2351 clone. This is a huge storage win for clients, but means we need
2445 clone. This is a huge storage win for clients, but means we need
2352 some sort of pseudo-manifest to surface to internals so we can
2446 some sort of pseudo-manifest to surface to internals so we can
2353 detect a merge conflict outside the narrowspec. That's what this
2447 detect a merge conflict outside the narrowspec. That's what this
2354 class is: it stands in for a directory whose node is known, but
2448 class is: it stands in for a directory whose node is known, but
2355 whose contents are unknown.
2449 whose contents are unknown.
2356 """
2450 """
2357
2451
2358 def __init__(self, nodeconstants, dir, node):
2452 def __init__(self, nodeconstants, dir, node):
2359 super(excludeddir, self).__init__(nodeconstants, dir)
2453 super(excludeddir, self).__init__(nodeconstants, dir)
2360 self._node = node
2454 self._node = node
2361 # Add an empty file, which will be included by iterators and such,
2455 # Add an empty file, which will be included by iterators and such,
2362 # appearing as the directory itself (i.e. something like "dir/")
2456 # appearing as the directory itself (i.e. something like "dir/")
2363 self._files[b''] = node
2457 self._files[b''] = node
2364 self._flags[b''] = b't'
2458 self._flags[b''] = b't'
2365
2459
2366 # Manifests outside the narrowspec should never be modified, so avoid
2460 # Manifests outside the narrowspec should never be modified, so avoid
2367 # copying. This makes a noticeable difference when there are very many
2461 # copying. This makes a noticeable difference when there are very many
2368 # directories outside the narrowspec. Also, it makes sense for the copy to
2462 # directories outside the narrowspec. Also, it makes sense for the copy to
2369 # be of the same type as the original, which would not happen with the
2463 # be of the same type as the original, which would not happen with the
2370 # super type's copy().
2464 # super type's copy().
2371 def copy(self):
2465 def copy(self):
2372 return self
2466 return self
2373
2467
2374
2468
2375 class excludeddirmanifestctx(treemanifestctx):
2469 class excludeddirmanifestctx(treemanifestctx):
2376 """context wrapper for excludeddir - see that docstring for rationale"""
2470 """context wrapper for excludeddir - see that docstring for rationale"""
2377
2471
2378 def __init__(self, nodeconstants, dir, node):
2472 def __init__(self, nodeconstants, dir, node):
2379 self.nodeconstants = nodeconstants
2473 self.nodeconstants = nodeconstants
2380 self._dir = dir
2474 self._dir = dir
2381 self._node = node
2475 self._node = node
2382
2476
2383 def read(self):
2477 def read(self):
2384 return excludeddir(self.nodeconstants, self._dir, self._node)
2478 return excludeddir(self.nodeconstants, self._dir, self._node)
2385
2479
2386 def readfast(self, shallow=False):
2480 def readfast(self, shallow=False):
2387 # special version of readfast since we don't have underlying storage
2481 # special version of readfast since we don't have underlying storage
2388 return self.read()
2482 return self.read()
2389
2483
2390 def write(self, *args):
2484 def write(self, *args):
2391 raise error.ProgrammingError(
2485 raise error.ProgrammingError(
2392 b'attempt to write manifest from excluded dir %s' % self._dir
2486 b'attempt to write manifest from excluded dir %s' % self._dir
2393 )
2487 )
2394
2488
2395
2489
2396 class excludedmanifestrevlog(manifestrevlog):
2490 class excludedmanifestrevlog(manifestrevlog):
2397 """Stand-in for excluded treemanifest revlogs.
2491 """Stand-in for excluded treemanifest revlogs.
2398
2492
2399 When narrowing is active on a treemanifest repository, we'll have
2493 When narrowing is active on a treemanifest repository, we'll have
2400 references to directories we can't see due to the revlog being
2494 references to directories we can't see due to the revlog being
2401 skipped. This class exists to conform to the manifestrevlog
2495 skipped. This class exists to conform to the manifestrevlog
2402 interface for those directories and proactively prevent writes to
2496 interface for those directories and proactively prevent writes to
2403 outside the narrowspec.
2497 outside the narrowspec.
2404 """
2498 """
2405
2499
2406 def __init__(self, nodeconstants, dir):
2500 def __init__(self, nodeconstants, dir):
2407 self.nodeconstants = nodeconstants
2501 self.nodeconstants = nodeconstants
2408 self._dir = dir
2502 self._dir = dir
2409
2503
2410 def __len__(self):
2504 def __len__(self):
2411 raise error.ProgrammingError(
2505 raise error.ProgrammingError(
2412 b'attempt to get length of excluded dir %s' % self._dir
2506 b'attempt to get length of excluded dir %s' % self._dir
2413 )
2507 )
2414
2508
2415 def rev(self, node):
2509 def rev(self, node):
2416 raise error.ProgrammingError(
2510 raise error.ProgrammingError(
2417 b'attempt to get rev from excluded dir %s' % self._dir
2511 b'attempt to get rev from excluded dir %s' % self._dir
2418 )
2512 )
2419
2513
2420 def linkrev(self, node):
2514 def linkrev(self, node):
2421 raise error.ProgrammingError(
2515 raise error.ProgrammingError(
2422 b'attempt to get linkrev from excluded dir %s' % self._dir
2516 b'attempt to get linkrev from excluded dir %s' % self._dir
2423 )
2517 )
2424
2518
2425 def node(self, rev):
2519 def node(self, rev):
2426 raise error.ProgrammingError(
2520 raise error.ProgrammingError(
2427 b'attempt to get node from excluded dir %s' % self._dir
2521 b'attempt to get node from excluded dir %s' % self._dir
2428 )
2522 )
2429
2523
2430 def add(self, *args, **kwargs):
2524 def add(self, *args, **kwargs):
2431 # We should never write entries in dirlogs outside the narrow clone.
2525 # We should never write entries in dirlogs outside the narrow clone.
2432 # However, the method still gets called from writesubtree() in
2526 # However, the method still gets called from writesubtree() in
2433 # _addtree(), so we need to handle it. We should possibly make that
2527 # _addtree(), so we need to handle it. We should possibly make that
2434 # avoid calling add() with a clean manifest (_dirty is always False
2528 # avoid calling add() with a clean manifest (_dirty is always False
2435 # in excludeddir instances).
2529 # in excludeddir instances).
2436 pass
2530 pass
General Comments 0
You need to be logged in to leave comments. Login now