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