##// END OF EJS Templates
manifest: drop the CamelCase name for `manifest.memmanifestctx`...
Matt Harbison -
r52964:3f47f0d9 default
parent child Browse files
Show More
@@ -1,2774 +1,2766
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: # (repository.imanifestdict)
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 def _msearch(
756 def _msearch(
757 m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None
757 m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None
758 ) -> Tuple[int, int]:
758 ) -> Tuple[int, int]:
759 """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.
760
760
761 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
762 that string. If start == end the string was not found and
762 that string. If start == end the string was not found and
763 they indicate the proper sorted insertion point.
763 they indicate the proper sorted insertion point.
764 """
764 """
765
765
766 def advance(i: int, c: bytes):
766 def advance(i: int, c: bytes):
767 while i < lenm and m[i : i + 1] != c:
767 while i < lenm and m[i : i + 1] != c:
768 i += 1
768 i += 1
769 return i
769 return i
770
770
771 if not s:
771 if not s:
772 return (lo, lo)
772 return (lo, lo)
773 lenm = len(m)
773 lenm = len(m)
774 if not hi:
774 if not hi:
775 hi = lenm
775 hi = lenm
776 while lo < hi:
776 while lo < hi:
777 mid = (lo + hi) // 2
777 mid = (lo + hi) // 2
778 start = mid
778 start = mid
779 while start > 0 and m[start - 1 : start] != b'\n':
779 while start > 0 and m[start - 1 : start] != b'\n':
780 start -= 1
780 start -= 1
781 end = advance(start, b'\0')
781 end = advance(start, b'\0')
782 if bytes(m[start:end]) < s:
782 if bytes(m[start:end]) < s:
783 # 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
784 # this translates to the bisect lo = mid + 1
784 # this translates to the bisect lo = mid + 1
785 lo = advance(end + 40, b'\n') + 1
785 lo = advance(end + 40, b'\n') + 1
786 else:
786 else:
787 # this translates to the bisect hi = mid
787 # this translates to the bisect hi = mid
788 hi = start
788 hi = start
789 end = advance(lo, b'\0')
789 end = advance(lo, b'\0')
790 found = m[lo:end]
790 found = m[lo:end]
791 if s == found:
791 if s == found:
792 # 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
793 end = advance(end + 40, b'\n')
793 end = advance(end + 40, b'\n')
794 return (lo, end + 1)
794 return (lo, end + 1)
795 else:
795 else:
796 return (lo, lo)
796 return (lo, lo)
797
797
798
798
799 def _checkforbidden(l: Iterable[bytes]) -> None:
799 def _checkforbidden(l: Iterable[bytes]) -> None:
800 """Check filenames for illegal characters."""
800 """Check filenames for illegal characters."""
801 for f in l:
801 for f in l:
802 if b'\n' in f or b'\r' in f:
802 if b'\n' in f or b'\r' in f:
803 raise error.StorageError(
803 raise error.StorageError(
804 _(b"'\\n' and '\\r' disallowed in filenames: %r")
804 _(b"'\\n' and '\\r' disallowed in filenames: %r")
805 % pycompat.bytestr(f)
805 % pycompat.bytestr(f)
806 )
806 )
807
807
808
808
809 # apply the changes collected during the bisect loop to our addlist
809 # apply the changes collected during the bisect loop to our addlist
810 # return a delta suitable for addrevision
810 # return a delta suitable for addrevision
811 def _addlistdelta(
811 def _addlistdelta(
812 addlist: ByteString,
812 addlist: ByteString,
813 x: Iterable[Tuple[int, int, bytes]],
813 x: Iterable[Tuple[int, int, bytes]],
814 ) -> Tuple[bytes, ByteString]:
814 ) -> Tuple[bytes, ByteString]:
815 # for large addlist arrays, building a new array is cheaper
815 # for large addlist arrays, building a new array is cheaper
816 # than repeatedly modifying the existing one
816 # than repeatedly modifying the existing one
817 currentposition = 0
817 currentposition = 0
818 newaddlist = bytearray()
818 newaddlist = bytearray()
819
819
820 for start, end, content in x:
820 for start, end, content in x:
821 newaddlist += addlist[currentposition:start]
821 newaddlist += addlist[currentposition:start]
822 if content:
822 if content:
823 newaddlist += bytearray(content)
823 newaddlist += bytearray(content)
824
824
825 currentposition = end
825 currentposition = end
826
826
827 newaddlist += addlist[currentposition:]
827 newaddlist += addlist[currentposition:]
828
828
829 deltatext = b"".join(
829 deltatext = b"".join(
830 struct.pack(b">lll", start, end, len(content)) + content
830 struct.pack(b">lll", start, end, len(content)) + content
831 for start, end, content in x
831 for start, end, content in x
832 )
832 )
833 return deltatext, newaddlist
833 return deltatext, newaddlist
834
834
835
835
836 def _splittopdir(f: bytes) -> Tuple[bytes, bytes]:
836 def _splittopdir(f: bytes) -> Tuple[bytes, bytes]:
837 if b'/' in f:
837 if b'/' in f:
838 dir, subpath = f.split(b'/', 1)
838 dir, subpath = f.split(b'/', 1)
839 return dir + b'/', subpath
839 return dir + b'/', subpath
840 else:
840 else:
841 return b'', f
841 return b'', f
842
842
843
843
844 _noop = lambda s: None
844 _noop = lambda s: None
845
845
846
846
847 class treemanifest: # (repository.imanifestdict)
847 class treemanifest: # (repository.imanifestdict)
848 _dir: bytes
848 _dir: bytes
849 _dirs: Dict[bytes, 'treemanifest']
849 _dirs: Dict[bytes, 'treemanifest']
850 _dirty: bool
850 _dirty: bool
851 _files: Dict[bytes, bytes]
851 _files: Dict[bytes, bytes]
852 _flags: Dict[bytes, bytes]
852 _flags: Dict[bytes, bytes]
853
853
854 def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''):
854 def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''):
855 self._dir = dir
855 self._dir = dir
856 self.nodeconstants = nodeconstants
856 self.nodeconstants = nodeconstants
857 self._node = self.nodeconstants.nullid
857 self._node = self.nodeconstants.nullid
858 self._nodelen = self.nodeconstants.nodelen
858 self._nodelen = self.nodeconstants.nodelen
859 self._loadfunc = _noop
859 self._loadfunc = _noop
860 self._copyfunc = _noop
860 self._copyfunc = _noop
861 self._dirty = False
861 self._dirty = False
862 self._dirs = {}
862 self._dirs = {}
863 self._lazydirs: Dict[
863 self._lazydirs: Dict[
864 bytes,
864 bytes,
865 Tuple[bytes, Callable[[bytes, bytes], 'treemanifest'], bool],
865 Tuple[bytes, Callable[[bytes, bytes], 'treemanifest'], bool],
866 ] = {}
866 ] = {}
867 # Using _lazymanifest here is a little slower than plain old dicts
867 # Using _lazymanifest here is a little slower than plain old dicts
868 self._files = {}
868 self._files = {}
869 self._flags = {}
869 self._flags = {}
870 if text:
870 if text:
871
871
872 def readsubtree(subdir, subm):
872 def readsubtree(subdir, subm):
873 raise AssertionError(
873 raise AssertionError(
874 b'treemanifest constructor only accepts flat manifests'
874 b'treemanifest constructor only accepts flat manifests'
875 )
875 )
876
876
877 self.parse(text, readsubtree)
877 self.parse(text, readsubtree)
878 self._dirty = True # Mark flat manifest dirty after parsing
878 self._dirty = True # Mark flat manifest dirty after parsing
879
879
880 def _subpath(self, path: bytes) -> bytes:
880 def _subpath(self, path: bytes) -> bytes:
881 return self._dir + path
881 return self._dir + path
882
882
883 def _loadalllazy(self) -> None:
883 def _loadalllazy(self) -> None:
884 selfdirs = self._dirs
884 selfdirs = self._dirs
885 subpath = self._subpath
885 subpath = self._subpath
886 for d, (node, readsubtree, docopy) in self._lazydirs.items():
886 for d, (node, readsubtree, docopy) in self._lazydirs.items():
887 if docopy:
887 if docopy:
888 selfdirs[d] = readsubtree(subpath(d), node).copy()
888 selfdirs[d] = readsubtree(subpath(d), node).copy()
889 else:
889 else:
890 selfdirs[d] = readsubtree(subpath(d), node)
890 selfdirs[d] = readsubtree(subpath(d), node)
891 self._lazydirs.clear()
891 self._lazydirs.clear()
892
892
893 def _loadlazy(self, d: bytes) -> None:
893 def _loadlazy(self, d: bytes) -> None:
894 v = self._lazydirs.get(d)
894 v = self._lazydirs.get(d)
895 if v is not None:
895 if v is not None:
896 node, readsubtree, docopy = v
896 node, readsubtree, docopy = v
897 if docopy:
897 if docopy:
898 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
898 self._dirs[d] = readsubtree(self._subpath(d), node).copy()
899 else:
899 else:
900 self._dirs[d] = readsubtree(self._subpath(d), node)
900 self._dirs[d] = readsubtree(self._subpath(d), node)
901 del self._lazydirs[d]
901 del self._lazydirs[d]
902
902
903 def _loadchildrensetlazy(
903 def _loadchildrensetlazy(
904 self, visit: Union[Set[bytes], bytes]
904 self, visit: Union[Set[bytes], bytes]
905 ) -> Optional[Set[bytes]]:
905 ) -> Optional[Set[bytes]]:
906 if not visit:
906 if not visit:
907 return None
907 return None
908 if visit == b'all' or visit == b'this':
908 if visit == b'all' or visit == b'this':
909 self._loadalllazy()
909 self._loadalllazy()
910 return None
910 return None
911
911
912 visit = cast(Set[bytes], visit)
912 visit = cast(Set[bytes], visit)
913
913
914 loadlazy = self._loadlazy
914 loadlazy = self._loadlazy
915 for k in visit:
915 for k in visit:
916 loadlazy(k + b'/')
916 loadlazy(k + b'/')
917 return visit
917 return visit
918
918
919 def _loaddifflazy(self, t1: 'treemanifest', t2: 'treemanifest'):
919 def _loaddifflazy(self, t1: 'treemanifest', t2: 'treemanifest'):
920 """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.
921
921
922 The criteria currently is:
922 The criteria currently is:
923 - 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
924 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)
925 - 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
926 differs, load it in both
926 differs, load it in both
927 """
927 """
928 toloadlazy = []
928 toloadlazy = []
929 for d, v1 in t1._lazydirs.items():
929 for d, v1 in t1._lazydirs.items():
930 v2 = t2._lazydirs.get(d)
930 v2 = t2._lazydirs.get(d)
931 if v2 is None or v2[0] != v1[0]:
931 if v2 is None or v2[0] != v1[0]:
932 toloadlazy.append(d)
932 toloadlazy.append(d)
933 for d, v1 in t2._lazydirs.items():
933 for d, v1 in t2._lazydirs.items():
934 if d not in t1._lazydirs:
934 if d not in t1._lazydirs:
935 toloadlazy.append(d)
935 toloadlazy.append(d)
936
936
937 for d in toloadlazy:
937 for d in toloadlazy:
938 t1._loadlazy(d)
938 t1._loadlazy(d)
939 t2._loadlazy(d)
939 t2._loadlazy(d)
940
940
941 def __len__(self) -> int:
941 def __len__(self) -> int:
942 self._load()
942 self._load()
943 size = len(self._files)
943 size = len(self._files)
944 self._loadalllazy()
944 self._loadalllazy()
945 for m in self._dirs.values():
945 for m in self._dirs.values():
946 size += m.__len__()
946 size += m.__len__()
947 return size
947 return size
948
948
949 def __nonzero__(self) -> bool:
949 def __nonzero__(self) -> bool:
950 # Faster than "__len__() != 0" since it avoids loading sub-manifests
950 # Faster than "__len__() != 0" since it avoids loading sub-manifests
951 return not self._isempty()
951 return not self._isempty()
952
952
953 __bool__ = __nonzero__
953 __bool__ = __nonzero__
954
954
955 def _isempty(self) -> bool:
955 def _isempty(self) -> bool:
956 self._load() # for consistency; already loaded by all callers
956 self._load() # for consistency; already loaded by all callers
957 # See if we can skip loading everything.
957 # See if we can skip loading everything.
958 if self._files or (
958 if self._files or (
959 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())
960 ):
960 ):
961 return False
961 return False
962 self._loadalllazy()
962 self._loadalllazy()
963 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())
964
964
965 @encoding.strmethod
965 @encoding.strmethod
966 def __repr__(self) -> bytes:
966 def __repr__(self) -> bytes:
967 return (
967 return (
968 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>'
969 % (
969 % (
970 self._dir,
970 self._dir,
971 hex(self._node),
971 hex(self._node),
972 bool(self._loadfunc is _noop),
972 bool(self._loadfunc is _noop),
973 self._dirty,
973 self._dirty,
974 id(self),
974 id(self),
975 )
975 )
976 )
976 )
977
977
978 def dir(self) -> bytes:
978 def dir(self) -> bytes:
979 """The directory that this tree manifest represents, including a
979 """The directory that this tree manifest represents, including a
980 trailing '/'. Empty string for the repo root directory."""
980 trailing '/'. Empty string for the repo root directory."""
981 return self._dir
981 return self._dir
982
982
983 def node(self) -> bytes:
983 def node(self) -> bytes:
984 """This node of this instance. nullid for unsaved instances. Should
984 """This node of this instance. nullid for unsaved instances. Should
985 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.
986 """
986 """
987 assert not self._dirty
987 assert not self._dirty
988 return self._node
988 return self._node
989
989
990 def setnode(self, node: bytes) -> None:
990 def setnode(self, node: bytes) -> None:
991 self._node = node
991 self._node = node
992 self._dirty = False
992 self._dirty = False
993
993
994 def iterentries(
994 def iterentries(
995 self,
995 self,
996 ) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest'], bytes]]:
996 ) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest'], bytes]]:
997 self._load()
997 self._load()
998 self._loadalllazy()
998 self._loadalllazy()
999 for p, n in sorted(
999 for p, n in sorted(
1000 itertools.chain(self._dirs.items(), self._files.items())
1000 itertools.chain(self._dirs.items(), self._files.items())
1001 ):
1001 ):
1002 if p in self._files:
1002 if p in self._files:
1003 yield self._subpath(p), n, self._flags.get(p, b'')
1003 yield self._subpath(p), n, self._flags.get(p, b'')
1004 else:
1004 else:
1005 for x in n.iterentries():
1005 for x in n.iterentries():
1006 yield x
1006 yield x
1007
1007
1008 def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest']]]:
1008 def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest']]]:
1009 self._load()
1009 self._load()
1010 self._loadalllazy()
1010 self._loadalllazy()
1011 for p, n in sorted(
1011 for p, n in sorted(
1012 itertools.chain(self._dirs.items(), self._files.items())
1012 itertools.chain(self._dirs.items(), self._files.items())
1013 ):
1013 ):
1014 if p in self._files:
1014 if p in self._files:
1015 yield self._subpath(p), n
1015 yield self._subpath(p), n
1016 else:
1016 else:
1017 for f, sn in n.items():
1017 for f, sn in n.items():
1018 yield f, sn
1018 yield f, sn
1019
1019
1020 iteritems = items
1020 iteritems = items
1021
1021
1022 def iterkeys(self) -> Iterator[bytes]:
1022 def iterkeys(self) -> Iterator[bytes]:
1023 self._load()
1023 self._load()
1024 self._loadalllazy()
1024 self._loadalllazy()
1025 for p in sorted(itertools.chain(self._dirs, self._files)):
1025 for p in sorted(itertools.chain(self._dirs, self._files)):
1026 if p in self._files:
1026 if p in self._files:
1027 yield self._subpath(p)
1027 yield self._subpath(p)
1028 else:
1028 else:
1029 for f in self._dirs[p]:
1029 for f in self._dirs[p]:
1030 yield f
1030 yield f
1031
1031
1032 def keys(self) -> List[bytes]:
1032 def keys(self) -> List[bytes]:
1033 return list(self.iterkeys())
1033 return list(self.iterkeys())
1034
1034
1035 def __iter__(self) -> Iterator[bytes]:
1035 def __iter__(self) -> Iterator[bytes]:
1036 return self.iterkeys()
1036 return self.iterkeys()
1037
1037
1038 def __contains__(self, f: bytes) -> bool:
1038 def __contains__(self, f: bytes) -> bool:
1039 if f is None:
1039 if f is None:
1040 return False
1040 return False
1041 self._load()
1041 self._load()
1042 dir, subpath = _splittopdir(f)
1042 dir, subpath = _splittopdir(f)
1043 if dir:
1043 if dir:
1044 self._loadlazy(dir)
1044 self._loadlazy(dir)
1045
1045
1046 if dir not in self._dirs:
1046 if dir not in self._dirs:
1047 return False
1047 return False
1048
1048
1049 return self._dirs[dir].__contains__(subpath)
1049 return self._dirs[dir].__contains__(subpath)
1050 else:
1050 else:
1051 return f in self._files
1051 return f in self._files
1052
1052
1053 def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]:
1053 def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]:
1054 self._load()
1054 self._load()
1055 dir, subpath = _splittopdir(f)
1055 dir, subpath = _splittopdir(f)
1056 if dir:
1056 if dir:
1057 self._loadlazy(dir)
1057 self._loadlazy(dir)
1058
1058
1059 if dir not in self._dirs:
1059 if dir not in self._dirs:
1060 return default
1060 return default
1061 return self._dirs[dir].get(subpath, default)
1061 return self._dirs[dir].get(subpath, default)
1062 else:
1062 else:
1063 return self._files.get(f, default)
1063 return self._files.get(f, default)
1064
1064
1065 def __getitem__(self, f: bytes) -> bytes:
1065 def __getitem__(self, f: bytes) -> bytes:
1066 self._load()
1066 self._load()
1067 dir, subpath = _splittopdir(f)
1067 dir, subpath = _splittopdir(f)
1068 if dir:
1068 if dir:
1069 self._loadlazy(dir)
1069 self._loadlazy(dir)
1070
1070
1071 return self._dirs[dir].__getitem__(subpath)
1071 return self._dirs[dir].__getitem__(subpath)
1072 else:
1072 else:
1073 return self._files[f]
1073 return self._files[f]
1074
1074
1075 def flags(self, f: bytes) -> bytes:
1075 def flags(self, f: bytes) -> bytes:
1076 self._load()
1076 self._load()
1077 dir, subpath = _splittopdir(f)
1077 dir, subpath = _splittopdir(f)
1078 if dir:
1078 if dir:
1079 self._loadlazy(dir)
1079 self._loadlazy(dir)
1080
1080
1081 if dir not in self._dirs:
1081 if dir not in self._dirs:
1082 return b''
1082 return b''
1083 return self._dirs[dir].flags(subpath)
1083 return self._dirs[dir].flags(subpath)
1084 else:
1084 else:
1085 if f in self._lazydirs or f in self._dirs:
1085 if f in self._lazydirs or f in self._dirs:
1086 return b''
1086 return b''
1087 return self._flags.get(f, b'')
1087 return self._flags.get(f, b'')
1088
1088
1089 def find(self, f: bytes) -> Tuple[bytes, bytes]:
1089 def find(self, f: bytes) -> Tuple[bytes, bytes]:
1090 self._load()
1090 self._load()
1091 dir, subpath = _splittopdir(f)
1091 dir, subpath = _splittopdir(f)
1092 if dir:
1092 if dir:
1093 self._loadlazy(dir)
1093 self._loadlazy(dir)
1094
1094
1095 return self._dirs[dir].find(subpath)
1095 return self._dirs[dir].find(subpath)
1096 else:
1096 else:
1097 return self._files[f], self._flags.get(f, b'')
1097 return self._files[f], self._flags.get(f, b'')
1098
1098
1099 def __delitem__(self, f: bytes) -> None:
1099 def __delitem__(self, f: bytes) -> None:
1100 self._load()
1100 self._load()
1101 dir, subpath = _splittopdir(f)
1101 dir, subpath = _splittopdir(f)
1102 if dir:
1102 if dir:
1103 self._loadlazy(dir)
1103 self._loadlazy(dir)
1104
1104
1105 self._dirs[dir].__delitem__(subpath)
1105 self._dirs[dir].__delitem__(subpath)
1106 # If the directory is now empty, remove it
1106 # If the directory is now empty, remove it
1107 if self._dirs[dir]._isempty():
1107 if self._dirs[dir]._isempty():
1108 del self._dirs[dir]
1108 del self._dirs[dir]
1109 else:
1109 else:
1110 del self._files[f]
1110 del self._files[f]
1111 if f in self._flags:
1111 if f in self._flags:
1112 del self._flags[f]
1112 del self._flags[f]
1113 self._dirty = True
1113 self._dirty = True
1114
1114
1115 def set(self, f: bytes, node: bytes, flags: bytes) -> None:
1115 def set(self, f: bytes, node: bytes, flags: bytes) -> None:
1116 """Set both the node and the flags for path f."""
1116 """Set both the node and the flags for path f."""
1117 assert node is not None
1117 assert node is not None
1118 if flags not in _manifestflags:
1118 if flags not in _manifestflags:
1119 raise TypeError(b"Invalid manifest flag set.")
1119 raise TypeError(b"Invalid manifest flag set.")
1120 self._load()
1120 self._load()
1121 dir, subpath = _splittopdir(f)
1121 dir, subpath = _splittopdir(f)
1122 if dir:
1122 if dir:
1123 self._loadlazy(dir)
1123 self._loadlazy(dir)
1124 if dir not in self._dirs:
1124 if dir not in self._dirs:
1125 self._dirs[dir] = treemanifest(
1125 self._dirs[dir] = treemanifest(
1126 self.nodeconstants, self._subpath(dir)
1126 self.nodeconstants, self._subpath(dir)
1127 )
1127 )
1128 self._dirs[dir].set(subpath, node, flags)
1128 self._dirs[dir].set(subpath, node, flags)
1129 else:
1129 else:
1130 assert len(node) in (20, 32)
1130 assert len(node) in (20, 32)
1131 self._files[f] = node
1131 self._files[f] = node
1132 self._flags[f] = flags
1132 self._flags[f] = flags
1133 self._dirty = True
1133 self._dirty = True
1134
1134
1135 def __setitem__(self, f: bytes, n: bytes) -> None:
1135 def __setitem__(self, f: bytes, n: bytes) -> None:
1136 assert n is not None
1136 assert n is not None
1137 self._load()
1137 self._load()
1138 dir, subpath = _splittopdir(f)
1138 dir, subpath = _splittopdir(f)
1139 if dir:
1139 if dir:
1140 self._loadlazy(dir)
1140 self._loadlazy(dir)
1141 if dir not in self._dirs:
1141 if dir not in self._dirs:
1142 self._dirs[dir] = treemanifest(
1142 self._dirs[dir] = treemanifest(
1143 self.nodeconstants, self._subpath(dir)
1143 self.nodeconstants, self._subpath(dir)
1144 )
1144 )
1145 self._dirs[dir].__setitem__(subpath, n)
1145 self._dirs[dir].__setitem__(subpath, n)
1146 else:
1146 else:
1147 # manifest nodes are either 20 bytes or 32 bytes,
1147 # manifest nodes are either 20 bytes or 32 bytes,
1148 # depending on the hash in use. Assert this as historically
1148 # depending on the hash in use. Assert this as historically
1149 # sometimes extra bytes were added.
1149 # sometimes extra bytes were added.
1150 assert len(n) in (20, 32)
1150 assert len(n) in (20, 32)
1151 self._files[f] = n
1151 self._files[f] = n
1152 self._dirty = True
1152 self._dirty = True
1153
1153
1154 def _load(self) -> None:
1154 def _load(self) -> None:
1155 if self._loadfunc is not _noop:
1155 if self._loadfunc is not _noop:
1156 lf, self._loadfunc = self._loadfunc, _noop
1156 lf, self._loadfunc = self._loadfunc, _noop
1157 lf(self)
1157 lf(self)
1158 elif self._copyfunc is not _noop:
1158 elif self._copyfunc is not _noop:
1159 cf, self._copyfunc = self._copyfunc, _noop
1159 cf, self._copyfunc = self._copyfunc, _noop
1160 cf(self)
1160 cf(self)
1161
1161
1162 def setflag(self, f: bytes, flags: bytes) -> None:
1162 def setflag(self, f: bytes, flags: bytes) -> None:
1163 """Set the flags (symlink, executable) for path f."""
1163 """Set the flags (symlink, executable) for path f."""
1164 if flags not in _manifestflags:
1164 if flags not in _manifestflags:
1165 raise TypeError(b"Invalid manifest flag set.")
1165 raise TypeError(b"Invalid manifest flag set.")
1166 self._load()
1166 self._load()
1167 dir, subpath = _splittopdir(f)
1167 dir, subpath = _splittopdir(f)
1168 if dir:
1168 if dir:
1169 self._loadlazy(dir)
1169 self._loadlazy(dir)
1170 if dir not in self._dirs:
1170 if dir not in self._dirs:
1171 self._dirs[dir] = treemanifest(
1171 self._dirs[dir] = treemanifest(
1172 self.nodeconstants, self._subpath(dir)
1172 self.nodeconstants, self._subpath(dir)
1173 )
1173 )
1174 self._dirs[dir].setflag(subpath, flags)
1174 self._dirs[dir].setflag(subpath, flags)
1175 else:
1175 else:
1176 self._flags[f] = flags
1176 self._flags[f] = flags
1177 self._dirty = True
1177 self._dirty = True
1178
1178
1179 def copy(self) -> 'treemanifest':
1179 def copy(self) -> 'treemanifest':
1180 copy = treemanifest(self.nodeconstants, self._dir)
1180 copy = treemanifest(self.nodeconstants, self._dir)
1181 copy._node = self._node
1181 copy._node = self._node
1182 copy._dirty = self._dirty
1182 copy._dirty = self._dirty
1183 if self._copyfunc is _noop:
1183 if self._copyfunc is _noop:
1184
1184
1185 def _copyfunc(s):
1185 def _copyfunc(s):
1186 self._load()
1186 self._load()
1187 s._lazydirs = {
1187 s._lazydirs = {
1188 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()
1189 }
1189 }
1190 sdirs = s._dirs
1190 sdirs = s._dirs
1191 for d, v in self._dirs.items():
1191 for d, v in self._dirs.items():
1192 sdirs[d] = v.copy()
1192 sdirs[d] = v.copy()
1193 s._files = dict.copy(self._files)
1193 s._files = dict.copy(self._files)
1194 s._flags = dict.copy(self._flags)
1194 s._flags = dict.copy(self._flags)
1195
1195
1196 if self._loadfunc is _noop:
1196 if self._loadfunc is _noop:
1197 _copyfunc(copy)
1197 _copyfunc(copy)
1198 else:
1198 else:
1199 copy._copyfunc = _copyfunc
1199 copy._copyfunc = _copyfunc
1200 else:
1200 else:
1201 copy._copyfunc = self._copyfunc
1201 copy._copyfunc = self._copyfunc
1202 return copy
1202 return copy
1203
1203
1204 def filesnotin(
1204 def filesnotin(
1205 self, m2: 'treemanifest', match: Optional[matchmod.basematcher] = None
1205 self, m2: 'treemanifest', match: Optional[matchmod.basematcher] = None
1206 ) -> Set[bytes]:
1206 ) -> Set[bytes]:
1207 '''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'''
1208 if match and not match.always():
1208 if match and not match.always():
1209 m1 = self._matches(match)
1209 m1 = self._matches(match)
1210 m2 = m2._matches(match)
1210 m2 = m2._matches(match)
1211 return m1.filesnotin(m2)
1211 return m1.filesnotin(m2)
1212
1212
1213 files = set()
1213 files = set()
1214
1214
1215 def _filesnotin(t1, t2):
1215 def _filesnotin(t1, t2):
1216 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:
1217 return
1217 return
1218 t1._load()
1218 t1._load()
1219 t2._load()
1219 t2._load()
1220 self._loaddifflazy(t1, t2)
1220 self._loaddifflazy(t1, t2)
1221 for d, m1 in t1._dirs.items():
1221 for d, m1 in t1._dirs.items():
1222 if d in t2._dirs:
1222 if d in t2._dirs:
1223 m2 = t2._dirs[d]
1223 m2 = t2._dirs[d]
1224 _filesnotin(m1, m2)
1224 _filesnotin(m1, m2)
1225 else:
1225 else:
1226 files.update(m1.iterkeys())
1226 files.update(m1.iterkeys())
1227
1227
1228 for fn in t1._files:
1228 for fn in t1._files:
1229 if fn not in t2._files:
1229 if fn not in t2._files:
1230 files.add(t1._subpath(fn))
1230 files.add(t1._subpath(fn))
1231
1231
1232 _filesnotin(self, m2)
1232 _filesnotin(self, m2)
1233 return files
1233 return files
1234
1234
1235 @propertycache
1235 @propertycache
1236 def _alldirs(self) -> pathutil.dirs:
1236 def _alldirs(self) -> pathutil.dirs:
1237 return pathutil.dirs(self)
1237 return pathutil.dirs(self)
1238
1238
1239 def dirs(self) -> pathutil.dirs:
1239 def dirs(self) -> pathutil.dirs:
1240 return self._alldirs
1240 return self._alldirs
1241
1241
1242 def hasdir(self, dir: bytes) -> bool:
1242 def hasdir(self, dir: bytes) -> bool:
1243 self._load()
1243 self._load()
1244 topdir, subdir = _splittopdir(dir)
1244 topdir, subdir = _splittopdir(dir)
1245 if topdir:
1245 if topdir:
1246 self._loadlazy(topdir)
1246 self._loadlazy(topdir)
1247 if topdir in self._dirs:
1247 if topdir in self._dirs:
1248 return self._dirs[topdir].hasdir(subdir)
1248 return self._dirs[topdir].hasdir(subdir)
1249 return False
1249 return False
1250 dirslash = dir + b'/'
1250 dirslash = dir + b'/'
1251 return dirslash in self._dirs or dirslash in self._lazydirs
1251 return dirslash in self._dirs or dirslash in self._lazydirs
1252
1252
1253 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1253 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1254 """Generates matching file names.
1254 """Generates matching file names.
1255
1255
1256 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().
1257 """
1257 """
1258 if match.always():
1258 if match.always():
1259 for f in iter(self):
1259 for f in iter(self):
1260 yield f
1260 yield f
1261 return
1261 return
1262
1262
1263 fset = set(match.files())
1263 fset = set(match.files())
1264
1264
1265 for fn in self._walk(match):
1265 for fn in self._walk(match):
1266 if fn in fset:
1266 if fn in fset:
1267 # specified pattern is the exact name
1267 # specified pattern is the exact name
1268 fset.remove(fn)
1268 fset.remove(fn)
1269 yield fn
1269 yield fn
1270
1270
1271 # for dirstate.walk, files=[''] means "walk the whole tree".
1271 # for dirstate.walk, files=[''] means "walk the whole tree".
1272 # follow that here, too
1272 # follow that here, too
1273 fset.discard(b'')
1273 fset.discard(b'')
1274
1274
1275 for fn in sorted(fset):
1275 for fn in sorted(fset):
1276 if not self.hasdir(fn):
1276 if not self.hasdir(fn):
1277 match.bad(fn, None)
1277 match.bad(fn, None)
1278
1278
1279 def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1279 def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
1280 '''Recursively generates matching file names for walk().'''
1280 '''Recursively generates matching file names for walk().'''
1281 visit = match.visitchildrenset(self._dir[:-1])
1281 visit = match.visitchildrenset(self._dir[:-1])
1282 if not visit:
1282 if not visit:
1283 return
1283 return
1284
1284
1285 # yield this dir's files and walk its submanifests
1285 # yield this dir's files and walk its submanifests
1286 self._load()
1286 self._load()
1287 visit = self._loadchildrensetlazy(visit)
1287 visit = self._loadchildrensetlazy(visit)
1288 for p in sorted(list(self._dirs) + list(self._files)):
1288 for p in sorted(list(self._dirs) + list(self._files)):
1289 if p in self._files:
1289 if p in self._files:
1290 fullp = self._subpath(p)
1290 fullp = self._subpath(p)
1291 if match(fullp):
1291 if match(fullp):
1292 yield fullp
1292 yield fullp
1293 else:
1293 else:
1294 if not visit or p[:-1] in visit:
1294 if not visit or p[:-1] in visit:
1295 for f in self._dirs[p]._walk(match):
1295 for f in self._dirs[p]._walk(match):
1296 yield f
1296 yield f
1297
1297
1298 def _matches(self, match: matchmod.basematcher) -> 'treemanifest':
1298 def _matches(self, match: matchmod.basematcher) -> 'treemanifest':
1299 """recursively generate a new manifest filtered by the match argument."""
1299 """recursively generate a new manifest filtered by the match argument."""
1300 if match.always():
1300 if match.always():
1301 return self.copy()
1301 return self.copy()
1302 return self._matches_inner(match)
1302 return self._matches_inner(match)
1303
1303
1304 def _matches_inner(self, match: matchmod.basematcher) -> 'treemanifest':
1304 def _matches_inner(self, match: matchmod.basematcher) -> 'treemanifest':
1305 if match.always():
1305 if match.always():
1306 return self.copy()
1306 return self.copy()
1307
1307
1308 visit = match.visitchildrenset(self._dir[:-1])
1308 visit = match.visitchildrenset(self._dir[:-1])
1309 if visit == b'all':
1309 if visit == b'all':
1310 return self.copy()
1310 return self.copy()
1311 ret = treemanifest(self.nodeconstants, self._dir)
1311 ret = treemanifest(self.nodeconstants, self._dir)
1312 if not visit:
1312 if not visit:
1313 return ret
1313 return ret
1314
1314
1315 self._load()
1315 self._load()
1316 for fn in self._files:
1316 for fn in self._files:
1317 # While visitchildrenset *usually* lists only subdirs, this is
1317 # While visitchildrenset *usually* lists only subdirs, this is
1318 # 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().
1319 # 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
1320 # 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
1321 # fn (but no need to inspect things not in the set).
1321 # fn (but no need to inspect things not in the set).
1322 if visit != b'this' and fn not in visit:
1322 if visit != b'this' and fn not in visit:
1323 continue
1323 continue
1324 fullp = self._subpath(fn)
1324 fullp = self._subpath(fn)
1325 # visitchildrenset isn't perfect, we still need to call the regular
1325 # visitchildrenset isn't perfect, we still need to call the regular
1326 # matcher code to further filter results.
1326 # matcher code to further filter results.
1327 if not match(fullp):
1327 if not match(fullp):
1328 continue
1328 continue
1329 ret._files[fn] = self._files[fn]
1329 ret._files[fn] = self._files[fn]
1330 if fn in self._flags:
1330 if fn in self._flags:
1331 ret._flags[fn] = self._flags[fn]
1331 ret._flags[fn] = self._flags[fn]
1332
1332
1333 visit = self._loadchildrensetlazy(visit)
1333 visit = self._loadchildrensetlazy(visit)
1334 for dir, subm in self._dirs.items():
1334 for dir, subm in self._dirs.items():
1335 if visit and dir[:-1] not in visit:
1335 if visit and dir[:-1] not in visit:
1336 continue
1336 continue
1337 m = subm._matches_inner(match)
1337 m = subm._matches_inner(match)
1338 if not m._isempty():
1338 if not m._isempty():
1339 ret._dirs[dir] = m
1339 ret._dirs[dir] = m
1340
1340
1341 if not ret._isempty():
1341 if not ret._isempty():
1342 ret._dirty = True
1342 ret._dirty = True
1343 return ret
1343 return ret
1344
1344
1345 def fastdelta(
1345 def fastdelta(
1346 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
1346 self, base: ByteString, changes: Iterable[Tuple[bytes, bool]]
1347 ) -> ByteString:
1347 ) -> ByteString:
1348 raise FastdeltaUnavailable()
1348 raise FastdeltaUnavailable()
1349
1349
1350 def diff(
1350 def diff(
1351 self,
1351 self,
1352 m2: 'treemanifest',
1352 m2: 'treemanifest',
1353 match: Optional[matchmod.basematcher] = None,
1353 match: Optional[matchmod.basematcher] = None,
1354 clean: bool = False,
1354 clean: bool = False,
1355 ) -> Dict[
1355 ) -> Dict[
1356 bytes,
1356 bytes,
1357 Optional[
1357 Optional[
1358 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
1358 Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]]
1359 ],
1359 ],
1360 ]:
1360 ]:
1361 """Finds changes between the current manifest and m2.
1361 """Finds changes between the current manifest and m2.
1362
1362
1363 Args:
1363 Args:
1364 m2: the manifest to which this manifest should be compared.
1364 m2: the manifest to which this manifest should be compared.
1365 clean: if true, include files unchanged between these manifests
1365 clean: if true, include files unchanged between these manifests
1366 with a None value in the returned dictionary.
1366 with a None value in the returned dictionary.
1367
1367
1368 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
1369 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
1370 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
1371 in the current/other manifest. Where the file does not exist,
1371 in the current/other manifest. Where the file does not exist,
1372 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
1373 string.
1373 string.
1374 """
1374 """
1375 if match and not match.always():
1375 if match and not match.always():
1376 m1 = self._matches(match)
1376 m1 = self._matches(match)
1377 m2 = m2._matches(match)
1377 m2 = m2._matches(match)
1378 return m1.diff(m2, clean=clean)
1378 return m1.diff(m2, clean=clean)
1379 result = {}
1379 result = {}
1380 emptytree = treemanifest(self.nodeconstants)
1380 emptytree = treemanifest(self.nodeconstants)
1381
1381
1382 def _iterativediff(t1, t2, stack):
1382 def _iterativediff(t1, t2, stack):
1383 """compares two tree manifests and append new tree-manifests which
1383 """compares two tree manifests and append new tree-manifests which
1384 needs to be compared to stack"""
1384 needs to be compared to stack"""
1385 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:
1386 return
1386 return
1387 t1._load()
1387 t1._load()
1388 t2._load()
1388 t2._load()
1389 self._loaddifflazy(t1, t2)
1389 self._loaddifflazy(t1, t2)
1390
1390
1391 for d, m1 in t1._dirs.items():
1391 for d, m1 in t1._dirs.items():
1392 m2 = t2._dirs.get(d, emptytree)
1392 m2 = t2._dirs.get(d, emptytree)
1393 stack.append((m1, m2))
1393 stack.append((m1, m2))
1394
1394
1395 for d, m2 in t2._dirs.items():
1395 for d, m2 in t2._dirs.items():
1396 if d not in t1._dirs:
1396 if d not in t1._dirs:
1397 stack.append((emptytree, m2))
1397 stack.append((emptytree, m2))
1398
1398
1399 for fn, n1 in t1._files.items():
1399 for fn, n1 in t1._files.items():
1400 fl1 = t1._flags.get(fn, b'')
1400 fl1 = t1._flags.get(fn, b'')
1401 n2 = t2._files.get(fn, None)
1401 n2 = t2._files.get(fn, None)
1402 fl2 = t2._flags.get(fn, b'')
1402 fl2 = t2._flags.get(fn, b'')
1403 if n1 != n2 or fl1 != fl2:
1403 if n1 != n2 or fl1 != fl2:
1404 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1404 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1405 elif clean:
1405 elif clean:
1406 result[t1._subpath(fn)] = None
1406 result[t1._subpath(fn)] = None
1407
1407
1408 for fn, n2 in t2._files.items():
1408 for fn, n2 in t2._files.items():
1409 if fn not in t1._files:
1409 if fn not in t1._files:
1410 fl2 = t2._flags.get(fn, b'')
1410 fl2 = t2._flags.get(fn, b'')
1411 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1411 result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
1412
1412
1413 stackls = []
1413 stackls = []
1414 _iterativediff(self, m2, stackls)
1414 _iterativediff(self, m2, stackls)
1415 while stackls:
1415 while stackls:
1416 t1, t2 = stackls.pop()
1416 t1, t2 = stackls.pop()
1417 # stackls is populated in the function call
1417 # stackls is populated in the function call
1418 _iterativediff(t1, t2, stackls)
1418 _iterativediff(t1, t2, stackls)
1419 return result
1419 return result
1420
1420
1421 def unmodifiedsince(self, m2: 'treemanifest') -> bool:
1421 def unmodifiedsince(self, m2: 'treemanifest') -> bool:
1422 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
1423
1423
1424 def parse(
1424 def parse(
1425 self,
1425 self,
1426 text: bytes,
1426 text: bytes,
1427 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1427 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1428 ) -> None:
1428 ) -> None:
1429 selflazy = self._lazydirs
1429 selflazy = self._lazydirs
1430 for f, n, fl in _parse(self._nodelen, text):
1430 for f, n, fl in _parse(self._nodelen, text):
1431 if fl == b't':
1431 if fl == b't':
1432 f = f + b'/'
1432 f = f + b'/'
1433 # 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
1434 # cached value from readsubtree directly.
1434 # cached value from readsubtree directly.
1435 selflazy[f] = (n, readsubtree, False)
1435 selflazy[f] = (n, readsubtree, False)
1436 elif b'/' in f:
1436 elif b'/' in f:
1437 # This is a flat manifest, so use __setitem__ and setflag rather
1437 # This is a flat manifest, so use __setitem__ and setflag rather
1438 # than assigning directly to _files and _flags, so we can
1438 # than assigning directly to _files and _flags, so we can
1439 # assign a path in a subdirectory, and to mark dirty (compared
1439 # assign a path in a subdirectory, and to mark dirty (compared
1440 # to nullid).
1440 # to nullid).
1441 self[f] = n
1441 self[f] = n
1442 if fl:
1442 if fl:
1443 self.setflag(f, fl)
1443 self.setflag(f, fl)
1444 else:
1444 else:
1445 # Assigning to _files and _flags avoids marking as dirty,
1445 # Assigning to _files and _flags avoids marking as dirty,
1446 # and should be a little faster.
1446 # and should be a little faster.
1447 self._files[f] = n
1447 self._files[f] = n
1448 if fl:
1448 if fl:
1449 self._flags[f] = fl
1449 self._flags[f] = fl
1450
1450
1451 def text(self) -> ByteString:
1451 def text(self) -> ByteString:
1452 """Get the full data of this manifest as a bytestring."""
1452 """Get the full data of this manifest as a bytestring."""
1453 self._load()
1453 self._load()
1454 return _text(self.iterentries())
1454 return _text(self.iterentries())
1455
1455
1456 def dirtext(self) -> ByteString:
1456 def dirtext(self) -> ByteString:
1457 """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
1458 any submanifests have been written first, so their nodeids are correct.
1458 any submanifests have been written first, so their nodeids are correct.
1459 """
1459 """
1460 self._load()
1460 self._load()
1461 flags = self.flags
1461 flags = self.flags
1462 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()]
1463 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]
1464 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]
1465 return _text(sorted(dirs + files + lazydirs))
1465 return _text(sorted(dirs + files + lazydirs))
1466
1466
1467 def read(
1467 def read(
1468 self,
1468 self,
1469 gettext: Callable[[], ByteString],
1469 gettext: Callable[[], ByteString],
1470 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1470 readsubtree: Callable[[bytes, bytes], 'treemanifest'],
1471 ) -> None:
1471 ) -> None:
1472 def _load_for_read(s):
1472 def _load_for_read(s):
1473 s.parse(gettext(), readsubtree)
1473 s.parse(gettext(), readsubtree)
1474 s._dirty = False
1474 s._dirty = False
1475
1475
1476 self._loadfunc = _load_for_read
1476 self._loadfunc = _load_for_read
1477
1477
1478 def writesubtrees(
1478 def writesubtrees(
1479 self,
1479 self,
1480 m1: 'treemanifest',
1480 m1: 'treemanifest',
1481 m2: 'treemanifest',
1481 m2: 'treemanifest',
1482 writesubtree: Callable[
1482 writesubtree: Callable[
1483 [
1483 [
1484 Callable[['treemanifest'], None],
1484 Callable[['treemanifest'], None],
1485 bytes,
1485 bytes,
1486 bytes,
1486 bytes,
1487 matchmod.basematcher,
1487 matchmod.basematcher,
1488 ],
1488 ],
1489 None,
1489 None,
1490 ],
1490 ],
1491 match: matchmod.basematcher,
1491 match: matchmod.basematcher,
1492 ) -> None:
1492 ) -> None:
1493 self._load() # for consistency; should never have any effect here
1493 self._load() # for consistency; should never have any effect here
1494 m1._load()
1494 m1._load()
1495 m2._load()
1495 m2._load()
1496 emptytree = treemanifest(self.nodeconstants)
1496 emptytree = treemanifest(self.nodeconstants)
1497
1497
1498 def getnode(m, d):
1498 def getnode(m, d):
1499 ld = m._lazydirs.get(d)
1499 ld = m._lazydirs.get(d)
1500 if ld:
1500 if ld:
1501 return ld[0]
1501 return ld[0]
1502 tree = m._dirs.get(d, emptytree)
1502 tree = m._dirs.get(d, emptytree)
1503 assert tree is not None # helps pytype
1503 assert tree is not None # helps pytype
1504 return tree._node
1504 return tree._node
1505
1505
1506 # 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.
1507 visit = match.visitchildrenset(self._dir[:-1])
1507 visit = match.visitchildrenset(self._dir[:-1])
1508 visit = self._loadchildrensetlazy(visit)
1508 visit = self._loadchildrensetlazy(visit)
1509 if visit == b'this' or visit == b'all':
1509 if visit == b'this' or visit == b'all':
1510 visit = None
1510 visit = None
1511 for d, subm in self._dirs.items():
1511 for d, subm in self._dirs.items():
1512 if visit and d[:-1] not in visit:
1512 if visit and d[:-1] not in visit:
1513 continue
1513 continue
1514 subp1 = getnode(m1, d)
1514 subp1 = getnode(m1, d)
1515 subp2 = getnode(m2, d)
1515 subp2 = getnode(m2, d)
1516 if subp1 == self.nodeconstants.nullid:
1516 if subp1 == self.nodeconstants.nullid:
1517 subp1, subp2 = subp2, subp1
1517 subp1, subp2 = subp2, subp1
1518 writesubtree(subm, subp1, subp2, match)
1518 writesubtree(subm, subp1, subp2, match)
1519
1519
1520 def walksubtrees(
1520 def walksubtrees(
1521 self, matcher: Optional[matchmod.basematcher] = None
1521 self, matcher: Optional[matchmod.basematcher] = None
1522 ) -> Iterator['treemanifest']:
1522 ) -> Iterator['treemanifest']:
1523 """Returns an iterator of the subtrees of this manifest, including this
1523 """Returns an iterator of the subtrees of this manifest, including this
1524 manifest itself.
1524 manifest itself.
1525
1525
1526 If `matcher` is provided, it only returns subtrees that match.
1526 If `matcher` is provided, it only returns subtrees that match.
1527 """
1527 """
1528 if matcher and not matcher.visitdir(self._dir[:-1]):
1528 if matcher and not matcher.visitdir(self._dir[:-1]):
1529 return
1529 return
1530 if not matcher or matcher(self._dir[:-1]):
1530 if not matcher or matcher(self._dir[:-1]):
1531 yield self
1531 yield self
1532
1532
1533 self._load()
1533 self._load()
1534 # OPT: use visitchildrenset to avoid loading everything.
1534 # OPT: use visitchildrenset to avoid loading everything.
1535 self._loadalllazy()
1535 self._loadalllazy()
1536 for d, subm in self._dirs.items():
1536 for d, subm in self._dirs.items():
1537 for subtree in subm.walksubtrees(matcher=matcher):
1537 for subtree in subm.walksubtrees(matcher=matcher):
1538 yield subtree
1538 yield subtree
1539
1539
1540
1540
1541 class manifestfulltextcache(util.lrucachedict):
1541 class manifestfulltextcache(util.lrucachedict):
1542 """File-backed LRU cache for the manifest cache
1542 """File-backed LRU cache for the manifest cache
1543
1543
1544 File consists of entries, up to EOF:
1544 File consists of entries, up to EOF:
1545
1545
1546 - 20 bytes node, 4 bytes length, <length> manifest data
1546 - 20 bytes node, 4 bytes length, <length> manifest data
1547
1547
1548 These are written in reverse cache order (oldest to newest).
1548 These are written in reverse cache order (oldest to newest).
1549
1549
1550 """
1550 """
1551
1551
1552 _file = b'manifestfulltextcache'
1552 _file = b'manifestfulltextcache'
1553
1553
1554 def __init__(self, max):
1554 def __init__(self, max):
1555 super(manifestfulltextcache, self).__init__(max)
1555 super(manifestfulltextcache, self).__init__(max)
1556 self._dirty = False
1556 self._dirty = False
1557 self._read = False
1557 self._read = False
1558 self._opener = None
1558 self._opener = None
1559
1559
1560 def read(self):
1560 def read(self):
1561 if self._read or self._opener is None:
1561 if self._read or self._opener is None:
1562 return
1562 return
1563
1563
1564 try:
1564 try:
1565 with self._opener(self._file) as fp:
1565 with self._opener(self._file) as fp:
1566 set = super(manifestfulltextcache, self).__setitem__
1566 set = super(manifestfulltextcache, self).__setitem__
1567 # ignore trailing data, this is a cache, corruption is skipped
1567 # ignore trailing data, this is a cache, corruption is skipped
1568 while True:
1568 while True:
1569 # TODO do we need to do work here for sha1 portability?
1569 # TODO do we need to do work here for sha1 portability?
1570 node = fp.read(20)
1570 node = fp.read(20)
1571 if len(node) < 20:
1571 if len(node) < 20:
1572 break
1572 break
1573 try:
1573 try:
1574 size = struct.unpack(b'>L', fp.read(4))[0]
1574 size = struct.unpack(b'>L', fp.read(4))[0]
1575 except struct.error:
1575 except struct.error:
1576 break
1576 break
1577 value = bytearray(fp.read(size))
1577 value = bytearray(fp.read(size))
1578 if len(value) != size:
1578 if len(value) != size:
1579 break
1579 break
1580 set(node, value)
1580 set(node, value)
1581 except IOError:
1581 except IOError:
1582 # the file is allowed to be missing
1582 # the file is allowed to be missing
1583 pass
1583 pass
1584
1584
1585 self._read = True
1585 self._read = True
1586 self._dirty = False
1586 self._dirty = False
1587
1587
1588 def write(self):
1588 def write(self):
1589 if not self._dirty or self._opener is None:
1589 if not self._dirty or self._opener is None:
1590 return
1590 return
1591 # rotate backwards to the first used node
1591 # rotate backwards to the first used node
1592 try:
1592 try:
1593 with self._opener(
1593 with self._opener(
1594 self._file, b'w', atomictemp=True, checkambig=True
1594 self._file, b'w', atomictemp=True, checkambig=True
1595 ) as fp:
1595 ) as fp:
1596 node = self._head.prev
1596 node = self._head.prev
1597 while True:
1597 while True:
1598 if node.key in self._cache:
1598 if node.key in self._cache:
1599 fp.write(node.key)
1599 fp.write(node.key)
1600 fp.write(struct.pack(b'>L', len(node.value)))
1600 fp.write(struct.pack(b'>L', len(node.value)))
1601 fp.write(node.value)
1601 fp.write(node.value)
1602 if node is self._head:
1602 if node is self._head:
1603 break
1603 break
1604 node = node.prev
1604 node = node.prev
1605 except IOError:
1605 except IOError:
1606 # We could not write the cache (eg: permission error)
1606 # We could not write the cache (eg: permission error)
1607 # the content can be missing.
1607 # the content can be missing.
1608 #
1608 #
1609 # We could try harder and see if we could recreate a wcache
1609 # We could try harder and see if we could recreate a wcache
1610 # directory were we coudl write too.
1610 # directory were we coudl write too.
1611 #
1611 #
1612 # XXX the error pass silently, having some way to issue an error
1612 # XXX the error pass silently, having some way to issue an error
1613 # log `ui.log` would be nice.
1613 # log `ui.log` would be nice.
1614 pass
1614 pass
1615
1615
1616 def __len__(self):
1616 def __len__(self):
1617 if not self._read:
1617 if not self._read:
1618 self.read()
1618 self.read()
1619 return super(manifestfulltextcache, self).__len__()
1619 return super(manifestfulltextcache, self).__len__()
1620
1620
1621 def __contains__(self, k):
1621 def __contains__(self, k):
1622 if not self._read:
1622 if not self._read:
1623 self.read()
1623 self.read()
1624 return super(manifestfulltextcache, self).__contains__(k)
1624 return super(manifestfulltextcache, self).__contains__(k)
1625
1625
1626 def __iter__(self):
1626 def __iter__(self):
1627 if not self._read:
1627 if not self._read:
1628 self.read()
1628 self.read()
1629 return super(manifestfulltextcache, self).__iter__()
1629 return super(manifestfulltextcache, self).__iter__()
1630
1630
1631 def __getitem__(self, k):
1631 def __getitem__(self, k):
1632 if not self._read:
1632 if not self._read:
1633 self.read()
1633 self.read()
1634 # the cache lru order can change on read
1634 # the cache lru order can change on read
1635 setdirty = self._cache.get(k) is not self._head
1635 setdirty = self._cache.get(k) is not self._head
1636 value = super(manifestfulltextcache, self).__getitem__(k)
1636 value = super(manifestfulltextcache, self).__getitem__(k)
1637 if setdirty:
1637 if setdirty:
1638 self._dirty = True
1638 self._dirty = True
1639 return value
1639 return value
1640
1640
1641 def __setitem__(self, k, v):
1641 def __setitem__(self, k, v):
1642 if not self._read:
1642 if not self._read:
1643 self.read()
1643 self.read()
1644 super(manifestfulltextcache, self).__setitem__(k, v)
1644 super(manifestfulltextcache, self).__setitem__(k, v)
1645 self._dirty = True
1645 self._dirty = True
1646
1646
1647 def __delitem__(self, k):
1647 def __delitem__(self, k):
1648 if not self._read:
1648 if not self._read:
1649 self.read()
1649 self.read()
1650 super(manifestfulltextcache, self).__delitem__(k)
1650 super(manifestfulltextcache, self).__delitem__(k)
1651 self._dirty = True
1651 self._dirty = True
1652
1652
1653 def get(self, k, default=None):
1653 def get(self, k, default=None):
1654 if not self._read:
1654 if not self._read:
1655 self.read()
1655 self.read()
1656 return super(manifestfulltextcache, self).get(k, default=default)
1656 return super(manifestfulltextcache, self).get(k, default=default)
1657
1657
1658 def clear(self, clear_persisted_data=False):
1658 def clear(self, clear_persisted_data=False):
1659 super(manifestfulltextcache, self).clear()
1659 super(manifestfulltextcache, self).clear()
1660 if clear_persisted_data:
1660 if clear_persisted_data:
1661 self._dirty = True
1661 self._dirty = True
1662 self.write()
1662 self.write()
1663 self._read = False
1663 self._read = False
1664
1664
1665
1665
1666 # and upper bound of what we expect from compression
1666 # and upper bound of what we expect from compression
1667 # (real live value seems to be "3")
1667 # (real live value seems to be "3")
1668 MAXCOMPRESSION = 3
1668 MAXCOMPRESSION = 3
1669
1669
1670
1670
1671 class FastdeltaUnavailable(Exception):
1671 class FastdeltaUnavailable(Exception):
1672 """Exception raised when fastdelta isn't usable on a manifest."""
1672 """Exception raised when fastdelta isn't usable on a manifest."""
1673
1673
1674
1674
1675 class manifestrevlog: # (repository.imanifeststorage)
1675 class manifestrevlog: # (repository.imanifeststorage)
1676 """A revlog that stores manifest texts. This is responsible for caching the
1676 """A revlog that stores manifest texts. This is responsible for caching the
1677 full-text manifest contents.
1677 full-text manifest contents.
1678 """
1678 """
1679
1679
1680 def __init__(
1680 def __init__(
1681 self,
1681 self,
1682 nodeconstants,
1682 nodeconstants,
1683 opener,
1683 opener,
1684 tree=b'',
1684 tree=b'',
1685 dirlogcache=None,
1685 dirlogcache=None,
1686 treemanifest=False,
1686 treemanifest=False,
1687 ):
1687 ):
1688 """Constructs a new manifest revlog
1688 """Constructs a new manifest revlog
1689
1689
1690 `indexfile` - used by extensions to have two manifests at once, like
1690 `indexfile` - used by extensions to have two manifests at once, like
1691 when transitioning between flatmanifeset and treemanifests.
1691 when transitioning between flatmanifeset and treemanifests.
1692
1692
1693 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1693 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1694 options can also be used to make this a tree manifest revlog. The opener
1694 options can also be used to make this a tree manifest revlog. The opener
1695 option takes precedence, so if it is set to True, we ignore whatever
1695 option takes precedence, so if it is set to True, we ignore whatever
1696 value is passed in to the constructor.
1696 value is passed in to the constructor.
1697 """
1697 """
1698 self.nodeconstants = nodeconstants
1698 self.nodeconstants = nodeconstants
1699 # During normal operations, we expect to deal with not more than four
1699 # During normal operations, we expect to deal with not more than four
1700 # revs at a time (such as during commit --amend). When rebasing large
1700 # revs at a time (such as during commit --amend). When rebasing large
1701 # stacks of commits, the number can go up, hence the config knob below.
1701 # stacks of commits, the number can go up, hence the config knob below.
1702 cachesize = 4
1702 cachesize = 4
1703 optiontreemanifest = False
1703 optiontreemanifest = False
1704 persistentnodemap = False
1704 persistentnodemap = False
1705 opts = getattr(opener, 'options', None)
1705 opts = getattr(opener, 'options', None)
1706 if opts is not None:
1706 if opts is not None:
1707 cachesize = opts.get(b'manifestcachesize', cachesize)
1707 cachesize = opts.get(b'manifestcachesize', cachesize)
1708 optiontreemanifest = opts.get(b'treemanifest', False)
1708 optiontreemanifest = opts.get(b'treemanifest', False)
1709 persistentnodemap = opts.get(b'persistent-nodemap', False)
1709 persistentnodemap = opts.get(b'persistent-nodemap', False)
1710
1710
1711 self._treeondisk = optiontreemanifest or treemanifest
1711 self._treeondisk = optiontreemanifest or treemanifest
1712
1712
1713 self._fulltextcache = manifestfulltextcache(cachesize)
1713 self._fulltextcache = manifestfulltextcache(cachesize)
1714
1714
1715 if tree:
1715 if tree:
1716 assert self._treeondisk, (tree, b'opts is %r' % opts)
1716 assert self._treeondisk, (tree, b'opts is %r' % opts)
1717
1717
1718 radix = b'00manifest'
1718 radix = b'00manifest'
1719 if tree:
1719 if tree:
1720 radix = b"meta/" + tree + radix
1720 radix = b"meta/" + tree + radix
1721
1721
1722 self.tree = tree
1722 self.tree = tree
1723
1723
1724 # The dirlogcache is kept on the root manifest log
1724 # The dirlogcache is kept on the root manifest log
1725 if tree:
1725 if tree:
1726 self._dirlogcache = dirlogcache
1726 self._dirlogcache = dirlogcache
1727 else:
1727 else:
1728 self._dirlogcache = {b'': self}
1728 self._dirlogcache = {b'': self}
1729
1729
1730 self._revlog = revlog.revlog(
1730 self._revlog = revlog.revlog(
1731 opener,
1731 opener,
1732 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1732 target=(revlog_constants.KIND_MANIFESTLOG, self.tree),
1733 radix=radix,
1733 radix=radix,
1734 # only root indexfile is cached
1734 # only root indexfile is cached
1735 checkambig=not bool(tree),
1735 checkambig=not bool(tree),
1736 mmaplargeindex=True,
1736 mmaplargeindex=True,
1737 upperboundcomp=MAXCOMPRESSION,
1737 upperboundcomp=MAXCOMPRESSION,
1738 persistentnodemap=persistentnodemap,
1738 persistentnodemap=persistentnodemap,
1739 )
1739 )
1740
1740
1741 self.index = self._revlog.index
1741 self.index = self._revlog.index
1742
1742
1743 def get_revlog(self):
1743 def get_revlog(self):
1744 """return an actual revlog instance if any
1744 """return an actual revlog instance if any
1745
1745
1746 This exist because a lot of code leverage the fact the underlying
1746 This exist because a lot of code leverage the fact the underlying
1747 storage is a revlog for optimization, so giving simple way to access
1747 storage is a revlog for optimization, so giving simple way to access
1748 the revlog instance helps such code.
1748 the revlog instance helps such code.
1749 """
1749 """
1750 return self._revlog
1750 return self._revlog
1751
1751
1752 def _setupmanifestcachehooks(self, repo):
1752 def _setupmanifestcachehooks(self, repo):
1753 """Persist the manifestfulltextcache on lock release"""
1753 """Persist the manifestfulltextcache on lock release"""
1754 if not hasattr(repo, '_wlockref'):
1754 if not hasattr(repo, '_wlockref'):
1755 return
1755 return
1756
1756
1757 self._fulltextcache._opener = repo.wcachevfs
1757 self._fulltextcache._opener = repo.wcachevfs
1758 if repo._currentlock(repo._wlockref) is None:
1758 if repo._currentlock(repo._wlockref) is None:
1759 return
1759 return
1760
1760
1761 reporef = weakref.ref(repo)
1761 reporef = weakref.ref(repo)
1762 manifestrevlogref = weakref.ref(self)
1762 manifestrevlogref = weakref.ref(self)
1763
1763
1764 def persistmanifestcache(success):
1764 def persistmanifestcache(success):
1765 # Repo is in an unknown state, do not persist.
1765 # Repo is in an unknown state, do not persist.
1766 if not success:
1766 if not success:
1767 return
1767 return
1768
1768
1769 repo = reporef()
1769 repo = reporef()
1770 self = manifestrevlogref()
1770 self = manifestrevlogref()
1771 if repo is None or self is None:
1771 if repo is None or self is None:
1772 return
1772 return
1773 if repo.manifestlog.getstorage(b'') is not self:
1773 if repo.manifestlog.getstorage(b'') is not self:
1774 # there's a different manifest in play now, abort
1774 # there's a different manifest in play now, abort
1775 return
1775 return
1776 self._fulltextcache.write()
1776 self._fulltextcache.write()
1777
1777
1778 repo._afterlock(persistmanifestcache)
1778 repo._afterlock(persistmanifestcache)
1779
1779
1780 @property
1780 @property
1781 def fulltextcache(self):
1781 def fulltextcache(self):
1782 return self._fulltextcache
1782 return self._fulltextcache
1783
1783
1784 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1784 def clearcaches(self, clear_persisted_data: bool = False) -> None:
1785 self._revlog.clearcaches()
1785 self._revlog.clearcaches()
1786 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1786 self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
1787 self._dirlogcache = {self.tree: self}
1787 self._dirlogcache = {self.tree: self}
1788
1788
1789 def dirlog(self, d):
1789 def dirlog(self, d):
1790 if d:
1790 if d:
1791 assert self._treeondisk
1791 assert self._treeondisk
1792 if d not in self._dirlogcache:
1792 if d not in self._dirlogcache:
1793 mfrevlog = manifestrevlog(
1793 mfrevlog = manifestrevlog(
1794 self.nodeconstants,
1794 self.nodeconstants,
1795 self.opener,
1795 self.opener,
1796 d,
1796 d,
1797 self._dirlogcache,
1797 self._dirlogcache,
1798 treemanifest=self._treeondisk,
1798 treemanifest=self._treeondisk,
1799 )
1799 )
1800 self._dirlogcache[d] = mfrevlog
1800 self._dirlogcache[d] = mfrevlog
1801 return self._dirlogcache[d]
1801 return self._dirlogcache[d]
1802
1802
1803 def add(
1803 def add(
1804 self,
1804 self,
1805 m,
1805 m,
1806 transaction,
1806 transaction,
1807 link,
1807 link,
1808 p1,
1808 p1,
1809 p2,
1809 p2,
1810 added: Iterable[bytes],
1810 added: Iterable[bytes],
1811 removed: Iterable[bytes],
1811 removed: Iterable[bytes],
1812 readtree=None,
1812 readtree=None,
1813 match=None,
1813 match=None,
1814 ):
1814 ):
1815 """add some manifest entry in to the manifest log
1815 """add some manifest entry in to the manifest log
1816
1816
1817 input:
1817 input:
1818
1818
1819 m: the manifest dict we want to store
1819 m: the manifest dict we want to store
1820 transaction: the open transaction
1820 transaction: the open transaction
1821 p1: manifest-node of p1
1821 p1: manifest-node of p1
1822 p2: manifest-node of p2
1822 p2: manifest-node of p2
1823 added: file added/changed compared to parent
1823 added: file added/changed compared to parent
1824 removed: file removed compared to parent
1824 removed: file removed compared to parent
1825
1825
1826 tree manifest input:
1826 tree manifest input:
1827
1827
1828 readtree: a function to read a subtree
1828 readtree: a function to read a subtree
1829 match: a filematcher for the subpart of the tree manifest
1829 match: a filematcher for the subpart of the tree manifest
1830 """
1830 """
1831 try:
1831 try:
1832 if p1 not in self.fulltextcache:
1832 if p1 not in self.fulltextcache:
1833 raise FastdeltaUnavailable()
1833 raise FastdeltaUnavailable()
1834 # If our first parent is in the manifest cache, we can
1834 # If our first parent is in the manifest cache, we can
1835 # compute a delta here using properties we know about the
1835 # compute a delta here using properties we know about the
1836 # manifest up-front, which may save time later for the
1836 # manifest up-front, which may save time later for the
1837 # revlog layer.
1837 # revlog layer.
1838
1838
1839 _checkforbidden(added)
1839 _checkforbidden(added)
1840 # combine the changed lists into one sorted iterator
1840 # combine the changed lists into one sorted iterator
1841 work = heapq.merge(
1841 work = heapq.merge(
1842 [(x, False) for x in sorted(added)],
1842 [(x, False) for x in sorted(added)],
1843 [(x, True) for x in sorted(removed)],
1843 [(x, True) for x in sorted(removed)],
1844 )
1844 )
1845
1845
1846 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1846 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1847 cachedelta = self._revlog.rev(p1), deltatext
1847 cachedelta = self._revlog.rev(p1), deltatext
1848 text = util.buffer(arraytext)
1848 text = util.buffer(arraytext)
1849 rev = self._revlog.addrevision(
1849 rev = self._revlog.addrevision(
1850 text, transaction, link, p1, p2, cachedelta
1850 text, transaction, link, p1, p2, cachedelta
1851 )
1851 )
1852 n = self._revlog.node(rev)
1852 n = self._revlog.node(rev)
1853 except FastdeltaUnavailable:
1853 except FastdeltaUnavailable:
1854 # The first parent manifest isn't already loaded or the
1854 # The first parent manifest isn't already loaded or the
1855 # manifest implementation doesn't support fastdelta, so
1855 # manifest implementation doesn't support fastdelta, so
1856 # we'll just encode a fulltext of the manifest and pass
1856 # we'll just encode a fulltext of the manifest and pass
1857 # that through to the revlog layer, and let it handle the
1857 # that through to the revlog layer, and let it handle the
1858 # delta process.
1858 # delta process.
1859 if self._treeondisk:
1859 if self._treeondisk:
1860 assert readtree, b"readtree must be set for treemanifest writes"
1860 assert readtree, b"readtree must be set for treemanifest writes"
1861 assert match, b"match must be specified for treemanifest writes"
1861 assert match, b"match must be specified for treemanifest writes"
1862 m1 = readtree(self.tree, p1)
1862 m1 = readtree(self.tree, p1)
1863 m2 = readtree(self.tree, p2)
1863 m2 = readtree(self.tree, p2)
1864 n = self._addtree(
1864 n = self._addtree(
1865 m, transaction, link, m1, m2, readtree, match=match
1865 m, transaction, link, m1, m2, readtree, match=match
1866 )
1866 )
1867 arraytext = None
1867 arraytext = None
1868 else:
1868 else:
1869 text = m.text()
1869 text = m.text()
1870 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1870 rev = self._revlog.addrevision(text, transaction, link, p1, p2)
1871 n = self._revlog.node(rev)
1871 n = self._revlog.node(rev)
1872 arraytext = bytearray(text)
1872 arraytext = bytearray(text)
1873
1873
1874 if arraytext is not None:
1874 if arraytext is not None:
1875 self.fulltextcache[n] = arraytext
1875 self.fulltextcache[n] = arraytext
1876
1876
1877 return n
1877 return n
1878
1878
1879 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1879 def _addtree(self, m, transaction, link, m1, m2, readtree, match):
1880 # If the manifest is unchanged compared to one parent,
1880 # If the manifest is unchanged compared to one parent,
1881 # don't write a new revision
1881 # don't write a new revision
1882 if self.tree != b'' and (
1882 if self.tree != b'' and (
1883 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1883 m.unmodifiedsince(m1) or m.unmodifiedsince(m2)
1884 ):
1884 ):
1885 return m.node()
1885 return m.node()
1886
1886
1887 def writesubtree(subm, subp1, subp2, match):
1887 def writesubtree(subm, subp1, subp2, match):
1888 sublog = self.dirlog(subm.dir())
1888 sublog = self.dirlog(subm.dir())
1889 sublog.add(
1889 sublog.add(
1890 subm,
1890 subm,
1891 transaction,
1891 transaction,
1892 link,
1892 link,
1893 subp1,
1893 subp1,
1894 subp2,
1894 subp2,
1895 None,
1895 None,
1896 None,
1896 None,
1897 readtree=readtree,
1897 readtree=readtree,
1898 match=match,
1898 match=match,
1899 )
1899 )
1900
1900
1901 m.writesubtrees(m1, m2, writesubtree, match)
1901 m.writesubtrees(m1, m2, writesubtree, match)
1902 text = m.dirtext()
1902 text = m.dirtext()
1903 n = None
1903 n = None
1904 if self.tree != b'':
1904 if self.tree != b'':
1905 # Double-check whether contents are unchanged to one parent
1905 # Double-check whether contents are unchanged to one parent
1906 if text == m1.dirtext():
1906 if text == m1.dirtext():
1907 n = m1.node()
1907 n = m1.node()
1908 elif text == m2.dirtext():
1908 elif text == m2.dirtext():
1909 n = m2.node()
1909 n = m2.node()
1910
1910
1911 if not n:
1911 if not n:
1912 rev = self._revlog.addrevision(
1912 rev = self._revlog.addrevision(
1913 text, transaction, link, m1.node(), m2.node()
1913 text, transaction, link, m1.node(), m2.node()
1914 )
1914 )
1915 n = self._revlog.node(rev)
1915 n = self._revlog.node(rev)
1916
1916
1917 # Save nodeid so parent manifest can calculate its nodeid
1917 # Save nodeid so parent manifest can calculate its nodeid
1918 m.setnode(n)
1918 m.setnode(n)
1919 return n
1919 return n
1920
1920
1921 def __len__(self):
1921 def __len__(self):
1922 return len(self._revlog)
1922 return len(self._revlog)
1923
1923
1924 def __iter__(self):
1924 def __iter__(self):
1925 return self._revlog.__iter__()
1925 return self._revlog.__iter__()
1926
1926
1927 def rev(self, node):
1927 def rev(self, node):
1928 return self._revlog.rev(node)
1928 return self._revlog.rev(node)
1929
1929
1930 def node(self, rev):
1930 def node(self, rev):
1931 return self._revlog.node(rev)
1931 return self._revlog.node(rev)
1932
1932
1933 def lookup(self, value):
1933 def lookup(self, value):
1934 return self._revlog.lookup(value)
1934 return self._revlog.lookup(value)
1935
1935
1936 def parentrevs(self, rev):
1936 def parentrevs(self, rev):
1937 return self._revlog.parentrevs(rev)
1937 return self._revlog.parentrevs(rev)
1938
1938
1939 def parents(self, node):
1939 def parents(self, node):
1940 return self._revlog.parents(node)
1940 return self._revlog.parents(node)
1941
1941
1942 def linkrev(self, rev):
1942 def linkrev(self, rev):
1943 return self._revlog.linkrev(rev)
1943 return self._revlog.linkrev(rev)
1944
1944
1945 def checksize(self):
1945 def checksize(self):
1946 return self._revlog.checksize()
1946 return self._revlog.checksize()
1947
1947
1948 def revision(self, node):
1948 def revision(self, node):
1949 return self._revlog.revision(node)
1949 return self._revlog.revision(node)
1950
1950
1951 def rawdata(self, node):
1951 def rawdata(self, node):
1952 return self._revlog.rawdata(node)
1952 return self._revlog.rawdata(node)
1953
1953
1954 def revdiff(self, rev1, rev2):
1954 def revdiff(self, rev1, rev2):
1955 return self._revlog.revdiff(rev1, rev2)
1955 return self._revlog.revdiff(rev1, rev2)
1956
1956
1957 def cmp(self, node, text):
1957 def cmp(self, node, text):
1958 return self._revlog.cmp(node, text)
1958 return self._revlog.cmp(node, text)
1959
1959
1960 def deltaparent(self, rev):
1960 def deltaparent(self, rev):
1961 return self._revlog.deltaparent(rev)
1961 return self._revlog.deltaparent(rev)
1962
1962
1963 def emitrevisions(
1963 def emitrevisions(
1964 self,
1964 self,
1965 nodes,
1965 nodes,
1966 nodesorder=None,
1966 nodesorder=None,
1967 revisiondata=False,
1967 revisiondata=False,
1968 assumehaveparentrevisions=False,
1968 assumehaveparentrevisions=False,
1969 deltamode=repository.CG_DELTAMODE_STD,
1969 deltamode=repository.CG_DELTAMODE_STD,
1970 sidedata_helpers=None,
1970 sidedata_helpers=None,
1971 debug_info=None,
1971 debug_info=None,
1972 ):
1972 ):
1973 return self._revlog.emitrevisions(
1973 return self._revlog.emitrevisions(
1974 nodes,
1974 nodes,
1975 nodesorder=nodesorder,
1975 nodesorder=nodesorder,
1976 revisiondata=revisiondata,
1976 revisiondata=revisiondata,
1977 assumehaveparentrevisions=assumehaveparentrevisions,
1977 assumehaveparentrevisions=assumehaveparentrevisions,
1978 deltamode=deltamode,
1978 deltamode=deltamode,
1979 sidedata_helpers=sidedata_helpers,
1979 sidedata_helpers=sidedata_helpers,
1980 debug_info=debug_info,
1980 debug_info=debug_info,
1981 )
1981 )
1982
1982
1983 def addgroup(
1983 def addgroup(
1984 self,
1984 self,
1985 deltas,
1985 deltas,
1986 linkmapper,
1986 linkmapper,
1987 transaction,
1987 transaction,
1988 alwayscache=False,
1988 alwayscache=False,
1989 addrevisioncb=None,
1989 addrevisioncb=None,
1990 duplicaterevisioncb=None,
1990 duplicaterevisioncb=None,
1991 debug_info=None,
1991 debug_info=None,
1992 delta_base_reuse_policy=None,
1992 delta_base_reuse_policy=None,
1993 ):
1993 ):
1994 return self._revlog.addgroup(
1994 return self._revlog.addgroup(
1995 deltas,
1995 deltas,
1996 linkmapper,
1996 linkmapper,
1997 transaction,
1997 transaction,
1998 alwayscache=alwayscache,
1998 alwayscache=alwayscache,
1999 addrevisioncb=addrevisioncb,
1999 addrevisioncb=addrevisioncb,
2000 duplicaterevisioncb=duplicaterevisioncb,
2000 duplicaterevisioncb=duplicaterevisioncb,
2001 debug_info=debug_info,
2001 debug_info=debug_info,
2002 delta_base_reuse_policy=delta_base_reuse_policy,
2002 delta_base_reuse_policy=delta_base_reuse_policy,
2003 )
2003 )
2004
2004
2005 def rawsize(self, rev):
2005 def rawsize(self, rev):
2006 return self._revlog.rawsize(rev)
2006 return self._revlog.rawsize(rev)
2007
2007
2008 def getstrippoint(self, minlink):
2008 def getstrippoint(self, minlink):
2009 return self._revlog.getstrippoint(minlink)
2009 return self._revlog.getstrippoint(minlink)
2010
2010
2011 def strip(self, minlink, transaction):
2011 def strip(self, minlink, transaction):
2012 return self._revlog.strip(minlink, transaction)
2012 return self._revlog.strip(minlink, transaction)
2013
2013
2014 def files(self):
2014 def files(self):
2015 return self._revlog.files()
2015 return self._revlog.files()
2016
2016
2017 def clone(self, tr, destrevlog, **kwargs):
2017 def clone(self, tr, destrevlog, **kwargs):
2018 if not isinstance(destrevlog, manifestrevlog):
2018 if not isinstance(destrevlog, manifestrevlog):
2019 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
2019 raise error.ProgrammingError(b'expected manifestrevlog to clone()')
2020
2020
2021 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
2021 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
2022
2022
2023 def storageinfo(
2023 def storageinfo(
2024 self,
2024 self,
2025 exclusivefiles=False,
2025 exclusivefiles=False,
2026 sharedfiles=False,
2026 sharedfiles=False,
2027 revisionscount=False,
2027 revisionscount=False,
2028 trackedsize=False,
2028 trackedsize=False,
2029 storedsize=False,
2029 storedsize=False,
2030 ):
2030 ):
2031 return self._revlog.storageinfo(
2031 return self._revlog.storageinfo(
2032 exclusivefiles=exclusivefiles,
2032 exclusivefiles=exclusivefiles,
2033 sharedfiles=sharedfiles,
2033 sharedfiles=sharedfiles,
2034 revisionscount=revisionscount,
2034 revisionscount=revisionscount,
2035 trackedsize=trackedsize,
2035 trackedsize=trackedsize,
2036 storedsize=storedsize,
2036 storedsize=storedsize,
2037 )
2037 )
2038
2038
2039 @property
2039 @property
2040 def opener(self):
2040 def opener(self):
2041 return self._revlog.opener
2041 return self._revlog.opener
2042
2042
2043 @opener.setter
2043 @opener.setter
2044 def opener(self, value):
2044 def opener(self, value):
2045 self._revlog.opener = value
2045 self._revlog.opener = value
2046
2046
2047
2047
2048 AnyManifestCtx = Union['ManifestCtx', 'TreeManifestCtx']
2048 AnyManifestCtx = Union['ManifestCtx', 'TreeManifestCtx']
2049 # TODO: drop this in favor of repository.imanifestdict
2049 # TODO: drop this in favor of repository.imanifestdict
2050 AnyManifestDict = Union[manifestdict, treemanifest]
2050 AnyManifestDict = Union[manifestdict, treemanifest]
2051
2051
2052
2052
2053 class manifestlog: # (repository.imanifestlog)
2053 class manifestlog: # (repository.imanifestlog)
2054 """A collection class representing the collection of manifest snapshots
2054 """A collection class representing the collection of manifest snapshots
2055 referenced by commits in the repository.
2055 referenced by commits in the repository.
2056
2056
2057 In this situation, 'manifest' refers to the abstract concept of a snapshot
2057 In this situation, 'manifest' refers to the abstract concept of a snapshot
2058 of the list of files in the given commit. Consumers of the output of this
2058 of the list of files in the given commit. Consumers of the output of this
2059 class do not care about the implementation details of the actual manifests
2059 class do not care about the implementation details of the actual manifests
2060 they receive (i.e. tree or flat or lazily loaded, etc)."""
2060 they receive (i.e. tree or flat or lazily loaded, etc)."""
2061
2061
2062 def __init__(self, opener, repo, rootstore, narrowmatch):
2062 def __init__(self, opener, repo, rootstore, narrowmatch):
2063 self.nodeconstants = repo.nodeconstants
2063 self.nodeconstants = repo.nodeconstants
2064 usetreemanifest = False
2064 usetreemanifest = False
2065 cachesize = 4
2065 cachesize = 4
2066
2066
2067 opts = getattr(opener, 'options', None)
2067 opts = getattr(opener, 'options', None)
2068 if opts is not None:
2068 if opts is not None:
2069 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
2069 usetreemanifest = opts.get(b'treemanifest', usetreemanifest)
2070 cachesize = opts.get(b'manifestcachesize', cachesize)
2070 cachesize = opts.get(b'manifestcachesize', cachesize)
2071
2071
2072 self._treemanifests = usetreemanifest
2072 self._treemanifests = usetreemanifest
2073
2073
2074 self._rootstore = rootstore
2074 self._rootstore = rootstore
2075 self._rootstore._setupmanifestcachehooks(repo)
2075 self._rootstore._setupmanifestcachehooks(repo)
2076 self._narrowmatch = narrowmatch
2076 self._narrowmatch = narrowmatch
2077
2077
2078 # A cache of the manifestctx or treemanifestctx for each directory
2078 # A cache of the manifestctx or treemanifestctx for each directory
2079 self._dirmancache = {}
2079 self._dirmancache = {}
2080 self._dirmancache[b''] = util.lrucachedict(cachesize)
2080 self._dirmancache[b''] = util.lrucachedict(cachesize)
2081
2081
2082 self._cachesize = cachesize
2082 self._cachesize = cachesize
2083
2083
2084 def __getitem__(self, node):
2084 def __getitem__(self, node):
2085 """Retrieves the manifest instance for the given node. Throws a
2085 """Retrieves the manifest instance for the given node. Throws a
2086 LookupError if not found.
2086 LookupError if not found.
2087 """
2087 """
2088 return self.get(b'', node)
2088 return self.get(b'', node)
2089
2089
2090 @property
2090 @property
2091 def narrowed(self):
2091 def narrowed(self):
2092 return not (self._narrowmatch is None or self._narrowmatch.always())
2092 return not (self._narrowmatch is None or self._narrowmatch.always())
2093
2093
2094 def get(
2094 def get(
2095 self, tree: bytes, node: bytes, verify: bool = True
2095 self, tree: bytes, node: bytes, verify: bool = True
2096 ) -> AnyManifestCtx:
2096 ) -> AnyManifestCtx:
2097 """Retrieves the manifest instance for the given node. Throws a
2097 """Retrieves the manifest instance for the given node. Throws a
2098 LookupError if not found.
2098 LookupError if not found.
2099
2099
2100 `verify` - if True an exception will be thrown if the node is not in
2100 `verify` - if True an exception will be thrown if the node is not in
2101 the revlog
2101 the revlog
2102 """
2102 """
2103 if node in self._dirmancache.get(tree, ()):
2103 if node in self._dirmancache.get(tree, ()):
2104 return self._dirmancache[tree][node]
2104 return self._dirmancache[tree][node]
2105
2105
2106 if not self._narrowmatch.always():
2106 if not self._narrowmatch.always():
2107 if not self._narrowmatch.visitdir(tree[:-1]):
2107 if not self._narrowmatch.visitdir(tree[:-1]):
2108 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2108 return excludeddirmanifestctx(self.nodeconstants, tree, node)
2109 if tree:
2109 if tree:
2110 if self._rootstore._treeondisk:
2110 if self._rootstore._treeondisk:
2111 if verify:
2111 if verify:
2112 # Side-effect is LookupError is raised if node doesn't
2112 # Side-effect is LookupError is raised if node doesn't
2113 # exist.
2113 # exist.
2114 self.getstorage(tree).rev(node)
2114 self.getstorage(tree).rev(node)
2115
2115
2116 m = treemanifestctx(self, tree, node)
2116 m = treemanifestctx(self, tree, node)
2117 else:
2117 else:
2118 raise error.Abort(
2118 raise error.Abort(
2119 _(
2119 _(
2120 b"cannot ask for manifest directory '%s' in a flat "
2120 b"cannot ask for manifest directory '%s' in a flat "
2121 b"manifest"
2121 b"manifest"
2122 )
2122 )
2123 % tree
2123 % tree
2124 )
2124 )
2125 else:
2125 else:
2126 if verify:
2126 if verify:
2127 # Side-effect is LookupError is raised if node doesn't exist.
2127 # Side-effect is LookupError is raised if node doesn't exist.
2128 self._rootstore.rev(node)
2128 self._rootstore.rev(node)
2129
2129
2130 if self._treemanifests:
2130 if self._treemanifests:
2131 m = treemanifestctx(self, b'', node)
2131 m = treemanifestctx(self, b'', node)
2132 else:
2132 else:
2133 m = manifestctx(self, node)
2133 m = manifestctx(self, node)
2134
2134
2135 if node != self.nodeconstants.nullid:
2135 if node != self.nodeconstants.nullid:
2136 mancache = self._dirmancache.get(tree)
2136 mancache = self._dirmancache.get(tree)
2137 if not mancache:
2137 if not mancache:
2138 mancache = util.lrucachedict(self._cachesize)
2138 mancache = util.lrucachedict(self._cachesize)
2139 self._dirmancache[tree] = mancache
2139 self._dirmancache[tree] = mancache
2140 mancache[node] = m
2140 mancache[node] = m
2141 return m
2141 return m
2142
2142
2143 def getstorage(self, tree):
2143 def getstorage(self, tree):
2144 return self._rootstore.dirlog(tree)
2144 return self._rootstore.dirlog(tree)
2145
2145
2146 def clearcaches(self, clear_persisted_data: bool = False) -> None:
2146 def clearcaches(self, clear_persisted_data: bool = False) -> None:
2147 self._dirmancache.clear()
2147 self._dirmancache.clear()
2148 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2148 self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
2149
2149
2150 def rev(self, node) -> int:
2150 def rev(self, node) -> int:
2151 return self._rootstore.rev(node)
2151 return self._rootstore.rev(node)
2152
2152
2153 def update_caches(self, transaction) -> None:
2153 def update_caches(self, transaction) -> None:
2154 return self._rootstore._revlog.update_caches(transaction=transaction)
2154 return self._rootstore._revlog.update_caches(transaction=transaction)
2155
2155
2156
2156
2157 class MemManifestCtx:
2157 class memmanifestctx: # (repository.imanifestrevisionwritable)
2158 _manifestdict: manifestdict
2158 _manifestdict: manifestdict
2159
2159
2160 def __init__(self, manifestlog):
2160 def __init__(self, manifestlog):
2161 self._manifestlog = manifestlog
2161 self._manifestlog = manifestlog
2162 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2162 self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
2163
2163
2164 def _storage(self) -> manifestrevlog:
2164 def _storage(self) -> manifestrevlog:
2165 return self._manifestlog.getstorage(b'')
2165 return self._manifestlog.getstorage(b'')
2166
2166
2167 def copy(self) -> 'MemManifestCtx':
2167 def copy(self) -> 'memmanifestctx':
2168 memmf = memmanifestctx(self._manifestlog)
2168 memmf = memmanifestctx(self._manifestlog)
2169 memmf._manifestdict = self.read().copy()
2169 memmf._manifestdict = self.read().copy()
2170 return memmf
2170 return memmf
2171
2171
2172 def read(self) -> 'manifestdict':
2172 def read(self) -> 'manifestdict':
2173 return self._manifestdict
2173 return self._manifestdict
2174
2174
2175 def write(self, transaction, link, p1, p2, added, removed, match=None):
2175 def write(self, transaction, link, p1, p2, added, removed, match=None):
2176 return self._storage().add(
2176 return self._storage().add(
2177 self._manifestdict,
2177 self._manifestdict,
2178 transaction,
2178 transaction,
2179 link,
2179 link,
2180 p1,
2180 p1,
2181 p2,
2181 p2,
2182 added,
2182 added,
2183 removed,
2183 removed,
2184 match=match,
2184 match=match,
2185 )
2185 )
2186
2186
2187
2187
2188 memmanifestctx = interfaceutil.implementer(
2189 repository.imanifestrevisionwritable
2190 )(MemManifestCtx)
2191
2192 if typing.TYPE_CHECKING:
2193 memmanifestctx = MemManifestCtx
2194
2195
2196 class ManifestCtx:
2188 class ManifestCtx:
2197 """A class representing a single revision of a manifest, including its
2189 """A class representing a single revision of a manifest, including its
2198 contents, its parent revs, and its linkrev.
2190 contents, its parent revs, and its linkrev.
2199 """
2191 """
2200
2192
2201 _data: Optional[manifestdict]
2193 _data: Optional[manifestdict]
2202
2194
2203 def __init__(self, manifestlog, node):
2195 def __init__(self, manifestlog, node):
2204 self._manifestlog = manifestlog
2196 self._manifestlog = manifestlog
2205 self._data = None
2197 self._data = None
2206
2198
2207 self._node = node
2199 self._node = node
2208
2200
2209 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2201 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
2210 # but let's add it later when something needs it and we can load it
2202 # but let's add it later when something needs it and we can load it
2211 # lazily.
2203 # lazily.
2212 # self.p1, self.p2 = store.parents(node)
2204 # self.p1, self.p2 = store.parents(node)
2213 # rev = store.rev(node)
2205 # rev = store.rev(node)
2214 # self.linkrev = store.linkrev(rev)
2206 # self.linkrev = store.linkrev(rev)
2215
2207
2216 def _storage(self) -> 'manifestrevlog':
2208 def _storage(self) -> 'manifestrevlog':
2217 return self._manifestlog.getstorage(b'')
2209 return self._manifestlog.getstorage(b'')
2218
2210
2219 def node(self) -> bytes:
2211 def node(self) -> bytes:
2220 return self._node
2212 return self._node
2221
2213
2222 def copy(self) -> MemManifestCtx:
2214 def copy(self) -> memmanifestctx:
2223 memmf = memmanifestctx(self._manifestlog)
2215 memmf = memmanifestctx(self._manifestlog)
2224 memmf._manifestdict = self.read().copy()
2216 memmf._manifestdict = self.read().copy()
2225 return memmf
2217 return memmf
2226
2218
2227 @propertycache
2219 @propertycache
2228 def parents(self) -> Tuple[bytes, bytes]:
2220 def parents(self) -> Tuple[bytes, bytes]:
2229 return self._storage().parents(self._node)
2221 return self._storage().parents(self._node)
2230
2222
2231 def read(self) -> 'manifestdict':
2223 def read(self) -> 'manifestdict':
2232 if self._data is None:
2224 if self._data is None:
2233 nc = self._manifestlog.nodeconstants
2225 nc = self._manifestlog.nodeconstants
2234 if self._node == nc.nullid:
2226 if self._node == nc.nullid:
2235 self._data = manifestdict(nc.nodelen)
2227 self._data = manifestdict(nc.nodelen)
2236 else:
2228 else:
2237 store = self._storage()
2229 store = self._storage()
2238 if self._node in store.fulltextcache:
2230 if self._node in store.fulltextcache:
2239 text = pycompat.bytestr(store.fulltextcache[self._node])
2231 text = pycompat.bytestr(store.fulltextcache[self._node])
2240 else:
2232 else:
2241 text = store.revision(self._node)
2233 text = store.revision(self._node)
2242 arraytext = bytearray(text)
2234 arraytext = bytearray(text)
2243 store.fulltextcache[self._node] = arraytext
2235 store.fulltextcache[self._node] = arraytext
2244 self._data = manifestdict(nc.nodelen, text)
2236 self._data = manifestdict(nc.nodelen, text)
2245 return self._data
2237 return self._data
2246
2238
2247 def readfast(self, shallow: bool = False) -> 'manifestdict':
2239 def readfast(self, shallow: bool = False) -> 'manifestdict':
2248 """Calls either readdelta or read, based on which would be less work.
2240 """Calls either readdelta or read, based on which would be less work.
2249 readdelta is called if the delta is against the p1, and therefore can be
2241 readdelta is called if the delta is against the p1, and therefore can be
2250 read quickly.
2242 read quickly.
2251
2243
2252 If `shallow` is True, nothing changes since this is a flat manifest.
2244 If `shallow` is True, nothing changes since this is a flat manifest.
2253 """
2245 """
2254 util.nouideprecwarn(
2246 util.nouideprecwarn(
2255 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2247 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2256 b"6.9",
2248 b"6.9",
2257 stacklevel=2,
2249 stacklevel=2,
2258 )
2250 )
2259 store = self._storage()
2251 store = self._storage()
2260 r = store.rev(self._node)
2252 r = store.rev(self._node)
2261 deltaparent = store.deltaparent(r)
2253 deltaparent = store.deltaparent(r)
2262 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2254 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2263 return self.readdelta()
2255 return self.readdelta()
2264 return self.read()
2256 return self.read()
2265
2257
2266 def readdelta(self, shallow: bool = False) -> 'manifestdict':
2258 def readdelta(self, shallow: bool = False) -> 'manifestdict':
2267 """Returns a manifest containing just the entries that are present
2259 """Returns a manifest containing just the entries that are present
2268 in this manifest, but not in its p1 manifest. This is efficient to read
2260 in this manifest, but not in its p1 manifest. This is efficient to read
2269 if the revlog delta is already p1.
2261 if the revlog delta is already p1.
2270
2262
2271 Changing the value of `shallow` has no effect on flat manifests.
2263 Changing the value of `shallow` has no effect on flat manifests.
2272 """
2264 """
2273 util.nouideprecwarn(
2265 util.nouideprecwarn(
2274 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2266 b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2275 b"6.9",
2267 b"6.9",
2276 stacklevel=2,
2268 stacklevel=2,
2277 )
2269 )
2278 store = self._storage()
2270 store = self._storage()
2279 r = store.rev(self._node)
2271 r = store.rev(self._node)
2280 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2272 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2281 return manifestdict(store.nodeconstants.nodelen, d)
2273 return manifestdict(store.nodeconstants.nodelen, d)
2282
2274
2283 def read_any_fast_delta(
2275 def read_any_fast_delta(
2284 self,
2276 self,
2285 valid_bases: Optional[Collection[int]] = None,
2277 valid_bases: Optional[Collection[int]] = None,
2286 *,
2278 *,
2287 shallow: bool = False,
2279 shallow: bool = False,
2288 ) -> Tuple[Optional[int], manifestdict]:
2280 ) -> Tuple[Optional[int], manifestdict]:
2289 """see `imanifestrevisionstored` documentation"""
2281 """see `imanifestrevisionstored` documentation"""
2290 store = self._storage()
2282 store = self._storage()
2291 r = store.rev(self._node)
2283 r = store.rev(self._node)
2292 deltaparent = store.deltaparent(r)
2284 deltaparent = store.deltaparent(r)
2293 if valid_bases is None:
2285 if valid_bases is None:
2294 # make sure the next check is True
2286 # make sure the next check is True
2295 valid_bases = (deltaparent,)
2287 valid_bases = (deltaparent,)
2296 if deltaparent != nullrev and deltaparent in valid_bases:
2288 if deltaparent != nullrev and deltaparent in valid_bases:
2297 d = mdiff.patchtext(store.revdiff(deltaparent, r))
2289 d = mdiff.patchtext(store.revdiff(deltaparent, r))
2298 return (
2290 return (
2299 deltaparent,
2291 deltaparent,
2300 manifestdict(store.nodeconstants.nodelen, d),
2292 manifestdict(store.nodeconstants.nodelen, d),
2301 )
2293 )
2302 return (None, self.read())
2294 return (None, self.read())
2303
2295
2304 def read_delta_parents(
2296 def read_delta_parents(
2305 self,
2297 self,
2306 *,
2298 *,
2307 shallow: bool = False,
2299 shallow: bool = False,
2308 exact: bool = True,
2300 exact: bool = True,
2309 ) -> manifestdict:
2301 ) -> manifestdict:
2310 """see `interface.imanifestrevisionbase` documentations"""
2302 """see `interface.imanifestrevisionbase` documentations"""
2311 store = self._storage()
2303 store = self._storage()
2312 r = store.rev(self._node)
2304 r = store.rev(self._node)
2313 deltaparent = store.deltaparent(r)
2305 deltaparent = store.deltaparent(r)
2314 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2306 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2315 if not exact and deltaparent in parents:
2307 if not exact and deltaparent in parents:
2316 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2308 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2317 return manifestdict(store.nodeconstants.nodelen, d)
2309 return manifestdict(store.nodeconstants.nodelen, d)
2318 elif not exact or len(parents) == 0:
2310 elif not exact or len(parents) == 0:
2319 return self.read()
2311 return self.read()
2320 elif len(parents) == 1:
2312 elif len(parents) == 1:
2321 p = parents[0]
2313 p = parents[0]
2322 d = mdiff.patchtext(store.revdiff(p, r))
2314 d = mdiff.patchtext(store.revdiff(p, r))
2323 return manifestdict(store.nodeconstants.nodelen, d)
2315 return manifestdict(store.nodeconstants.nodelen, d)
2324 else:
2316 else:
2325 p1, p2 = parents
2317 p1, p2 = parents
2326 d1 = mdiff.patchtext(store.revdiff(p1, r))
2318 d1 = mdiff.patchtext(store.revdiff(p1, r))
2327 d2 = mdiff.patchtext(store.revdiff(p2, r))
2319 d2 = mdiff.patchtext(store.revdiff(p2, r))
2328 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2320 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2329 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2321 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2330 md = manifestdict(store.nodeconstants.nodelen)
2322 md = manifestdict(store.nodeconstants.nodelen)
2331 for f, new_node, new_flag in d1.iterentries():
2323 for f, new_node, new_flag in d1.iterentries():
2332 if f not in d2:
2324 if f not in d2:
2333 continue
2325 continue
2334 if new_node is not None:
2326 if new_node is not None:
2335 md.set(f, new_node, new_flag)
2327 md.set(f, new_node, new_flag)
2336 return md
2328 return md
2337
2329
2338 def read_delta_new_entries(self, *, shallow=False) -> manifestdict:
2330 def read_delta_new_entries(self, *, shallow=False) -> manifestdict:
2339 """see `interface.imanifestrevisionbase` documentations"""
2331 """see `interface.imanifestrevisionbase` documentations"""
2340 # If we are using narrow, returning a delta against an arbitrary
2332 # If we are using narrow, returning a delta against an arbitrary
2341 # changeset might return file outside the narrowspec. This can create
2333 # changeset might return file outside the narrowspec. This can create
2342 # issue when running validation server side with strict security as
2334 # issue when running validation server side with strict security as
2343 # push from low priviledge usage might be seen as adding new revision
2335 # push from low priviledge usage might be seen as adding new revision
2344 # for files they cannot touch. So we are strict if narrow is involved.
2336 # for files they cannot touch. So we are strict if narrow is involved.
2345 if self._manifestlog.narrowed:
2337 if self._manifestlog.narrowed:
2346 return self.read_delta_parents(shallow=shallow, exact=True)
2338 return self.read_delta_parents(shallow=shallow, exact=True)
2347 store = self._storage()
2339 store = self._storage()
2348 r = store.rev(self._node)
2340 r = store.rev(self._node)
2349 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2341 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2350 return manifestdict(store.nodeconstants.nodelen, d)
2342 return manifestdict(store.nodeconstants.nodelen, d)
2351
2343
2352 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2344 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2353 return self.read().find(key)
2345 return self.read().find(key)
2354
2346
2355
2347
2356 manifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2348 manifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2357 ManifestCtx
2349 ManifestCtx
2358 )
2350 )
2359
2351
2360 if typing.TYPE_CHECKING:
2352 if typing.TYPE_CHECKING:
2361 manifestctx = ManifestCtx
2353 manifestctx = ManifestCtx
2362
2354
2363
2355
2364 class MemTreeManifestCtx:
2356 class MemTreeManifestCtx:
2365 _treemanifest: treemanifest
2357 _treemanifest: treemanifest
2366
2358
2367 def __init__(self, manifestlog, dir=b''):
2359 def __init__(self, manifestlog, dir=b''):
2368 self._manifestlog = manifestlog
2360 self._manifestlog = manifestlog
2369 self._dir = dir
2361 self._dir = dir
2370 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2362 self._treemanifest = treemanifest(manifestlog.nodeconstants)
2371
2363
2372 def _storage(self) -> manifestrevlog:
2364 def _storage(self) -> manifestrevlog:
2373 return self._manifestlog.getstorage(b'')
2365 return self._manifestlog.getstorage(b'')
2374
2366
2375 def copy(self) -> 'MemTreeManifestCtx':
2367 def copy(self) -> 'MemTreeManifestCtx':
2376 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2368 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2377 memmf._treemanifest = self._treemanifest.copy()
2369 memmf._treemanifest = self._treemanifest.copy()
2378 return memmf
2370 return memmf
2379
2371
2380 def read(self) -> 'treemanifest':
2372 def read(self) -> 'treemanifest':
2381 return self._treemanifest
2373 return self._treemanifest
2382
2374
2383 def write(self, transaction, link, p1, p2, added, removed, match=None):
2375 def write(self, transaction, link, p1, p2, added, removed, match=None):
2384 def readtree(dir, node):
2376 def readtree(dir, node):
2385 return self._manifestlog.get(dir, node).read()
2377 return self._manifestlog.get(dir, node).read()
2386
2378
2387 return self._storage().add(
2379 return self._storage().add(
2388 self._treemanifest,
2380 self._treemanifest,
2389 transaction,
2381 transaction,
2390 link,
2382 link,
2391 p1,
2383 p1,
2392 p2,
2384 p2,
2393 added,
2385 added,
2394 removed,
2386 removed,
2395 readtree=readtree,
2387 readtree=readtree,
2396 match=match,
2388 match=match,
2397 )
2389 )
2398
2390
2399
2391
2400 memtreemanifestctx = interfaceutil.implementer(
2392 memtreemanifestctx = interfaceutil.implementer(
2401 repository.imanifestrevisionwritable
2393 repository.imanifestrevisionwritable
2402 )(MemTreeManifestCtx)
2394 )(MemTreeManifestCtx)
2403
2395
2404 if typing.TYPE_CHECKING:
2396 if typing.TYPE_CHECKING:
2405 memtreemanifestctx = MemTreeManifestCtx
2397 memtreemanifestctx = MemTreeManifestCtx
2406
2398
2407
2399
2408 class TreeManifestCtx:
2400 class TreeManifestCtx:
2409 _data: Optional[treemanifest]
2401 _data: Optional[treemanifest]
2410
2402
2411 def __init__(self, manifestlog, dir, node):
2403 def __init__(self, manifestlog, dir, node):
2412 self._manifestlog = manifestlog
2404 self._manifestlog = manifestlog
2413 self._dir = dir
2405 self._dir = dir
2414 self._data = None
2406 self._data = None
2415
2407
2416 self._node = node
2408 self._node = node
2417
2409
2418 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2410 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
2419 # we can instantiate treemanifestctx objects for directories we don't
2411 # we can instantiate treemanifestctx objects for directories we don't
2420 # have on disk.
2412 # have on disk.
2421 # self.p1, self.p2 = store.parents(node)
2413 # self.p1, self.p2 = store.parents(node)
2422 # rev = store.rev(node)
2414 # rev = store.rev(node)
2423 # self.linkrev = store.linkrev(rev)
2415 # self.linkrev = store.linkrev(rev)
2424
2416
2425 def _storage(self) -> manifestrevlog:
2417 def _storage(self) -> manifestrevlog:
2426 narrowmatch = self._manifestlog._narrowmatch
2418 narrowmatch = self._manifestlog._narrowmatch
2427 if not narrowmatch.always():
2419 if not narrowmatch.always():
2428 if not narrowmatch.visitdir(self._dir[:-1]):
2420 if not narrowmatch.visitdir(self._dir[:-1]):
2429 return excludedmanifestrevlog(
2421 return excludedmanifestrevlog(
2430 self._manifestlog.nodeconstants, self._dir
2422 self._manifestlog.nodeconstants, self._dir
2431 )
2423 )
2432 return self._manifestlog.getstorage(self._dir)
2424 return self._manifestlog.getstorage(self._dir)
2433
2425
2434 def read(self) -> 'treemanifest':
2426 def read(self) -> 'treemanifest':
2435 if self._data is None:
2427 if self._data is None:
2436 store = self._storage()
2428 store = self._storage()
2437 if self._node == self._manifestlog.nodeconstants.nullid:
2429 if self._node == self._manifestlog.nodeconstants.nullid:
2438 self._data = treemanifest(self._manifestlog.nodeconstants)
2430 self._data = treemanifest(self._manifestlog.nodeconstants)
2439 # TODO accessing non-public API
2431 # TODO accessing non-public API
2440 elif store._treeondisk:
2432 elif store._treeondisk:
2441 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2433 m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2442
2434
2443 def gettext():
2435 def gettext():
2444 return store.revision(self._node)
2436 return store.revision(self._node)
2445
2437
2446 def readsubtree(dir, subm):
2438 def readsubtree(dir, subm):
2447 # Set verify to False since we need to be able to create
2439 # Set verify to False since we need to be able to create
2448 # subtrees for trees that don't exist on disk.
2440 # subtrees for trees that don't exist on disk.
2449 return self._manifestlog.get(dir, subm, verify=False).read()
2441 return self._manifestlog.get(dir, subm, verify=False).read()
2450
2442
2451 m.read(gettext, readsubtree)
2443 m.read(gettext, readsubtree)
2452 m.setnode(self._node)
2444 m.setnode(self._node)
2453 self._data = m
2445 self._data = m
2454 else:
2446 else:
2455 if self._node in store.fulltextcache:
2447 if self._node in store.fulltextcache:
2456 text = pycompat.bytestr(store.fulltextcache[self._node])
2448 text = pycompat.bytestr(store.fulltextcache[self._node])
2457 else:
2449 else:
2458 text = store.revision(self._node)
2450 text = store.revision(self._node)
2459 arraytext = bytearray(text)
2451 arraytext = bytearray(text)
2460 store.fulltextcache[self._node] = arraytext
2452 store.fulltextcache[self._node] = arraytext
2461 self._data = treemanifest(
2453 self._data = treemanifest(
2462 self._manifestlog.nodeconstants, dir=self._dir, text=text
2454 self._manifestlog.nodeconstants, dir=self._dir, text=text
2463 )
2455 )
2464
2456
2465 return self._data
2457 return self._data
2466
2458
2467 def node(self) -> bytes:
2459 def node(self) -> bytes:
2468 return self._node
2460 return self._node
2469
2461
2470 def copy(self) -> 'MemTreeManifestCtx':
2462 def copy(self) -> 'MemTreeManifestCtx':
2471 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2463 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
2472 memmf._treemanifest = self.read().copy()
2464 memmf._treemanifest = self.read().copy()
2473 return memmf
2465 return memmf
2474
2466
2475 @propertycache
2467 @propertycache
2476 def parents(self) -> Tuple[bytes, bytes]:
2468 def parents(self) -> Tuple[bytes, bytes]:
2477 return self._storage().parents(self._node)
2469 return self._storage().parents(self._node)
2478
2470
2479 def readdelta(self, shallow: bool = False) -> AnyManifestDict:
2471 def readdelta(self, shallow: bool = False) -> AnyManifestDict:
2480 """see `imanifestrevisionstored` documentation"""
2472 """see `imanifestrevisionstored` documentation"""
2481 util.nouideprecwarn(
2473 util.nouideprecwarn(
2482 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2474 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"',
2483 b"6.9",
2475 b"6.9",
2484 stacklevel=2,
2476 stacklevel=2,
2485 )
2477 )
2486 store = self._storage()
2478 store = self._storage()
2487 if shallow:
2479 if shallow:
2488 r = store.rev(self._node)
2480 r = store.rev(self._node)
2489 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2481 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2490 return manifestdict(store.nodeconstants.nodelen, d)
2482 return manifestdict(store.nodeconstants.nodelen, d)
2491 else:
2483 else:
2492 # Need to perform a slow delta
2484 # Need to perform a slow delta
2493 r0 = store.deltaparent(store.rev(self._node))
2485 r0 = store.deltaparent(store.rev(self._node))
2494 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2486 m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
2495 m1 = self.read()
2487 m1 = self.read()
2496 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2488 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2497 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2489 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2498 if n1:
2490 if n1:
2499 md[f] = n1
2491 md[f] = n1
2500 if fl1:
2492 if fl1:
2501 md.setflag(f, fl1)
2493 md.setflag(f, fl1)
2502 return md
2494 return md
2503
2495
2504 def read_any_fast_delta(
2496 def read_any_fast_delta(
2505 self,
2497 self,
2506 valid_bases: Optional[Collection[int]] = None,
2498 valid_bases: Optional[Collection[int]] = None,
2507 *,
2499 *,
2508 shallow: bool = False,
2500 shallow: bool = False,
2509 ) -> Tuple[Optional[int], AnyManifestDict]:
2501 ) -> Tuple[Optional[int], AnyManifestDict]:
2510 """see `imanifestrevisionstored` documentation"""
2502 """see `imanifestrevisionstored` documentation"""
2511 store = self._storage()
2503 store = self._storage()
2512 r = store.rev(self._node)
2504 r = store.rev(self._node)
2513 deltaparent = store.deltaparent(r)
2505 deltaparent = store.deltaparent(r)
2514
2506
2515 if valid_bases is None:
2507 if valid_bases is None:
2516 # make sure the next check is True
2508 # make sure the next check is True
2517 valid_bases = (deltaparent,)
2509 valid_bases = (deltaparent,)
2518 can_use_delta = deltaparent != nullrev and deltaparent in valid_bases
2510 can_use_delta = deltaparent != nullrev and deltaparent in valid_bases
2519
2511
2520 if shallow:
2512 if shallow:
2521 if can_use_delta:
2513 if can_use_delta:
2522 return (deltaparent, self._read_storage_delta_shallow())
2514 return (deltaparent, self._read_storage_delta_shallow())
2523 else:
2515 else:
2524 d = store.revision(self._node)
2516 d = store.revision(self._node)
2525 return (None, manifestdict(store.nodeconstants.nodelen, d))
2517 return (None, manifestdict(store.nodeconstants.nodelen, d))
2526 else:
2518 else:
2527 # note: This use "slow_delta" here is cargo culted from the previous
2519 # note: This use "slow_delta" here is cargo culted from the previous
2528 # implementation. I am not sure it make sense since the goal here is to
2520 # implementation. I am not sure it make sense since the goal here is to
2529 # be fast, so why are we computing a delta? On the other hand, tree
2521 # be fast, so why are we computing a delta? On the other hand, tree
2530 # manifest delta as fairly "cheap" and allow for skipping whole part of
2522 # manifest delta as fairly "cheap" and allow for skipping whole part of
2531 # the tree that a full read would access. So it might be a good idea.
2523 # the tree that a full read would access. So it might be a good idea.
2532 #
2524 #
2533 # If we realize we don't need delta here, we should simply use:
2525 # If we realize we don't need delta here, we should simply use:
2534 #
2526 #
2535 # return (None, self.read())
2527 # return (None, self.read())
2536 if can_use_delta:
2528 if can_use_delta:
2537 return (None, self._read_storage_slow_delta(base=deltaparent))
2529 return (None, self._read_storage_slow_delta(base=deltaparent))
2538 else:
2530 else:
2539 parents = [
2531 parents = [
2540 p
2532 p
2541 for p in store.parentrevs(r)
2533 for p in store.parentrevs(r)
2542 if p is not nullrev and p in valid_bases
2534 if p is not nullrev and p in valid_bases
2543 ]
2535 ]
2544 if parents:
2536 if parents:
2545 best_base = max(parents)
2537 best_base = max(parents)
2546 else:
2538 else:
2547 best_base = max(valid_bases)
2539 best_base = max(valid_bases)
2548 return (None, self._read_storage_slow_delta(base=best_base))
2540 return (None, self._read_storage_slow_delta(base=best_base))
2549
2541
2550 def _read_storage_delta_shallow(self) -> manifestdict:
2542 def _read_storage_delta_shallow(self) -> manifestdict:
2551 store = self._storage()
2543 store = self._storage()
2552 r = store.rev(self._node)
2544 r = store.rev(self._node)
2553 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2545 d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
2554 return manifestdict(store.nodeconstants.nodelen, d)
2546 return manifestdict(store.nodeconstants.nodelen, d)
2555
2547
2556 def _read_storage_slow_delta(self, base) -> 'treemanifest':
2548 def _read_storage_slow_delta(self, base) -> 'treemanifest':
2557 store = self._storage()
2549 store = self._storage()
2558 if base is None:
2550 if base is None:
2559 base = store.deltaparent(store.rev(self._node))
2551 base = store.deltaparent(store.rev(self._node))
2560 m0 = self._manifestlog.get(self._dir, store.node(base)).read()
2552 m0 = self._manifestlog.get(self._dir, store.node(base)).read()
2561 m1 = self.read()
2553 m1 = self.read()
2562 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2554 md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
2563 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2555 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
2564 if n1:
2556 if n1:
2565 md[f] = n1
2557 md[f] = n1
2566 if fl1:
2558 if fl1:
2567 md.setflag(f, fl1)
2559 md.setflag(f, fl1)
2568 return md
2560 return md
2569
2561
2570 def read_delta_parents(
2562 def read_delta_parents(
2571 self,
2563 self,
2572 *,
2564 *,
2573 shallow: bool = False,
2565 shallow: bool = False,
2574 exact: bool = True,
2566 exact: bool = True,
2575 ) -> AnyManifestDict:
2567 ) -> AnyManifestDict:
2576 """see `interface.imanifestrevisionbase` documentations"""
2568 """see `interface.imanifestrevisionbase` documentations"""
2577 store = self._storage()
2569 store = self._storage()
2578 r = store.rev(self._node)
2570 r = store.rev(self._node)
2579 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2571 parents = [p for p in store.parentrevs(r) if p is not nullrev]
2580 if not exact:
2572 if not exact:
2581 return self.read_any_fast_delta(parents, shallow=shallow)[1]
2573 return self.read_any_fast_delta(parents, shallow=shallow)[1]
2582 elif len(parents) == 0:
2574 elif len(parents) == 0:
2583 if shallow:
2575 if shallow:
2584 d = store.revision(self._node)
2576 d = store.revision(self._node)
2585 return manifestdict(store.nodeconstants.nodelen, d)
2577 return manifestdict(store.nodeconstants.nodelen, d)
2586 else:
2578 else:
2587 return self.read()
2579 return self.read()
2588 elif len(parents) == 1:
2580 elif len(parents) == 1:
2589 p = parents[0]
2581 p = parents[0]
2590 if shallow:
2582 if shallow:
2591 d = mdiff.patchtext(store.revdiff(p, r))
2583 d = mdiff.patchtext(store.revdiff(p, r))
2592 return manifestdict(store.nodeconstants.nodelen, d)
2584 return manifestdict(store.nodeconstants.nodelen, d)
2593 else:
2585 else:
2594 return self._read_storage_slow_delta(base=p)
2586 return self._read_storage_slow_delta(base=p)
2595 else:
2587 else:
2596 p1, p2 = parents
2588 p1, p2 = parents
2597 if shallow:
2589 if shallow:
2598 d1 = mdiff.patchtext(store.revdiff(p1, r))
2590 d1 = mdiff.patchtext(store.revdiff(p1, r))
2599 d2 = mdiff.patchtext(store.revdiff(p2, r))
2591 d2 = mdiff.patchtext(store.revdiff(p2, r))
2600 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2592 d1 = manifestdict(store.nodeconstants.nodelen, d1)
2601 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2593 d2 = manifestdict(store.nodeconstants.nodelen, d2)
2602 md = manifestdict(store.nodeconstants.nodelen)
2594 md = manifestdict(store.nodeconstants.nodelen)
2603 for f, new_node, new_flag in d1.iterentries():
2595 for f, new_node, new_flag in d1.iterentries():
2604 if f not in d2:
2596 if f not in d2:
2605 continue
2597 continue
2606 if new_node is not None:
2598 if new_node is not None:
2607 md.set(f, new_node, new_flag)
2599 md.set(f, new_node, new_flag)
2608 return md
2600 return md
2609 else:
2601 else:
2610 m1 = self._manifestlog.get(self._dir, store.node(p1)).read()
2602 m1 = self._manifestlog.get(self._dir, store.node(p1)).read()
2611 m2 = self._manifestlog.get(self._dir, store.node(p2)).read()
2603 m2 = self._manifestlog.get(self._dir, store.node(p2)).read()
2612 mc = self.read()
2604 mc = self.read()
2613 d1 = m1.diff(mc)
2605 d1 = m1.diff(mc)
2614 d2 = m2.diff(mc)
2606 d2 = m2.diff(mc)
2615 md = treemanifest(
2607 md = treemanifest(
2616 self._manifestlog.nodeconstants,
2608 self._manifestlog.nodeconstants,
2617 dir=self._dir,
2609 dir=self._dir,
2618 )
2610 )
2619 for f, new_node, new_flag in d1.iterentries():
2611 for f, new_node, new_flag in d1.iterentries():
2620 if f not in d2:
2612 if f not in d2:
2621 continue
2613 continue
2622 if new_node is not None:
2614 if new_node is not None:
2623 md.set(f, new_node, new_flag)
2615 md.set(f, new_node, new_flag)
2624 return md
2616 return md
2625
2617
2626 def read_delta_new_entries(
2618 def read_delta_new_entries(
2627 self, *, shallow: bool = False
2619 self, *, shallow: bool = False
2628 ) -> AnyManifestDict:
2620 ) -> AnyManifestDict:
2629 """see `interface.imanifestrevisionbase` documentations"""
2621 """see `interface.imanifestrevisionbase` documentations"""
2630 # If we are using narrow, returning a delta against an arbitrary
2622 # If we are using narrow, returning a delta against an arbitrary
2631 # changeset might return file outside the narrowspec. This can create
2623 # changeset might return file outside the narrowspec. This can create
2632 # issue when running validation server side with strict security as
2624 # issue when running validation server side with strict security as
2633 # push from low priviledge usage might be seen as adding new revision
2625 # push from low priviledge usage might be seen as adding new revision
2634 # for files they cannot touch. So we are strict if narrow is involved.
2626 # for files they cannot touch. So we are strict if narrow is involved.
2635 if self._manifestlog.narrowed:
2627 if self._manifestlog.narrowed:
2636 return self.read_delta_parents(shallow=shallow, exact=True)
2628 return self.read_delta_parents(shallow=shallow, exact=True)
2637 # delegate to existing another existing method for simplicity
2629 # delegate to existing another existing method for simplicity
2638 store = self._storage()
2630 store = self._storage()
2639 r = store.rev(self._node)
2631 r = store.rev(self._node)
2640 bases = (store.deltaparent(r),)
2632 bases = (store.deltaparent(r),)
2641 return self.read_any_fast_delta(bases, shallow=shallow)[1]
2633 return self.read_any_fast_delta(bases, shallow=shallow)[1]
2642
2634
2643 def readfast(self, shallow=False) -> AnyManifestDict:
2635 def readfast(self, shallow=False) -> AnyManifestDict:
2644 """Calls either readdelta or read, based on which would be less work.
2636 """Calls either readdelta or read, based on which would be less work.
2645 readdelta is called if the delta is against the p1, and therefore can be
2637 readdelta is called if the delta is against the p1, and therefore can be
2646 read quickly.
2638 read quickly.
2647
2639
2648 If `shallow` is True, it only returns the entries from this manifest,
2640 If `shallow` is True, it only returns the entries from this manifest,
2649 and not any submanifests.
2641 and not any submanifests.
2650 """
2642 """
2651 util.nouideprecwarn(
2643 util.nouideprecwarn(
2652 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2644 b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_parents"',
2653 b"6.9",
2645 b"6.9",
2654 stacklevel=2,
2646 stacklevel=2,
2655 )
2647 )
2656 store = self._storage()
2648 store = self._storage()
2657 r = store.rev(self._node)
2649 r = store.rev(self._node)
2658 deltaparent = store.deltaparent(r)
2650 deltaparent = store.deltaparent(r)
2659 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2651 if deltaparent != nullrev and deltaparent in store.parentrevs(r):
2660 return self.readdelta(shallow=shallow)
2652 return self.readdelta(shallow=shallow)
2661
2653
2662 if shallow:
2654 if shallow:
2663 return manifestdict(
2655 return manifestdict(
2664 store.nodeconstants.nodelen, store.revision(self._node)
2656 store.nodeconstants.nodelen, store.revision(self._node)
2665 )
2657 )
2666 else:
2658 else:
2667 return self.read()
2659 return self.read()
2668
2660
2669 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2661 def find(self, key: bytes) -> Tuple[bytes, bytes]:
2670 return self.read().find(key)
2662 return self.read().find(key)
2671
2663
2672
2664
2673 treemanifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2665 treemanifestctx = interfaceutil.implementer(repository.imanifestrevisionstored)(
2674 TreeManifestCtx
2666 TreeManifestCtx
2675 )
2667 )
2676
2668
2677 if typing.TYPE_CHECKING:
2669 if typing.TYPE_CHECKING:
2678 treemanifestctx = TreeManifestCtx
2670 treemanifestctx = TreeManifestCtx
2679
2671
2680
2672
2681 class excludeddir(treemanifest):
2673 class excludeddir(treemanifest):
2682 """Stand-in for a directory that is excluded from the repository.
2674 """Stand-in for a directory that is excluded from the repository.
2683
2675
2684 With narrowing active on a repository that uses treemanifests,
2676 With narrowing active on a repository that uses treemanifests,
2685 some of the directory revlogs will be excluded from the resulting
2677 some of the directory revlogs will be excluded from the resulting
2686 clone. This is a huge storage win for clients, but means we need
2678 clone. This is a huge storage win for clients, but means we need
2687 some sort of pseudo-manifest to surface to internals so we can
2679 some sort of pseudo-manifest to surface to internals so we can
2688 detect a merge conflict outside the narrowspec. That's what this
2680 detect a merge conflict outside the narrowspec. That's what this
2689 class is: it stands in for a directory whose node is known, but
2681 class is: it stands in for a directory whose node is known, but
2690 whose contents are unknown.
2682 whose contents are unknown.
2691 """
2683 """
2692
2684
2693 _files: Dict[bytes, bytes]
2685 _files: Dict[bytes, bytes]
2694 _flags: Dict[bytes, bytes]
2686 _flags: Dict[bytes, bytes]
2695
2687
2696 def __init__(self, nodeconstants, dir, node):
2688 def __init__(self, nodeconstants, dir, node):
2697 super(excludeddir, self).__init__(nodeconstants, dir)
2689 super(excludeddir, self).__init__(nodeconstants, dir)
2698 self._node = node
2690 self._node = node
2699 # Add an empty file, which will be included by iterators and such,
2691 # Add an empty file, which will be included by iterators and such,
2700 # appearing as the directory itself (i.e. something like "dir/")
2692 # appearing as the directory itself (i.e. something like "dir/")
2701 self._files[b''] = node
2693 self._files[b''] = node
2702 self._flags[b''] = b't'
2694 self._flags[b''] = b't'
2703
2695
2704 # Manifests outside the narrowspec should never be modified, so avoid
2696 # Manifests outside the narrowspec should never be modified, so avoid
2705 # copying. This makes a noticeable difference when there are very many
2697 # copying. This makes a noticeable difference when there are very many
2706 # directories outside the narrowspec. Also, it makes sense for the copy to
2698 # directories outside the narrowspec. Also, it makes sense for the copy to
2707 # be of the same type as the original, which would not happen with the
2699 # be of the same type as the original, which would not happen with the
2708 # super type's copy().
2700 # super type's copy().
2709 def copy(self):
2701 def copy(self):
2710 return self
2702 return self
2711
2703
2712
2704
2713 class excludeddirmanifestctx(treemanifestctx):
2705 class excludeddirmanifestctx(treemanifestctx):
2714 """context wrapper for excludeddir - see that docstring for rationale"""
2706 """context wrapper for excludeddir - see that docstring for rationale"""
2715
2707
2716 def __init__(self, nodeconstants, dir, node):
2708 def __init__(self, nodeconstants, dir, node):
2717 self.nodeconstants = nodeconstants
2709 self.nodeconstants = nodeconstants
2718 self._dir = dir
2710 self._dir = dir
2719 self._node = node
2711 self._node = node
2720
2712
2721 def read(self):
2713 def read(self):
2722 return excludeddir(self.nodeconstants, self._dir, self._node)
2714 return excludeddir(self.nodeconstants, self._dir, self._node)
2723
2715
2724 def readfast(self, shallow=False):
2716 def readfast(self, shallow=False):
2725 # special version of readfast since we don't have underlying storage
2717 # special version of readfast since we don't have underlying storage
2726 return self.read()
2718 return self.read()
2727
2719
2728 def write(self, *args):
2720 def write(self, *args):
2729 raise error.ProgrammingError(
2721 raise error.ProgrammingError(
2730 b'attempt to write manifest from excluded dir %s' % self._dir
2722 b'attempt to write manifest from excluded dir %s' % self._dir
2731 )
2723 )
2732
2724
2733
2725
2734 class excludedmanifestrevlog(manifestrevlog):
2726 class excludedmanifestrevlog(manifestrevlog):
2735 """Stand-in for excluded treemanifest revlogs.
2727 """Stand-in for excluded treemanifest revlogs.
2736
2728
2737 When narrowing is active on a treemanifest repository, we'll have
2729 When narrowing is active on a treemanifest repository, we'll have
2738 references to directories we can't see due to the revlog being
2730 references to directories we can't see due to the revlog being
2739 skipped. This class exists to conform to the manifestrevlog
2731 skipped. This class exists to conform to the manifestrevlog
2740 interface for those directories and proactively prevent writes to
2732 interface for those directories and proactively prevent writes to
2741 outside the narrowspec.
2733 outside the narrowspec.
2742 """
2734 """
2743
2735
2744 def __init__(self, nodeconstants, dir):
2736 def __init__(self, nodeconstants, dir):
2745 self.nodeconstants = nodeconstants
2737 self.nodeconstants = nodeconstants
2746 self._dir = dir
2738 self._dir = dir
2747
2739
2748 def __len__(self):
2740 def __len__(self):
2749 raise error.ProgrammingError(
2741 raise error.ProgrammingError(
2750 b'attempt to get length of excluded dir %s' % self._dir
2742 b'attempt to get length of excluded dir %s' % self._dir
2751 )
2743 )
2752
2744
2753 def rev(self, node):
2745 def rev(self, node):
2754 raise error.ProgrammingError(
2746 raise error.ProgrammingError(
2755 b'attempt to get rev from excluded dir %s' % self._dir
2747 b'attempt to get rev from excluded dir %s' % self._dir
2756 )
2748 )
2757
2749
2758 def linkrev(self, node):
2750 def linkrev(self, node):
2759 raise error.ProgrammingError(
2751 raise error.ProgrammingError(
2760 b'attempt to get linkrev from excluded dir %s' % self._dir
2752 b'attempt to get linkrev from excluded dir %s' % self._dir
2761 )
2753 )
2762
2754
2763 def node(self, rev):
2755 def node(self, rev):
2764 raise error.ProgrammingError(
2756 raise error.ProgrammingError(
2765 b'attempt to get node from excluded dir %s' % self._dir
2757 b'attempt to get node from excluded dir %s' % self._dir
2766 )
2758 )
2767
2759
2768 def add(self, *args, **kwargs):
2760 def add(self, *args, **kwargs):
2769 # We should never write entries in dirlogs outside the narrow clone.
2761 # We should never write entries in dirlogs outside the narrow clone.
2770 # However, the method still gets called from writesubtree() in
2762 # However, the method still gets called from writesubtree() in
2771 # _addtree(), so we need to handle it. We should possibly make that
2763 # _addtree(), so we need to handle it. We should possibly make that
2772 # avoid calling add() with a clean manifest (_dirty is always False
2764 # avoid calling add() with a clean manifest (_dirty is always False
2773 # in excludeddir instances).
2765 # in excludeddir instances).
2774 pass
2766 pass
General Comments 0
You need to be logged in to leave comments. Login now