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