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