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