manifest.py
2747 lines
| 90.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / manifest.py
mpm@selenic.com
|
r1089 | # manifest.py - manifest revision class for mercurial | ||
# | ||||
Raphaël Gomès
|
r47575 | # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> | ||
mpm@selenic.com
|
r1089 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
mpm@selenic.com
|
r1089 | |||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Gregory Szorc
|
r27502 | |||
import heapq | ||||
Augie Fackler
|
r32536 | import itertools | ||
Gregory Szorc
|
r27502 | import struct | ||
Matt Harbison
|
r53224 | import typing | ||
Martijn Pieters
|
r38803 | import weakref | ||
Gregory Szorc
|
r27502 | |||
r52658 | from typing import ( | |||
r52667 | Callable, | |||
r52668 | Collection, | |||
r52667 | Dict, | |||
r52658 | Iterable, | |||
r52667 | Iterator, | |||
List, | ||||
Optional, | ||||
Set, | ||||
Tuple, | ||||
Union, | ||||
cast, | ||||
r52658 | ) | |||
Gregory Szorc
|
r27502 | from .i18n import _ | ||
Augie Fackler
|
r31530 | from .node import ( | ||
bin, | ||||
hex, | ||||
Gregory Szorc
|
r39352 | nullrev, | ||
Augie Fackler
|
r31530 | ) | ||
Gregory Szorc
|
r27502 | from . import ( | ||
Kyle Lippincott
|
r44742 | encoding, | ||
Gregory Szorc
|
r27502 | error, | ||
Augie Fackler
|
r44768 | match as matchmod, | ||
Gregory Szorc
|
r27502 | mdiff, | ||
r43923 | pathutil, | |||
Yuya Nishihara
|
r32372 | policy, | ||
Yuya Nishihara
|
r38353 | pycompat, | ||
Gregory Szorc
|
r27502 | revlog, | ||
util, | ||||
) | ||||
Pulkit Goyal
|
r43078 | from .interfaces import ( | ||
repository, | ||||
Gregory Szorc
|
r38549 | ) | ||
r47838 | from .revlogutils import ( | |||
constants as revlog_constants, | ||||
) | ||||
mpm@selenic.com
|
r1089 | |||
Matt Harbison
|
r53224 | if typing.TYPE_CHECKING: | ||
from typing import ( | ||||
ByteString, | ||||
) | ||||
Augie Fackler
|
r43906 | parsers = policy.importmod('parsers') | ||
Drew Gottlieb
|
r24322 | propertycache = util.propertycache | ||
Augie Fackler
|
r24225 | |||
Matt Harbison
|
r42568 | # Allow tests to more easily test the alternate path in manifestdict.fastdelta() | ||
FASTDELTA_TEXTDIFF_THRESHOLD = 1000 | ||||
Augie Fackler
|
r43346 | |||
r52667 | def _parse(nodelen, data: bytes): | |||
Martin von Zweigbergk
|
r24524 | # This method does a little bit of excessive-looking | ||
# precondition checking. This is so that the behavior of this | ||||
# class exactly matches its C counterpart to try and help | ||||
# prevent surprise breakage for anyone that develops against | ||||
# the pure version. | ||||
Augie Fackler
|
r43347 | if data and data[-1:] != b'\n': | ||
raise ValueError(b'Manifest did not end in a newline.') | ||||
Martin von Zweigbergk
|
r24524 | prev = None | ||
for l in data.splitlines(): | ||||
if prev is not None and prev > l: | ||||
Augie Fackler
|
r43347 | raise ValueError(b'Manifest lines not in sorted order.') | ||
Martin von Zweigbergk
|
r24524 | prev = l | ||
Augie Fackler
|
r43347 | f, n = l.split(b'\0') | ||
Augie Fackler
|
r45200 | nl = len(n) | ||
Joerg Sonnenberger
|
r45679 | flags = n[-1:] | ||
if flags in _manifestflags: | ||||
n = n[:-1] | ||||
nl -= 1 | ||||
Martin von Zweigbergk
|
r24524 | else: | ||
Joerg Sonnenberger
|
r45679 | flags = b'' | ||
Joerg Sonnenberger
|
r47817 | if nl != 2 * nodelen: | ||
Joerg Sonnenberger
|
r45679 | raise ValueError(b'Invalid manifest line') | ||
yield f, bin(n), flags | ||||
Martin von Zweigbergk
|
r24524 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r36391 | def _text(it): | ||
Martin von Zweigbergk
|
r24525 | files = [] | ||
lines = [] | ||||
for f, n, fl in it: | ||||
files.append(f) | ||||
# if this is changed to support newlines in filenames, | ||||
# be sure to check the templates/ dir again (especially *-raw.tmpl) | ||||
Augie Fackler
|
r43347 | lines.append(b"%s\0%s%s\n" % (f, hex(n), fl)) | ||
Martin von Zweigbergk
|
r24525 | |||
_checkforbidden(files) | ||||
Augie Fackler
|
r43347 | return b''.join(lines) | ||
Martin von Zweigbergk
|
r24525 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class lazymanifestiter: | ||
r52667 | def __init__(self, lm: '_LazyManifest') -> None: | |||
Maciej Fijalkowski
|
r30042 | self.pos = 0 | ||
self.lm = lm | ||||
Augie Fackler
|
r24225 | |||
r52667 | def __iter__(self) -> 'lazymanifestiter': | |||
Maciej Fijalkowski
|
r30042 | return self | ||
Augie Fackler
|
r24223 | |||
r52667 | def next(self) -> bytes: | |||
Maciej Fijalkowski
|
r30042 | try: | ||
data, pos = self.lm._get(self.pos) | ||||
except IndexError: | ||||
raise StopIteration | ||||
if pos == -1: | ||||
r52667 | assert isinstance(data, tuple) | |||
Maciej Fijalkowski
|
r30042 | self.pos += 1 | ||
return data[0] | ||||
r52667 | assert isinstance(data, bytes) | |||
Maciej Fijalkowski
|
r30042 | self.pos += 1 | ||
Augie Fackler
|
r43347 | zeropos = data.find(b'\x00', pos) | ||
Maciej Fijalkowski
|
r30042 | return data[pos:zeropos] | ||
Augie Fackler
|
r24224 | |||
Augie Fackler
|
r31364 | __next__ = next | ||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class lazymanifestiterentries: | ||
r52667 | def __init__(self, lm: '_LazyManifest') -> None: | |||
Maciej Fijalkowski
|
r30042 | self.lm = lm | ||
self.pos = 0 | ||||
Augie Fackler
|
r24225 | |||
r52667 | def __iter__(self) -> 'lazymanifestiterentries': | |||
Maciej Fijalkowski
|
r30042 | return self | ||
r52667 | def next(self) -> Tuple[bytes, bytes, bytes]: | |||
Maciej Fijalkowski
|
r30042 | try: | ||
data, pos = self.lm._get(self.pos) | ||||
except IndexError: | ||||
raise StopIteration | ||||
if pos == -1: | ||||
r52667 | assert isinstance(data, tuple) | |||
Maciej Fijalkowski
|
r30042 | self.pos += 1 | ||
return data | ||||
r52667 | assert isinstance(data, bytes) | |||
Augie Fackler
|
r43347 | zeropos = data.find(b'\x00', pos) | ||
Joerg Sonnenberger
|
r45678 | nlpos = data.find(b'\n', pos) | ||
if zeropos == -1 or nlpos == -1 or nlpos < zeropos: | ||||
raise error.StorageError(b'Invalid manifest line') | ||||
flags = data[nlpos - 1 : nlpos] | ||||
if flags in _manifestflags: | ||||
hlen = nlpos - zeropos - 2 | ||||
else: | ||||
hlen = nlpos - zeropos - 1 | ||||
flags = b'' | ||||
Joerg Sonnenberger
|
r47817 | if hlen != 2 * self.lm._nodelen: | ||
Joerg Sonnenberger
|
r45678 | raise error.StorageError(b'Invalid manifest line') | ||
hashval = unhexlify( | ||||
data, self.lm.extrainfo[self.pos], zeropos + 1, hlen | ||||
) | ||||
Maciej Fijalkowski
|
r30042 | self.pos += 1 | ||
return (data[pos:zeropos], hashval, flags) | ||||
Augie Fackler
|
r31364 | __next__ = next | ||
Augie Fackler
|
r43346 | |||
r52657 | def unhexlify(data: bytes, extra: int, pos, length: int): | |||
Augie Fackler
|
r43346 | s = bin(data[pos : pos + length]) | ||
Maciej Fijalkowski
|
r30042 | if extra: | ||
r52657 | s += bytes([extra & 0xFF]) | |||
Maciej Fijalkowski
|
r30042 | return s | ||
Augie Fackler
|
r43346 | |||
Maciej Fijalkowski
|
r30042 | def _cmp(a, b): | ||
return (a > b) - (a < b) | ||||
Augie Fackler
|
r43346 | |||
Joerg Sonnenberger
|
r45678 | _manifestflags = {b'', b'l', b't', b'x'} | ||
r52661 | class _LazyManifest: | |||
Matt Harbison
|
r42570 | """A pure python manifest backed by a byte string. It is supplimented with | ||
internal lists as it is modified, until it is compacted back to a pure byte | ||||
string. | ||||
``data`` is the initial manifest data. | ||||
``positions`` is a list of offsets, one per manifest entry. Positive | ||||
values are offsets into ``data``, negative values are offsets into the | ||||
``extradata`` list. When an entry is removed, its entry is dropped from | ||||
``positions``. The values are encoded such that when walking the list and | ||||
indexing into ``data`` or ``extradata`` as appropriate, the entries are | ||||
sorted by filename. | ||||
``extradata`` is a list of (key, hash, flags) for entries that were added or | ||||
modified since the manifest was created or compacted. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
def __init__( | ||||
self, | ||||
r52667 | nodelen: int, | |||
data: bytes, | ||||
Augie Fackler
|
r43346 | positions=None, | ||
extrainfo=None, | ||||
extradata=None, | ||||
r52667 | hasremovals: bool = False, | |||
Augie Fackler
|
r43346 | ): | ||
Joerg Sonnenberger
|
r47817 | self._nodelen = nodelen | ||
Maciej Fijalkowski
|
r30042 | if positions is None: | ||
self.positions = self.findlines(data) | ||||
self.extrainfo = [0] * len(self.positions) | ||||
self.data = data | ||||
self.extradata = [] | ||||
Matt Harbison
|
r42569 | self.hasremovals = False | ||
Maciej Fijalkowski
|
r30042 | else: | ||
self.positions = positions[:] | ||||
self.extrainfo = extrainfo[:] | ||||
self.extradata = extradata[:] | ||||
self.data = data | ||||
Matt Harbison
|
r42569 | self.hasremovals = hasremovals | ||
Maciej Fijalkowski
|
r30042 | |||
r52667 | def findlines(self, data: bytes) -> List[int]: | |||
Maciej Fijalkowski
|
r30042 | if not data: | ||
return [] | ||||
Augie Fackler
|
r43347 | pos = data.find(b"\n") | ||
if pos == -1 or data[-1:] != b'\n': | ||||
raise ValueError(b"Manifest did not end in a newline.") | ||||
Maciej Fijalkowski
|
r30042 | positions = [0] | ||
Augie Fackler
|
r43347 | prev = data[: data.find(b'\x00')] | ||
Maciej Fijalkowski
|
r30042 | while pos < len(data) - 1 and pos != -1: | ||
positions.append(pos + 1) | ||||
Augie Fackler
|
r43347 | nexts = data[pos + 1 : data.find(b'\x00', pos + 1)] | ||
Maciej Fijalkowski
|
r30042 | if nexts < prev: | ||
Augie Fackler
|
r43347 | raise ValueError(b"Manifest lines not in sorted order.") | ||
Maciej Fijalkowski
|
r30042 | prev = nexts | ||
Augie Fackler
|
r43347 | pos = data.find(b"\n", pos + 1) | ||
Maciej Fijalkowski
|
r30042 | return positions | ||
r52667 | def _get( | |||
self, index: int | ||||
) -> Tuple[Union[bytes, Tuple[bytes, bytes, bytes]], int]: | ||||
Maciej Fijalkowski
|
r30042 | # get the position encoded in pos: | ||
# positive number is an index in 'data' | ||||
# negative number is in extrapieces | ||||
pos = self.positions[index] | ||||
if pos >= 0: | ||||
return self.data, pos | ||||
return self.extradata[-pos - 1], -1 | ||||
r52667 | def _getkey(self, pos) -> bytes: | |||
Maciej Fijalkowski
|
r30042 | if pos >= 0: | ||
Augie Fackler
|
r43347 | return self.data[pos : self.data.find(b'\x00', pos + 1)] | ||
Maciej Fijalkowski
|
r30042 | return self.extradata[-pos - 1][0] | ||
r52667 | def bsearch(self, key: bytes) -> int: | |||
Maciej Fijalkowski
|
r30042 | first = 0 | ||
last = len(self.positions) - 1 | ||||
while first <= last: | ||||
Augie Fackler
|
r43346 | midpoint = (first + last) // 2 | ||
Maciej Fijalkowski
|
r30042 | nextpos = self.positions[midpoint] | ||
candidate = self._getkey(nextpos) | ||||
r = _cmp(key, candidate) | ||||
if r == 0: | ||||
return midpoint | ||||
else: | ||||
if r < 0: | ||||
last = midpoint - 1 | ||||
else: | ||||
first = midpoint + 1 | ||||
return -1 | ||||
Augie Fackler
|
r24225 | |||
r52667 | def bsearch2(self, key: bytes) -> Tuple[int, bool]: | |||
Maciej Fijalkowski
|
r30042 | # same as the above, but will always return the position | ||
# done for performance reasons | ||||
first = 0 | ||||
last = len(self.positions) - 1 | ||||
while first <= last: | ||||
Augie Fackler
|
r43346 | midpoint = (first + last) // 2 | ||
Maciej Fijalkowski
|
r30042 | nextpos = self.positions[midpoint] | ||
candidate = self._getkey(nextpos) | ||||
r = _cmp(key, candidate) | ||||
if r == 0: | ||||
return (midpoint, True) | ||||
else: | ||||
if r < 0: | ||||
last = midpoint - 1 | ||||
else: | ||||
first = midpoint + 1 | ||||
return (first, False) | ||||
r52667 | def __contains__(self, key: bytes) -> bool: | |||
Maciej Fijalkowski
|
r30042 | return self.bsearch(key) != -1 | ||
r52667 | def __getitem__(self, key: bytes) -> Tuple[bytes, bytes]: | |||
Augie Fackler
|
r31367 | if not isinstance(key, bytes): | ||
Augie Fackler
|
r43347 | raise TypeError(b"getitem: manifest keys must be a bytes.") | ||
Maciej Fijalkowski
|
r30042 | needle = self.bsearch(key) | ||
if needle == -1: | ||||
raise KeyError | ||||
data, pos = self._get(needle) | ||||
if pos == -1: | ||||
r52667 | assert isinstance(data, tuple) | |||
Maciej Fijalkowski
|
r30042 | return (data[1], data[2]) | ||
r52667 | ||||
assert isinstance(data, bytes) | ||||
Augie Fackler
|
r43347 | zeropos = data.find(b'\x00', pos) | ||
Augie Fackler
|
r45200 | nlpos = data.find(b'\n', zeropos) | ||
Maciej Fijalkowski
|
r30042 | assert 0 <= needle <= len(self.positions) | ||
assert len(self.extrainfo) == len(self.positions) | ||||
Joerg Sonnenberger
|
r45678 | if zeropos == -1 or nlpos == -1 or nlpos < zeropos: | ||
raise error.StorageError(b'Invalid manifest line') | ||||
Augie Fackler
|
r45200 | hlen = nlpos - zeropos - 1 | ||
Joerg Sonnenberger
|
r45678 | flags = data[nlpos - 1 : nlpos] | ||
if flags in _manifestflags: | ||||
Augie Fackler
|
r45200 | hlen -= 1 | ||
Joerg Sonnenberger
|
r45678 | else: | ||
flags = b'' | ||||
Joerg Sonnenberger
|
r47817 | if hlen != 2 * self._nodelen: | ||
Joerg Sonnenberger
|
r45678 | raise error.StorageError(b'Invalid manifest line') | ||
Augie Fackler
|
r45200 | hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, hlen) | ||
Maciej Fijalkowski
|
r30042 | return (hashval, flags) | ||
r52667 | def __delitem__(self, key: bytes) -> None: | |||
Maciej Fijalkowski
|
r30042 | needle, found = self.bsearch2(key) | ||
if not found: | ||||
raise KeyError | ||||
cur = self.positions[needle] | ||||
Augie Fackler
|
r43346 | self.positions = self.positions[:needle] + self.positions[needle + 1 :] | ||
self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1 :] | ||||
Maciej Fijalkowski
|
r30042 | if cur >= 0: | ||
Matt Harbison
|
r42570 | # This does NOT unsort the list as far as the search functions are | ||
# concerned, as they only examine lines mapped by self.positions. | ||||
Augie Fackler
|
r43347 | self.data = self.data[:cur] + b'\x00' + self.data[cur + 1 :] | ||
Matt Harbison
|
r42569 | self.hasremovals = True | ||
Maciej Fijalkowski
|
r30042 | |||
r52667 | def __setitem__(self, key: bytes, value: Tuple[bytes, bytes]): | |||
Augie Fackler
|
r31531 | if not isinstance(key, bytes): | ||
Augie Fackler
|
r43347 | raise TypeError(b"setitem: manifest keys must be a byte string.") | ||
Maciej Fijalkowski
|
r30042 | if not isinstance(value, tuple) or len(value) != 2: | ||
Augie Fackler
|
r43347 | raise TypeError( | ||
b"Manifest values must be a tuple of (node, flags)." | ||||
) | ||||
Maciej Fijalkowski
|
r30042 | hashval = value[0] | ||
Joerg Sonnenberger
|
r45779 | if not isinstance(hashval, bytes) or len(hashval) not in (20, 32): | ||
Augie Fackler
|
r45195 | raise TypeError(b"node must be a 20-byte or 32-byte byte string") | ||
Maciej Fijalkowski
|
r30042 | flags = value[1] | ||
Augie Fackler
|
r31531 | if not isinstance(flags, bytes) or len(flags) > 1: | ||
Augie Fackler
|
r43347 | raise TypeError(b"flags must a 0 or 1 byte string, got %r", flags) | ||
Maciej Fijalkowski
|
r30042 | needle, found = self.bsearch2(key) | ||
if found: | ||||
# put the item | ||||
pos = self.positions[needle] | ||||
if pos < 0: | ||||
self.extradata[-pos - 1] = (key, hashval, value[1]) | ||||
else: | ||||
# just don't bother | ||||
self.extradata.append((key, hashval, value[1])) | ||||
self.positions[needle] = -len(self.extradata) | ||||
else: | ||||
# not found, put it in with extra positions | ||||
self.extradata.append((key, hashval, value[1])) | ||||
Augie Fackler
|
r43346 | self.positions = ( | ||
self.positions[:needle] | ||||
+ [-len(self.extradata)] | ||||
+ self.positions[needle:] | ||||
) | ||||
self.extrainfo = ( | ||||
self.extrainfo[:needle] + [0] + self.extrainfo[needle:] | ||||
) | ||||
Martin von Zweigbergk
|
r24298 | |||
r52667 | def copy(self) -> '_LazyManifest': | |||
Maciej Fijalkowski
|
r30042 | # XXX call _compact like in C? | ||
Augie Fackler
|
r43346 | return _lazymanifest( | ||
Joerg Sonnenberger
|
r47817 | self._nodelen, | ||
Augie Fackler
|
r43346 | self.data, | ||
self.positions, | ||||
self.extrainfo, | ||||
self.extradata, | ||||
self.hasremovals, | ||||
) | ||||
Maciej Fijalkowski
|
r30042 | |||
r52667 | def _compact(self) -> None: | |||
Maciej Fijalkowski
|
r30042 | # hopefully not called TOO often | ||
Matt Harbison
|
r42569 | if len(self.extradata) == 0 and not self.hasremovals: | ||
Maciej Fijalkowski
|
r30042 | return | ||
l = [] | ||||
i = 0 | ||||
offset = 0 | ||||
self.extrainfo = [0] * len(self.positions) | ||||
while i < len(self.positions): | ||||
if self.positions[i] >= 0: | ||||
cur = self.positions[i] | ||||
last_cut = cur | ||||
Matt Harbison
|
r42570 | |||
# Collect all contiguous entries in the buffer at the current | ||||
# offset, breaking out only for added/modified items held in | ||||
# extradata, or a deleted line prior to the next position. | ||||
Maciej Fijalkowski
|
r30042 | while True: | ||
self.positions[i] = offset | ||||
i += 1 | ||||
if i == len(self.positions) or self.positions[i] < 0: | ||||
break | ||||
Matt Harbison
|
r42569 | |||
# A removed file has no positions[] entry, but does have an | ||||
# overwritten first byte. Break out and find the end of the | ||||
# current good entry/entries if there is a removed file | ||||
# before the next position. | ||||
Augie Fackler
|
r43346 | if ( | ||
self.hasremovals | ||||
Augie Fackler
|
r43347 | and self.data.find(b'\n\x00', cur, self.positions[i]) | ||
Augie Fackler
|
r43346 | != -1 | ||
): | ||||
Matt Harbison
|
r42569 | break | ||
Maciej Fijalkowski
|
r30042 | offset += self.positions[i] - cur | ||
cur = self.positions[i] | ||||
Augie Fackler
|
r43347 | end_cut = self.data.find(b'\n', cur) | ||
Maciej Fijalkowski
|
r30042 | if end_cut != -1: | ||
end_cut += 1 | ||||
offset += end_cut - cur | ||||
l.append(self.data[last_cut:end_cut]) | ||||
else: | ||||
while i < len(self.positions) and self.positions[i] < 0: | ||||
cur = self.positions[i] | ||||
t = self.extradata[-cur - 1] | ||||
l.append(self._pack(t)) | ||||
self.positions[i] = offset | ||||
Augie Fackler
|
r45194 | # Hashes are either 20 bytes (old sha1s) or 32 | ||
# bytes (new non-sha1). | ||||
hlen = 20 | ||||
if len(t[1]) > 25: | ||||
hlen = 32 | ||||
if len(t[1]) > hlen: | ||||
self.extrainfo[i] = ord(t[1][hlen + 1]) | ||||
Maciej Fijalkowski
|
r30042 | offset += len(l[-1]) | ||
i += 1 | ||||
Augie Fackler
|
r43347 | self.data = b''.join(l) | ||
Matt Harbison
|
r42569 | self.hasremovals = False | ||
Maciej Fijalkowski
|
r30042 | self.extradata = [] | ||
r52667 | def _pack(self, d: Tuple[bytes, bytes, bytes]) -> bytes: | |||
Augie Fackler
|
r45191 | n = d[1] | ||
Joerg Sonnenberger
|
r45678 | assert len(n) in (20, 32) | ||
Augie Fackler
|
r45191 | return d[0] + b'\x00' + hex(n) + d[2] + b'\n' | ||
Maciej Fijalkowski
|
r30042 | |||
r52667 | def text(self) -> ByteString: | |||
Maciej Fijalkowski
|
r30042 | self._compact() | ||
return self.data | ||||
Augie Fackler
|
r24225 | |||
r52667 | def diff( | |||
self, m2: '_LazyManifest', clean: bool = False | ||||
) -> Dict[ | ||||
bytes, | ||||
Optional[ | ||||
Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]] | ||||
], | ||||
]: | ||||
Augie Fackler
|
r24225 | '''Finds changes between the current manifest and m2.''' | ||
Maciej Fijalkowski
|
r30042 | # XXX think whether efficiency matters here | ||
Augie Fackler
|
r24225 | diff = {} | ||
Maciej Fijalkowski
|
r30042 | for fn, e1, flags in self.iterentries(): | ||
Augie Fackler
|
r24225 | if fn not in m2: | ||
Augie Fackler
|
r43347 | diff[fn] = (e1, flags), (None, b'') | ||
Augie Fackler
|
r24225 | else: | ||
e2 = m2[fn] | ||||
Maciej Fijalkowski
|
r30042 | if (e1, flags) != e2: | ||
diff[fn] = (e1, flags), e2 | ||||
Augie Fackler
|
r24225 | elif clean: | ||
diff[fn] = None | ||||
Maciej Fijalkowski
|
r30042 | for fn, e2, flags in m2.iterentries(): | ||
Augie Fackler
|
r24225 | if fn not in self: | ||
Augie Fackler
|
r43347 | diff[fn] = (None, b''), (e2, flags) | ||
Augie Fackler
|
r24225 | |||
return diff | ||||
r52667 | def iterentries(self) -> lazymanifestiterentries: | |||
Maciej Fijalkowski
|
r30042 | return lazymanifestiterentries(self) | ||
r52667 | def iterkeys(self) -> lazymanifestiter: | |||
Maciej Fijalkowski
|
r30042 | return lazymanifestiter(self) | ||
r52667 | def __iter__(self) -> lazymanifestiter: | |||
Maciej Fijalkowski
|
r30042 | return lazymanifestiter(self) | ||
r52667 | def __len__(self) -> int: | |||
Maciej Fijalkowski
|
r30042 | return len(self.positions) | ||
r52667 | def filtercopy(self, filterfn: Callable[[bytes], bool]) -> '_LazyManifest': | |||
Maciej Fijalkowski
|
r30042 | # XXX should be optimized | ||
Joerg Sonnenberger
|
r47817 | c = _lazymanifest(self._nodelen, b'') | ||
Martin von Zweigbergk
|
r24298 | for f, n, fl in self.iterentries(): | ||
Augie Fackler
|
r24225 | if filterfn(f): | ||
c[f] = n, fl | ||||
return c | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r24226 | try: | ||
_lazymanifest = parsers.lazymanifest | ||||
except AttributeError: | ||||
r52661 | _lazymanifest = _LazyManifest | |||
Augie Fackler
|
r24226 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r53392 | class manifestdict(repository.imanifestdict): | ||
r52667 | def __init__(self, nodelen: int, data: ByteString = b''): | |||
Joerg Sonnenberger
|
r47817 | self._nodelen = nodelen | ||
self._lm = _lazymanifest(nodelen, data) | ||||
Augie Fackler
|
r24225 | |||
r52667 | def __getitem__(self, key: bytes) -> bytes: | |||
Augie Fackler
|
r24225 | return self._lm[key][0] | ||
r52667 | def find(self, key: bytes) -> Tuple[bytes, bytes]: | |||
Martin von Zweigbergk
|
r24277 | return self._lm[key] | ||
r52667 | def __len__(self) -> int: | |||
Augie Fackler
|
r24225 | return len(self._lm) | ||
r52667 | def __nonzero__(self) -> bool: | |||
Durham Goode
|
r30331 | # nonzero is covered by the __len__ function, but implementing it here | ||
# makes it easier for extensions to override. | ||||
return len(self._lm) != 0 | ||||
Gregory Szorc
|
r31476 | __bool__ = __nonzero__ | ||
r52667 | def set(self, key: bytes, node: bytes, flags: bytes) -> None: | |||
Arseniy Alekseyev
|
r52651 | self._lm[key] = node, flags | ||
r52667 | def __setitem__(self, key: bytes, node: bytes) -> None: | |||
Augie Fackler
|
r44725 | self._lm[key] = node, self.flags(key) | ||
Augie Fackler
|
r24225 | |||
r52667 | def __contains__(self, key: bytes) -> bool: | |||
Pulkit Goyal
|
r34347 | if key is None: | ||
return False | ||||
Augie Fackler
|
r24225 | return key in self._lm | ||
Matt Harbison
|
r52694 | def __delitem__(self, key: bytes) -> None: | ||
Augie Fackler
|
r24225 | del self._lm[key] | ||
r52667 | def __iter__(self) -> Iterator[bytes]: | |||
Martin von Zweigbergk
|
r24298 | return self._lm.__iter__() | ||
Augie Fackler
|
r24225 | |||
r52667 | def iterkeys(self) -> Iterator[bytes]: | |||
Martin von Zweigbergk
|
r24295 | return self._lm.iterkeys() | ||
r52667 | def keys(self) -> List[bytes]: | |||
Martin von Zweigbergk
|
r24295 | return list(self.iterkeys()) | ||
Augie Fackler
|
r24225 | |||
r52667 | def filesnotin(self, m2, match=None) -> Set[bytes]: | |||
Martin von Zweigbergk
|
r24184 | '''Set of files in this manifest that are not in the other''' | ||
Augie Fackler
|
r44768 | if match is not None: | ||
match = matchmod.badmatch(match, lambda path, msg: None) | ||||
sm2 = set(m2.walk(match)) | ||||
return {f for f in self.walk(match) if f not in sm2} | ||||
return {f for f in self if f not in m2} | ||||
Martin von Zweigbergk
|
r24184 | |||
Drew Gottlieb
|
r24322 | @propertycache | ||
r52667 | def _dirs(self) -> pathutil.dirs: | |||
r43923 | return pathutil.dirs(self) | |||
Drew Gottlieb
|
r24322 | |||
r52667 | def dirs(self) -> pathutil.dirs: | |||
Drew Gottlieb
|
r24322 | return self._dirs | ||
r52667 | def hasdir(self, dir: bytes) -> bool: | |||
Drew Gottlieb
|
r24324 | return dir in self._dirs | ||
r52667 | def _filesfastpath(self, match: matchmod.basematcher) -> bool: | |||
Augie Fackler
|
r46554 | """Checks whether we can correctly and quickly iterate over matcher | ||
files instead of over manifest files.""" | ||||
Martin von Zweigbergk
|
r24685 | files = match.files() | ||
Augie Fackler
|
r43346 | return len(files) < 100 and ( | ||
match.isexact() | ||||
or (match.prefix() and all(fn in self for fn in files)) | ||||
) | ||||
Martin von Zweigbergk
|
r24685 | |||
r52667 | def walk(self, match: matchmod.basematcher) -> Iterator[bytes]: | |||
Augie Fackler
|
r46554 | """Generates matching file names. | ||
Drew Gottlieb
|
r24646 | |||
Equivalent to manifest.matches(match).iterkeys(), but without creating | ||||
an entirely new manifest. | ||||
It also reports nonexistent files by marking them bad with match.bad(). | ||||
Augie Fackler
|
r46554 | """ | ||
Martin von Zweigbergk
|
r24683 | if match.always(): | ||
for f in iter(self): | ||||
yield f | ||||
return | ||||
Drew Gottlieb
|
r24646 | fset = set(match.files()) | ||
# avoid the entire walk if we're only looking for specific files | ||||
Martin von Zweigbergk
|
r24685 | if self._filesfastpath(match): | ||
Martin von Zweigbergk
|
r24667 | for fn in sorted(fset): | ||
Augie Fackler
|
r44741 | if fn in self: | ||
yield fn | ||||
Martin von Zweigbergk
|
r24682 | return | ||
Drew Gottlieb
|
r24646 | |||
for fn in self: | ||||
if fn in fset: | ||||
# specified pattern is the exact name | ||||
fset.remove(fn) | ||||
if match(fn): | ||||
yield fn | ||||
Martin von Zweigbergk
|
r42528 | # for dirstate.walk, files=[''] means "walk the whole tree". | ||
Drew Gottlieb
|
r24646 | # follow that here, too | ||
Augie Fackler
|
r43347 | fset.discard(b'') | ||
Drew Gottlieb
|
r24646 | |||
for fn in sorted(fset): | ||||
if not self.hasdir(fn): | ||||
match.bad(fn, None) | ||||
Matt Harbison
|
r52960 | def _matches(self, match: matchmod.basematcher) -> 'manifestdict': | ||
Martin von Zweigbergk
|
r23305 | '''generate a new manifest filtered by the match argument''' | ||
if match.always(): | ||||
return self.copy() | ||||
Martin von Zweigbergk
|
r24685 | if self._filesfastpath(match): | ||
Joerg Sonnenberger
|
r47817 | m = manifestdict(self._nodelen) | ||
Martin von Zweigbergk
|
r24666 | lm = self._lm | ||
for fn in match.files(): | ||||
if fn in lm: | ||||
m._lm[fn] = lm[fn] | ||||
return m | ||||
Martin von Zweigbergk
|
r23305 | |||
Joerg Sonnenberger
|
r47817 | m = manifestdict(self._nodelen) | ||
Martin von Zweigbergk
|
r24664 | m._lm = self._lm.filtercopy(match) | ||
return m | ||||
Martin von Zweigbergk
|
r23305 | |||
r52667 | def diff( | |||
self, | ||||
Matt Harbison
|
r52960 | m2: 'manifestdict', | ||
r52667 | match: Optional[matchmod.basematcher] = None, | |||
clean: bool = False, | ||||
) -> Dict[ | ||||
bytes, | ||||
Optional[ | ||||
Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]] | ||||
], | ||||
]: | ||||
Augie Fackler
|
r46554 | """Finds changes between the current manifest and m2. | ||
Augie Fackler
|
r23756 | |||
Args: | ||||
m2: the manifest to which this manifest should be compared. | ||||
clean: if true, include files unchanged between these manifests | ||||
with a None value in the returned dictionary. | ||||
The result is returned as a dict with filename as key and | ||||
values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the | ||||
nodeid in the current/other manifest and fl1/fl2 is the flag | ||||
in the current/other manifest. Where the file does not exist, | ||||
the nodeid will be None and the flags will be the empty | ||||
string. | ||||
Augie Fackler
|
r46554 | """ | ||
Durham Goode
|
r31255 | if match: | ||
Augie Fackler
|
r44826 | m1 = self._matches(match) | ||
m2 = m2._matches(match) | ||||
Durham Goode
|
r31255 | return m1.diff(m2, clean=clean) | ||
Augie Fackler
|
r24225 | return self._lm.diff(m2._lm, clean) | ||
r52667 | def setflag(self, key: bytes, flag: bytes) -> None: | |||
Joerg Sonnenberger
|
r45678 | if flag not in _manifestflags: | ||
raise TypeError(b"Invalid manifest flag set.") | ||||
Augie Fackler
|
r24225 | self._lm[key] = self[key], flag | ||
r52667 | def get(self, key: bytes, default=None) -> Optional[bytes]: | |||
Augie Fackler
|
r24225 | try: | ||
return self._lm[key][0] | ||||
except KeyError: | ||||
return default | ||||
Martin von Zweigbergk
|
r22965 | |||
r52667 | def flags(self, key: bytes) -> bytes: | |||
Augie Fackler
|
r24225 | try: | ||
return self._lm[key][1] | ||||
except KeyError: | ||||
Augie Fackler
|
r44725 | return b'' | ||
Martin von Zweigbergk
|
r22965 | |||
Matt Harbison
|
r52960 | def copy(self) -> 'manifestdict': | ||
Joerg Sonnenberger
|
r47817 | c = manifestdict(self._nodelen) | ||
Augie Fackler
|
r24225 | c._lm = self._lm.copy() | ||
return c | ||||
r52667 | def items(self) -> Iterator[Tuple[bytes, bytes]]: | |||
Martin von Zweigbergk
|
r24298 | return (x[:2] for x in self._lm.iterentries()) | ||
Matt Mackall
|
r2831 | |||
r52667 | def iteritems(self) -> Iterator[Tuple[bytes, bytes]]: | |||
Augie Fackler
|
r38679 | return (x[:2] for x in self._lm.iterentries()) | ||
Augie Fackler
|
r32550 | |||
r52667 | def iterentries(self) -> Iterator[Tuple[bytes, bytes, bytes]]: | |||
Martin von Zweigbergk
|
r28203 | return self._lm.iterentries() | ||
r52667 | def text(self) -> ByteString: | |||
Augie Fackler
|
r36391 | # most likely uses native version | ||
return self._lm.text() | ||||
Augie Fackler
|
r22408 | |||
r52667 | def fastdelta( | |||
self, base: ByteString, changes: Iterable[Tuple[bytes, bool]] | ||||
) -> Tuple[ByteString, ByteString]: | ||||
Martin von Zweigbergk
|
r31787 | """Given a base manifest text as a bytearray and a list of changes | ||
Augie Fackler
|
r22931 | relative to that text, compute a delta that can be used by revlog. | ||
""" | ||||
delta = [] | ||||
dstart = None | ||||
dend = None | ||||
Augie Fackler
|
r43347 | dline = [b""] | ||
Augie Fackler
|
r22931 | start = 0 | ||
# zero copy representation of base as a buffer | ||||
addbuf = util.buffer(base) | ||||
Durham Goode
|
r26871 | changes = list(changes) | ||
Matt Harbison
|
r42568 | if len(changes) < FASTDELTA_TEXTDIFF_THRESHOLD: | ||
Durham Goode
|
r26871 | # start with a readonly loop that finds the offset of | ||
# each line and creates the deltas | ||||
for f, todelete in changes: | ||||
# bs will either be the index of the item or the insert point | ||||
start, end = _msearch(addbuf, f, start) | ||||
if not todelete: | ||||
h, fl = self._lm[f] | ||||
Augie Fackler
|
r43347 | l = b"%s\0%s%s\n" % (f, hex(h), fl) | ||
Durham Goode
|
r26871 | else: | ||
if start == end: | ||||
# item we want to delete was not found, error out | ||||
raise AssertionError( | ||||
Augie Fackler
|
r43347 | _(b"failed to remove %s from manifest") % f | ||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r43347 | l = b"" | ||
Durham Goode
|
r26871 | if dstart is not None and dstart <= start and dend >= start: | ||
if dend < end: | ||||
dend = end | ||||
if l: | ||||
dline.append(l) | ||||
else: | ||||
if dstart is not None: | ||||
r52662 | delta.append((dstart, dend, b"".join(dline))) | |||
Durham Goode
|
r26871 | dstart = start | ||
Augie Fackler
|
r22931 | dend = end | ||
Durham Goode
|
r26871 | dline = [l] | ||
Augie Fackler
|
r22931 | |||
Durham Goode
|
r26871 | if dstart is not None: | ||
r52662 | delta.append((dstart, dend, b"".join(dline))) | |||
Durham Goode
|
r26871 | # apply the delta to the base, and get a delta for addrevision | ||
deltatext, arraytext = _addlistdelta(base, delta) | ||||
else: | ||||
# For large changes, it's much cheaper to just build the text and | ||||
# diff it. | ||||
Augie Fackler
|
r31346 | arraytext = bytearray(self.text()) | ||
deltatext = mdiff.textdiff( | ||||
Augie Fackler
|
r43346 | util.buffer(base), util.buffer(arraytext) | ||
) | ||||
Durham Goode
|
r26871 | |||
Augie Fackler
|
r22931 | return arraytext, deltatext | ||
Augie Fackler
|
r43346 | |||
r52667 | def _msearch( | |||
m: ByteString, s: bytes, lo: int = 0, hi: Optional[int] = None | ||||
) -> Tuple[int, int]: | ||||
Augie Fackler
|
r46554 | """return a tuple (start, end) that says where to find s within m. | ||
Augie Fackler
|
r22930 | |||
If the string is found m[start:end] are the line containing | ||||
that string. If start == end the string was not found and | ||||
they indicate the proper sorted insertion point. | ||||
r52667 | """ | |||
def advance(i: int, c: bytes): | ||||
Augie Fackler
|
r43346 | while i < lenm and m[i : i + 1] != c: | ||
Augie Fackler
|
r22930 | i += 1 | ||
return i | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r22930 | if not s: | ||
return (lo, lo) | ||||
lenm = len(m) | ||||
if not hi: | ||||
hi = lenm | ||||
while lo < hi: | ||||
mid = (lo + hi) // 2 | ||||
start = mid | ||||
Augie Fackler
|
r43347 | while start > 0 and m[start - 1 : start] != b'\n': | ||
Augie Fackler
|
r22930 | start -= 1 | ||
Augie Fackler
|
r43347 | end = advance(start, b'\0') | ||
Yuya Nishihara
|
r31650 | if bytes(m[start:end]) < s: | ||
Augie Fackler
|
r22930 | # we know that after the null there are 40 bytes of sha1 | ||
# this translates to the bisect lo = mid + 1 | ||||
Augie Fackler
|
r43347 | lo = advance(end + 40, b'\n') + 1 | ||
Augie Fackler
|
r22930 | else: | ||
# this translates to the bisect hi = mid | ||||
hi = start | ||||
Augie Fackler
|
r43347 | end = advance(lo, b'\0') | ||
Augie Fackler
|
r22930 | found = m[lo:end] | ||
if s == found: | ||||
# we know that after the null there are 40 bytes of sha1 | ||||
Augie Fackler
|
r43347 | end = advance(end + 40, b'\n') | ||
Augie Fackler
|
r22930 | return (lo, end + 1) | ||
else: | ||||
return (lo, lo) | ||||
Augie Fackler
|
r43346 | |||
r52667 | def _checkforbidden(l: Iterable[bytes]) -> None: | |||
Augie Fackler
|
r22408 | """Check filenames for illegal characters.""" | ||
for f in l: | ||||
Augie Fackler
|
r43347 | if b'\n' in f or b'\r' in f: | ||
Gregory Szorc
|
r39813 | raise error.StorageError( | ||
Augie Fackler
|
r43347 | _(b"'\\n' and '\\r' disallowed in filenames: %r") | ||
Augie Fackler
|
r43346 | % pycompat.bytestr(f) | ||
) | ||||
Augie Fackler
|
r22408 | |||
Augie Fackler
|
r22409 | # apply the changes collected during the bisect loop to our addlist | ||
# return a delta suitable for addrevision | ||||
r52667 | def _addlistdelta( | |||
addlist: ByteString, | ||||
x: Iterable[Tuple[int, int, bytes]], | ||||
) -> Tuple[bytes, ByteString]: | ||||
Augie Fackler
|
r22409 | # for large addlist arrays, building a new array is cheaper | ||
# than repeatedly modifying the existing one | ||||
currentposition = 0 | ||||
Augie Fackler
|
r31346 | newaddlist = bytearray() | ||
Augie Fackler
|
r22409 | |||
for start, end, content in x: | ||||
newaddlist += addlist[currentposition:start] | ||||
if content: | ||||
Augie Fackler
|
r31346 | newaddlist += bytearray(content) | ||
Augie Fackler
|
r22409 | |||
currentposition = end | ||||
newaddlist += addlist[currentposition:] | ||||
Augie Fackler
|
r43347 | deltatext = b"".join( | ||
struct.pack(b">lll", start, end, len(content)) + content | ||||
Augie Fackler
|
r43346 | for start, end, content in x | ||
) | ||||
Augie Fackler
|
r22409 | return deltatext, newaddlist | ||
Augie Fackler
|
r43346 | |||
r52667 | def _splittopdir(f: bytes) -> Tuple[bytes, bytes]: | |||
Augie Fackler
|
r43347 | if b'/' in f: | ||
dir, subpath = f.split(b'/', 1) | ||||
return dir + b'/', subpath | ||||
Martin von Zweigbergk
|
r24401 | else: | ||
Augie Fackler
|
r43347 | return b'', f | ||
Martin von Zweigbergk
|
r24401 | |||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r26402 | _noop = lambda s: None | ||
Martin von Zweigbergk
|
r25222 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52961 | class treemanifest: # (repository.imanifestdict) | ||
Matt Harbison
|
r52719 | _dir: bytes | ||
Matt Harbison
|
r52961 | _dirs: Dict[bytes, 'treemanifest'] | ||
Matt Harbison
|
r52719 | _dirty: bool | ||
_files: Dict[bytes, bytes] | ||||
_flags: Dict[bytes, bytes] | ||||
r52667 | def __init__(self, nodeconstants, dir: bytes = b'', text: bytes = b''): | |||
Martin von Zweigbergk
|
r24403 | self._dir = dir | ||
Joerg Sonnenberger
|
r47538 | self.nodeconstants = nodeconstants | ||
Joerg Sonnenberger
|
r47771 | self._node = self.nodeconstants.nullid | ||
Joerg Sonnenberger
|
r47817 | self._nodelen = self.nodeconstants.nodelen | ||
Augie Fackler
|
r26402 | self._loadfunc = _noop | ||
self._copyfunc = _noop | ||||
Martin von Zweigbergk
|
r25220 | self._dirty = False | ||
Matt Harbison
|
r52719 | self._dirs = {} | ||
r52667 | self._lazydirs: Dict[ | |||
bytes, | ||||
Matt Harbison
|
r52961 | Tuple[bytes, Callable[[bytes, bytes], 'treemanifest'], bool], | ||
r52667 | ] = {} | |||
Martin von Zweigbergk
|
r24401 | # Using _lazymanifest here is a little slower than plain old dicts | ||
Matt Harbison
|
r52719 | self._files = {} | ||
Martin von Zweigbergk
|
r24401 | self._flags = {} | ||
Martin von Zweigbergk
|
r25220 | if text: | ||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r25220 | def readsubtree(subdir, subm): | ||
Augie Fackler
|
r43346 | raise AssertionError( | ||
Martin von Zweigbergk
|
r43387 | b'treemanifest constructor only accepts flat manifests' | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r25220 | self.parse(text, readsubtree) | ||
Augie Fackler
|
r43346 | self._dirty = True # Mark flat manifest dirty after parsing | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def _subpath(self, path: bytes) -> bytes: | |||
Martin von Zweigbergk
|
r24403 | return self._dir + path | ||
r52667 | def _loadalllazy(self) -> None: | |||
spectral
|
r40075 | selfdirs = self._dirs | ||
Kyle Lippincott
|
r46711 | subpath = self._subpath | ||
Gregory Szorc
|
r49781 | for d, (node, readsubtree, docopy) in self._lazydirs.items(): | ||
spectral
|
r40075 | if docopy: | ||
Kyle Lippincott
|
r46711 | selfdirs[d] = readsubtree(subpath(d), node).copy() | ||
spectral
|
r40075 | else: | ||
Kyle Lippincott
|
r46711 | selfdirs[d] = readsubtree(subpath(d), node) | ||
r52663 | self._lazydirs.clear() | |||
spectral
|
r39551 | |||
r52667 | def _loadlazy(self, d: bytes) -> None: | |||
spectral
|
r40017 | v = self._lazydirs.get(d) | ||
r52664 | if v is not None: | |||
Kyle Lippincott
|
r46711 | node, readsubtree, docopy = v | ||
spectral
|
r40075 | if docopy: | ||
Kyle Lippincott
|
r46711 | self._dirs[d] = readsubtree(self._subpath(d), node).copy() | ||
spectral
|
r40075 | else: | ||
Kyle Lippincott
|
r46711 | self._dirs[d] = readsubtree(self._subpath(d), node) | ||
spectral
|
r40017 | del self._lazydirs[d] | ||
spectral
|
r39551 | |||
r52667 | def _loadchildrensetlazy( | |||
self, visit: Union[Set[bytes], bytes] | ||||
) -> Optional[Set[bytes]]: | ||||
Kyle Lippincott
|
r39557 | if not visit: | ||
return None | ||||
Augie Fackler
|
r43347 | if visit == b'all' or visit == b'this': | ||
Kyle Lippincott
|
r39557 | self._loadalllazy() | ||
return None | ||||
r52667 | visit = cast(Set[bytes], visit) | |||
spectral
|
r40018 | loadlazy = self._loadlazy | ||
Kyle Lippincott
|
r39557 | for k in visit: | ||
Augie Fackler
|
r43347 | loadlazy(k + b'/') | ||
Kyle Lippincott
|
r39557 | return visit | ||
Matt Harbison
|
r52961 | def _loaddifflazy(self, t1: 'treemanifest', t2: 'treemanifest'): | ||
spectral
|
r40074 | """load items in t1 and t2 if they're needed for diffing. | ||
The criteria currently is: | ||||
- if it's not present in _lazydirs in either t1 or t2, load it in the | ||||
other (it may already be loaded or it may not exist, doesn't matter) | ||||
- if it's present in _lazydirs in both, compare the nodeid; if it | ||||
differs, load it in both | ||||
""" | ||||
toloadlazy = [] | ||||
Gregory Szorc
|
r49768 | for d, v1 in t1._lazydirs.items(): | ||
spectral
|
r40074 | v2 = t2._lazydirs.get(d) | ||
r52665 | if v2 is None or v2[0] != v1[0]: | |||
spectral
|
r40074 | toloadlazy.append(d) | ||
Gregory Szorc
|
r49768 | for d, v1 in t2._lazydirs.items(): | ||
spectral
|
r40074 | if d not in t1._lazydirs: | ||
toloadlazy.append(d) | ||||
for d in toloadlazy: | ||||
t1._loadlazy(d) | ||||
t2._loadlazy(d) | ||||
r52667 | def __len__(self) -> int: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | size = len(self._files) | ||
spectral
|
r39551 | self._loadalllazy() | ||
Martin von Zweigbergk
|
r24401 | for m in self._dirs.values(): | ||
size += m.__len__() | ||||
return size | ||||
r52667 | def __nonzero__(self) -> bool: | |||
# Faster than "__len__() != 0" since it avoids loading sub-manifests | ||||
Martin von Zweigbergk
|
r36192 | return not self._isempty() | ||
__bool__ = __nonzero__ | ||||
r52667 | def _isempty(self) -> bool: | |||
Augie Fackler
|
r43346 | self._load() # for consistency; already loaded by all callers | ||
Kyle Lippincott
|
r39552 | # See if we can skip loading everything. | ||
Augie Fackler
|
r43346 | if self._files or ( | ||
self._dirs and any(not m._isempty() for m in self._dirs.values()) | ||||
): | ||||
Kyle Lippincott
|
r39552 | return False | ||
spectral
|
r39551 | self._loadalllazy() | ||
Augie Fackler
|
r43346 | return not self._dirs or all(m._isempty() for m in self._dirs.values()) | ||
Drew Gottlieb
|
r24551 | |||
Kyle Lippincott
|
r44742 | @encoding.strmethod | ||
r52667 | def __repr__(self) -> bytes: | |||
r43663 | return ( | |||
Kyle Lippincott
|
r44742 | b'<treemanifest dir=%s, node=%s, loaded=%r, dirty=%r at 0x%x>' | ||
r43663 | % ( | |||
self._dir, | ||||
hex(self._node), | ||||
bool(self._loadfunc is _noop), | ||||
self._dirty, | ||||
id(self), | ||||
) | ||||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r25091 | |||
r52667 | def dir(self) -> bytes: | |||
Augie Fackler
|
r46554 | """The directory that this tree manifest represents, including a | ||
trailing '/'. Empty string for the repo root directory.""" | ||||
Martin von Zweigbergk
|
r25091 | return self._dir | ||
r52667 | def node(self) -> bytes: | |||
Augie Fackler
|
r46554 | """This node of this instance. nullid for unsaved instances. Should | ||
Martin von Zweigbergk
|
r25091 | be updated when the instance is read or written from a revlog. | ||
Augie Fackler
|
r46554 | """ | ||
Martin von Zweigbergk
|
r25220 | assert not self._dirty | ||
Martin von Zweigbergk
|
r25091 | return self._node | ||
r52667 | def setnode(self, node: bytes) -> None: | |||
Martin von Zweigbergk
|
r25091 | self._node = node | ||
Martin von Zweigbergk
|
r25220 | self._dirty = False | ||
Martin von Zweigbergk
|
r24403 | |||
r52667 | def iterentries( | |||
self, | ||||
Matt Harbison
|
r52961 | ) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest'], bytes]]: | ||
Martin von Zweigbergk
|
r28206 | self._load() | ||
spectral
|
r39551 | self._loadalllazy() | ||
Augie Fackler
|
r43346 | for p, n in sorted( | ||
itertools.chain(self._dirs.items(), self._files.items()) | ||||
): | ||||
Martin von Zweigbergk
|
r28206 | if p in self._files: | ||
Augie Fackler
|
r43347 | yield self._subpath(p), n, self._flags.get(p, b'') | ||
Martin von Zweigbergk
|
r28206 | else: | ||
for x in n.iterentries(): | ||||
yield x | ||||
Matt Harbison
|
r52961 | def items(self) -> Iterator[Tuple[bytes, Union[bytes, 'treemanifest']]]: | ||
Martin von Zweigbergk
|
r25222 | self._load() | ||
spectral
|
r39551 | self._loadalllazy() | ||
Augie Fackler
|
r43346 | for p, n in sorted( | ||
itertools.chain(self._dirs.items(), self._files.items()) | ||||
): | ||||
Martin von Zweigbergk
|
r24401 | if p in self._files: | ||
Martin von Zweigbergk
|
r24403 | yield self._subpath(p), n | ||
Martin von Zweigbergk
|
r24401 | else: | ||
Gregory Szorc
|
r49768 | for f, sn in n.items(): | ||
Martin von Zweigbergk
|
r24403 | yield f, sn | ||
Martin von Zweigbergk
|
r24401 | |||
Augie Fackler
|
r32550 | iteritems = items | ||
r52667 | def iterkeys(self) -> Iterator[bytes]: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
spectral
|
r39551 | self._loadalllazy() | ||
Augie Fackler
|
r32536 | for p in sorted(itertools.chain(self._dirs, self._files)): | ||
Martin von Zweigbergk
|
r24401 | if p in self._files: | ||
Martin von Zweigbergk
|
r24403 | yield self._subpath(p) | ||
Martin von Zweigbergk
|
r24401 | else: | ||
Pulkit Goyal
|
r35604 | for f in self._dirs[p]: | ||
Martin von Zweigbergk
|
r24403 | yield f | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def keys(self) -> List[bytes]: | |||
Martin von Zweigbergk
|
r24401 | return list(self.iterkeys()) | ||
r52667 | def __iter__(self) -> Iterator[bytes]: | |||
Martin von Zweigbergk
|
r24401 | return self.iterkeys() | ||
r52667 | def __contains__(self, f: bytes) -> bool: | |||
Martin von Zweigbergk
|
r24401 | if f is None: | ||
return False | ||||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | if dir not in self._dirs: | ||
return False | ||||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | return self._dirs[dir].__contains__(subpath) | ||
else: | ||||
return f in self._files | ||||
r52667 | def get(self, f: bytes, default: Optional[bytes] = None) -> Optional[bytes]: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | if dir not in self._dirs: | ||
return default | ||||
return self._dirs[dir].get(subpath, default) | ||||
else: | ||||
return self._files.get(f, default) | ||||
r52667 | def __getitem__(self, f: bytes) -> bytes: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | return self._dirs[dir].__getitem__(subpath) | ||
else: | ||||
return self._files[f] | ||||
r52667 | def flags(self, f: bytes) -> bytes: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | if dir not in self._dirs: | ||
Augie Fackler
|
r43347 | return b'' | ||
Martin von Zweigbergk
|
r24401 | return self._dirs[dir].flags(subpath) | ||
else: | ||||
spectral
|
r39551 | if f in self._lazydirs or f in self._dirs: | ||
Augie Fackler
|
r43347 | return b'' | ||
return self._flags.get(f, b'') | ||||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def find(self, f: bytes) -> Tuple[bytes, bytes]: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | return self._dirs[dir].find(subpath) | ||
else: | ||||
Augie Fackler
|
r43347 | return self._files[f], self._flags.get(f, b'') | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def __delitem__(self, f: bytes) -> None: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
spectral
|
r39551 | |||
Martin von Zweigbergk
|
r24401 | self._dirs[dir].__delitem__(subpath) | ||
# If the directory is now empty, remove it | ||||
Drew Gottlieb
|
r24551 | if self._dirs[dir]._isempty(): | ||
Martin von Zweigbergk
|
r24401 | del self._dirs[dir] | ||
else: | ||||
del self._files[f] | ||||
if f in self._flags: | ||||
del self._flags[f] | ||||
Martin von Zweigbergk
|
r25220 | self._dirty = True | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def set(self, f: bytes, node: bytes, flags: bytes) -> None: | |||
Arseniy Alekseyev
|
r52651 | """Set both the node and the flags for path f.""" | ||
assert node is not None | ||||
if flags not in _manifestflags: | ||||
raise TypeError(b"Invalid manifest flag set.") | ||||
self._load() | ||||
dir, subpath = _splittopdir(f) | ||||
if dir: | ||||
self._loadlazy(dir) | ||||
if dir not in self._dirs: | ||||
self._dirs[dir] = treemanifest( | ||||
self.nodeconstants, self._subpath(dir) | ||||
) | ||||
self._dirs[dir].set(subpath, node, flags) | ||||
else: | ||||
assert len(node) in (20, 32) | ||||
self._files[f] = node | ||||
self._flags[f] = flags | ||||
self._dirty = True | ||||
r52667 | def __setitem__(self, f: bytes, n: bytes) -> None: | |||
Martin von Zweigbergk
|
r24401 | assert n is not None | ||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
Martin von Zweigbergk
|
r24401 | if dir not in self._dirs: | ||
Joerg Sonnenberger
|
r47538 | self._dirs[dir] = treemanifest( | ||
self.nodeconstants, self._subpath(dir) | ||||
) | ||||
Martin von Zweigbergk
|
r24401 | self._dirs[dir].__setitem__(subpath, n) | ||
else: | ||||
Augie Fackler
|
r45193 | # manifest nodes are either 20 bytes or 32 bytes, | ||
Joerg Sonnenberger
|
r45678 | # depending on the hash in use. Assert this as historically | ||
# sometimes extra bytes were added. | ||||
assert len(n) in (20, 32) | ||||
self._files[f] = n | ||||
Martin von Zweigbergk
|
r25220 | self._dirty = True | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def _load(self) -> None: | |||
Augie Fackler
|
r26402 | if self._loadfunc is not _noop: | ||
lf, self._loadfunc = self._loadfunc, _noop | ||||
lf(self) | ||||
elif self._copyfunc is not _noop: | ||||
cf, self._copyfunc = self._copyfunc, _noop | ||||
cf(self) | ||||
r52667 | def setflag(self, f: bytes, flags: bytes) -> None: | |||
Martin von Zweigbergk
|
r24401 | """Set the flags (symlink, executable) for path f.""" | ||
Joerg Sonnenberger
|
r45678 | if flags not in _manifestflags: | ||
raise TypeError(b"Invalid manifest flag set.") | ||||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24401 | dir, subpath = _splittopdir(f) | ||
if dir: | ||||
spectral
|
r40019 | self._loadlazy(dir) | ||
Martin von Zweigbergk
|
r24401 | if dir not in self._dirs: | ||
Joerg Sonnenberger
|
r47538 | self._dirs[dir] = treemanifest( | ||
self.nodeconstants, self._subpath(dir) | ||||
) | ||||
Martin von Zweigbergk
|
r24401 | self._dirs[dir].setflag(subpath, flags) | ||
else: | ||||
self._flags[f] = flags | ||||
Martin von Zweigbergk
|
r25220 | self._dirty = True | ||
Martin von Zweigbergk
|
r24401 | |||
Matt Harbison
|
r52961 | def copy(self) -> 'treemanifest': | ||
Joerg Sonnenberger
|
r47538 | copy = treemanifest(self.nodeconstants, self._dir) | ||
Martin von Zweigbergk
|
r25091 | copy._node = self._node | ||
Martin von Zweigbergk
|
r25220 | copy._dirty = self._dirty | ||
Augie Fackler
|
r26402 | if self._copyfunc is _noop: | ||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r26402 | def _copyfunc(s): | ||
self._load() | ||||
Augie Fackler
|
r43346 | s._lazydirs = { | ||
Gregory Szorc
|
r49768 | d: (n, r, True) for d, (n, r, c) in self._lazydirs.items() | ||
Augie Fackler
|
r43346 | } | ||
spectral
|
r39551 | sdirs = s._dirs | ||
Gregory Szorc
|
r49768 | for d, v in self._dirs.items(): | ||
spectral
|
r39551 | sdirs[d] = v.copy() | ||
Augie Fackler
|
r26402 | s._files = dict.copy(self._files) | ||
s._flags = dict.copy(self._flags) | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r26402 | if self._loadfunc is _noop: | ||
_copyfunc(copy) | ||||
else: | ||||
copy._copyfunc = _copyfunc | ||||
else: | ||||
copy._copyfunc = self._copyfunc | ||||
Martin von Zweigbergk
|
r24401 | return copy | ||
r52667 | def filesnotin( | |||
Matt Harbison
|
r52961 | self, m2: 'treemanifest', match: Optional[matchmod.basematcher] = None | ||
r52667 | ) -> Set[bytes]: | |||
Martin von Zweigbergk
|
r24401 | '''Set of files in this manifest that are not in the other''' | ||
Kyle Lippincott
|
r39553 | if match and not match.always(): | ||
Augie Fackler
|
r44826 | m1 = self._matches(match) | ||
m2 = m2._matches(match) | ||||
Durham Goode
|
r31255 | return m1.filesnotin(m2) | ||
Martin von Zweigbergk
|
r24405 | files = set() | ||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r24405 | def _filesnotin(t1, t2): | ||
Martin von Zweigbergk
|
r25220 | if t1._node == t2._node and not t1._dirty and not t2._dirty: | ||
return | ||||
Martin von Zweigbergk
|
r25222 | t1._load() | ||
t2._load() | ||||
spectral
|
r40074 | self._loaddifflazy(t1, t2) | ||
Gregory Szorc
|
r49768 | for d, m1 in t1._dirs.items(): | ||
Martin von Zweigbergk
|
r24405 | if d in t2._dirs: | ||
m2 = t2._dirs[d] | ||||
_filesnotin(m1, m2) | ||||
else: | ||||
files.update(m1.iterkeys()) | ||||
Augie Fackler
|
r36314 | for fn in t1._files: | ||
Martin von Zweigbergk
|
r24405 | if fn not in t2._files: | ||
files.add(t1._subpath(fn)) | ||||
_filesnotin(self, m2) | ||||
Martin von Zweigbergk
|
r24401 | return files | ||
@propertycache | ||||
r52667 | def _alldirs(self) -> pathutil.dirs: | |||
r43923 | return pathutil.dirs(self) | |||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def dirs(self) -> pathutil.dirs: | |||
Martin von Zweigbergk
|
r24401 | return self._alldirs | ||
r52667 | def hasdir(self, dir: bytes) -> bool: | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r24406 | topdir, subdir = _splittopdir(dir) | ||
if topdir: | ||||
spectral
|
r40019 | self._loadlazy(topdir) | ||
Martin von Zweigbergk
|
r24406 | if topdir in self._dirs: | ||
return self._dirs[topdir].hasdir(subdir) | ||||
return False | ||||
Augie Fackler
|
r43347 | dirslash = dir + b'/' | ||
spectral
|
r39551 | return dirslash in self._dirs or dirslash in self._lazydirs | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def walk(self, match: matchmod.basematcher) -> Iterator[bytes]: | |||
Augie Fackler
|
r46554 | """Generates matching file names. | ||
Drew Gottlieb
|
r24646 | |||
It also reports nonexistent files by marking them bad with match.bad(). | ||||
Augie Fackler
|
r46554 | """ | ||
Martin von Zweigbergk
|
r24683 | if match.always(): | ||
for f in iter(self): | ||||
yield f | ||||
return | ||||
Drew Gottlieb
|
r24646 | fset = set(match.files()) | ||
Drew Gottlieb
|
r24647 | for fn in self._walk(match): | ||
Drew Gottlieb
|
r24646 | if fn in fset: | ||
# specified pattern is the exact name | ||||
fset.remove(fn) | ||||
Drew Gottlieb
|
r24647 | yield fn | ||
Drew Gottlieb
|
r24646 | |||
Martin von Zweigbergk
|
r42528 | # for dirstate.walk, files=[''] means "walk the whole tree". | ||
Drew Gottlieb
|
r24646 | # follow that here, too | ||
Augie Fackler
|
r43347 | fset.discard(b'') | ||
Drew Gottlieb
|
r24646 | |||
for fn in sorted(fset): | ||||
if not self.hasdir(fn): | ||||
match.bad(fn, None) | ||||
r52667 | def _walk(self, match: matchmod.basematcher) -> Iterator[bytes]: | |||
Drew Gottlieb
|
r25188 | '''Recursively generates matching file names for walk().''' | ||
Martin von Zweigbergk
|
r42528 | visit = match.visitchildrenset(self._dir[:-1]) | ||
Kyle Lippincott
|
r39558 | if not visit: | ||
Drew Gottlieb
|
r25188 | return | ||
Drew Gottlieb
|
r24647 | |||
# yield this dir's files and walk its submanifests | ||||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Kyle Lippincott
|
r39558 | visit = self._loadchildrensetlazy(visit) | ||
Augie Fackler
|
r36315 | for p in sorted(list(self._dirs) + list(self._files)): | ||
Drew Gottlieb
|
r24647 | if p in self._files: | ||
fullp = self._subpath(p) | ||||
if match(fullp): | ||||
yield fullp | ||||
else: | ||||
Kyle Lippincott
|
r39558 | if not visit or p[:-1] in visit: | ||
for f in self._dirs[p]._walk(match): | ||||
yield f | ||||
Drew Gottlieb
|
r24647 | |||
Matt Harbison
|
r52961 | def _matches(self, match: matchmod.basematcher) -> 'treemanifest': | ||
Augie Fackler
|
r46554 | """recursively generate a new manifest filtered by the match argument.""" | ||
Augie Fackler
|
r44826 | if match.always(): | ||
return self.copy() | ||||
return self._matches_inner(match) | ||||
Matt Harbison
|
r52961 | def _matches_inner(self, match: matchmod.basematcher) -> 'treemanifest': | ||
Augie Fackler
|
r44826 | if match.always(): | ||
return self.copy() | ||||
Martin von Zweigbergk
|
r27343 | |||
Martin von Zweigbergk
|
r42528 | visit = match.visitchildrenset(self._dir[:-1]) | ||
Augie Fackler
|
r43347 | if visit == b'all': | ||
Martin von Zweigbergk
|
r27343 | return self.copy() | ||
Joerg Sonnenberger
|
r47538 | ret = treemanifest(self.nodeconstants, self._dir) | ||
Martin von Zweigbergk
|
r27343 | if not visit: | ||
Drew Gottlieb
|
r25188 | return ret | ||
Drew Gottlieb
|
r24552 | |||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Drew Gottlieb
|
r24552 | for fn in self._files: | ||
Kyle Lippincott
|
r39557 | # While visitchildrenset *usually* lists only subdirs, this is | ||
# actually up to the matcher and may have some files in the set(). | ||||
# If visit == 'this', we should obviously look at the files in this | ||||
# directory; if visit is a set, and fn is in it, we should inspect | ||||
# fn (but no need to inspect things not in the set). | ||||
Augie Fackler
|
r43347 | if visit != b'this' and fn not in visit: | ||
Kyle Lippincott
|
r39557 | continue | ||
Drew Gottlieb
|
r24552 | fullp = self._subpath(fn) | ||
Kyle Lippincott
|
r39557 | # visitchildrenset isn't perfect, we still need to call the regular | ||
# matcher code to further filter results. | ||||
Drew Gottlieb
|
r24552 | if not match(fullp): | ||
continue | ||||
ret._files[fn] = self._files[fn] | ||||
if fn in self._flags: | ||||
ret._flags[fn] = self._flags[fn] | ||||
Kyle Lippincott
|
r39557 | visit = self._loadchildrensetlazy(visit) | ||
Gregory Szorc
|
r49768 | for dir, subm in self._dirs.items(): | ||
Kyle Lippincott
|
r39557 | if visit and dir[:-1] not in visit: | ||
continue | ||||
Augie Fackler
|
r44826 | m = subm._matches_inner(match) | ||
Drew Gottlieb
|
r24552 | if not m._isempty(): | ||
ret._dirs[dir] = m | ||||
Martin von Zweigbergk
|
r25220 | if not ret._isempty(): | ||
ret._dirty = True | ||||
Drew Gottlieb
|
r24552 | return ret | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def fastdelta( | |||
self, base: ByteString, changes: Iterable[Tuple[bytes, bool]] | ||||
) -> ByteString: | ||||
Augie Fackler
|
r45154 | raise FastdeltaUnavailable() | ||
r52667 | def diff( | |||
self, | ||||
Matt Harbison
|
r52961 | m2: 'treemanifest', | ||
r52667 | match: Optional[matchmod.basematcher] = None, | |||
clean: bool = False, | ||||
) -> Dict[ | ||||
bytes, | ||||
Optional[ | ||||
Tuple[Tuple[Optional[bytes], bytes], Tuple[Optional[bytes], bytes]] | ||||
], | ||||
]: | ||||
Augie Fackler
|
r46554 | """Finds changes between the current manifest and m2. | ||
Martin von Zweigbergk
|
r24401 | |||
Args: | ||||
m2: the manifest to which this manifest should be compared. | ||||
clean: if true, include files unchanged between these manifests | ||||
with a None value in the returned dictionary. | ||||
The result is returned as a dict with filename as key and | ||||
values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the | ||||
nodeid in the current/other manifest and fl1/fl2 is the flag | ||||
in the current/other manifest. Where the file does not exist, | ||||
the nodeid will be None and the flags will be the empty | ||||
string. | ||||
Augie Fackler
|
r46554 | """ | ||
Kyle Lippincott
|
r39553 | if match and not match.always(): | ||
Augie Fackler
|
r44826 | m1 = self._matches(match) | ||
m2 = m2._matches(match) | ||||
Durham Goode
|
r31255 | return m1.diff(m2, clean=clean) | ||
Martin von Zweigbergk
|
r24404 | result = {} | ||
Joerg Sonnenberger
|
r47538 | emptytree = treemanifest(self.nodeconstants) | ||
Pulkit Goyal
|
r41188 | |||
def _iterativediff(t1, t2, stack): | ||||
"""compares two tree manifests and append new tree-manifests which | ||||
needs to be compared to stack""" | ||||
Martin von Zweigbergk
|
r25220 | if t1._node == t2._node and not t1._dirty and not t2._dirty: | ||
return | ||||
Martin von Zweigbergk
|
r25222 | t1._load() | ||
t2._load() | ||||
spectral
|
r40074 | self._loaddifflazy(t1, t2) | ||
spectral
|
r40020 | |||
Gregory Szorc
|
r49768 | for d, m1 in t1._dirs.items(): | ||
Martin von Zweigbergk
|
r24404 | m2 = t2._dirs.get(d, emptytree) | ||
Pulkit Goyal
|
r41188 | stack.append((m1, m2)) | ||
Martin von Zweigbergk
|
r24404 | |||
Gregory Szorc
|
r49768 | for d, m2 in t2._dirs.items(): | ||
Martin von Zweigbergk
|
r24404 | if d not in t1._dirs: | ||
Pulkit Goyal
|
r41188 | stack.append((emptytree, m2)) | ||
Martin von Zweigbergk
|
r24401 | |||
Gregory Szorc
|
r49768 | for fn, n1 in t1._files.items(): | ||
Augie Fackler
|
r43347 | fl1 = t1._flags.get(fn, b'') | ||
Martin von Zweigbergk
|
r24404 | n2 = t2._files.get(fn, None) | ||
Augie Fackler
|
r43347 | fl2 = t2._flags.get(fn, b'') | ||
Martin von Zweigbergk
|
r24404 | if n1 != n2 or fl1 != fl2: | ||
result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2)) | ||||
elif clean: | ||||
result[t1._subpath(fn)] = None | ||||
Martin von Zweigbergk
|
r24401 | |||
Gregory Szorc
|
r49768 | for fn, n2 in t2._files.items(): | ||
Martin von Zweigbergk
|
r24404 | if fn not in t1._files: | ||
Augie Fackler
|
r43347 | fl2 = t2._flags.get(fn, b'') | ||
result[t2._subpath(fn)] = ((None, b''), (n2, fl2)) | ||||
Martin von Zweigbergk
|
r24401 | |||
Pulkit Goyal
|
r41188 | stackls = [] | ||
_iterativediff(self, m2, stackls) | ||||
while stackls: | ||||
t1, t2 = stackls.pop() | ||||
# stackls is populated in the function call | ||||
_iterativediff(t1, t2, stackls) | ||||
Martin von Zweigbergk
|
r24404 | return result | ||
Martin von Zweigbergk
|
r24401 | |||
Matt Harbison
|
r52961 | def unmodifiedsince(self, m2: 'treemanifest') -> bool: | ||
Martin von Zweigbergk
|
r25221 | return not self._dirty and not m2._dirty and self._node == m2._node | ||
r52667 | def parse( | |||
self, | ||||
text: bytes, | ||||
Matt Harbison
|
r52961 | readsubtree: Callable[[bytes, bytes], 'treemanifest'], | ||
r52667 | ) -> None: | |||
spectral
|
r39551 | selflazy = self._lazydirs | ||
Joerg Sonnenberger
|
r47817 | for f, n, fl in _parse(self._nodelen, text): | ||
Augie Fackler
|
r43347 | if fl == b't': | ||
f = f + b'/' | ||||
spectral
|
r40075 | # False below means "doesn't need to be copied" and can use the | ||
# cached value from readsubtree directly. | ||||
Kyle Lippincott
|
r46711 | selflazy[f] = (n, readsubtree, False) | ||
Augie Fackler
|
r43347 | elif b'/' in f: | ||
Martin von Zweigbergk
|
r25220 | # This is a flat manifest, so use __setitem__ and setflag rather | ||
# than assigning directly to _files and _flags, so we can | ||||
# assign a path in a subdirectory, and to mark dirty (compared | ||||
# to nullid). | ||||
Martin von Zweigbergk
|
r25091 | self[f] = n | ||
if fl: | ||||
self.setflag(f, fl) | ||||
Martin von Zweigbergk
|
r25220 | else: | ||
# Assigning to _files and _flags avoids marking as dirty, | ||||
# and should be a little faster. | ||||
self._files[f] = n | ||||
if fl: | ||||
self._flags[f] = fl | ||||
Martin von Zweigbergk
|
r24781 | |||
r52667 | def text(self) -> ByteString: | |||
Martin von Zweigbergk
|
r24401 | """Get the full data of this manifest as a bytestring.""" | ||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Augie Fackler
|
r36391 | return _text(self.iterentries()) | ||
Martin von Zweigbergk
|
r24401 | |||
r52667 | def dirtext(self) -> ByteString: | |||
Martin von Zweigbergk
|
r25091 | """Get the full data of this directory as a bytestring. Make sure that | ||
any submanifests have been written first, so their nodeids are correct. | ||||
""" | ||||
Martin von Zweigbergk
|
r25222 | self._load() | ||
Martin von Zweigbergk
|
r25091 | flags = self.flags | ||
Gregory Szorc
|
r49768 | lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()] | ||
Augie Fackler
|
r43347 | dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs] | ||
Martin von Zweigbergk
|
r25091 | files = [(f, self._files[f], flags(f)) for f in self._files] | ||
spectral
|
r39551 | return _text(sorted(dirs + files + lazydirs)) | ||
Martin von Zweigbergk
|
r25091 | |||
r52667 | def read( | |||
self, | ||||
gettext: Callable[[], ByteString], | ||||
Matt Harbison
|
r52961 | readsubtree: Callable[[bytes, bytes], 'treemanifest'], | ||
r52667 | ) -> None: | |||
Augie Fackler
|
r26402 | def _load_for_read(s): | ||
s.parse(gettext(), readsubtree) | ||||
s._dirty = False | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r26402 | self._loadfunc = _load_for_read | ||
Martin von Zweigbergk
|
r25222 | |||
r52667 | def writesubtrees( | |||
self, | ||||
Matt Harbison
|
r52961 | m1: 'treemanifest', | ||
m2: 'treemanifest', | ||||
r52667 | writesubtree: Callable[ | |||
[ | ||||
Matt Harbison
|
r52961 | Callable[['treemanifest'], None], | ||
r52667 | bytes, | |||
bytes, | ||||
matchmod.basematcher, | ||||
], | ||||
None, | ||||
], | ||||
match: matchmod.basematcher, | ||||
) -> None: | ||||
Augie Fackler
|
r43346 | self._load() # for consistency; should never have any effect here | ||
Durham Goode
|
r29888 | m1._load() | ||
m2._load() | ||||
Joerg Sonnenberger
|
r47538 | emptytree = treemanifest(self.nodeconstants) | ||
Augie Fackler
|
r43346 | |||
Kyle Lippincott
|
r39554 | def getnode(m, d): | ||
ld = m._lazydirs.get(d) | ||||
if ld: | ||||
Kyle Lippincott
|
r46711 | return ld[0] | ||
r52666 | tree = m._dirs.get(d, emptytree) | |||
assert tree is not None # helps pytype | ||||
return tree._node | ||||
Kyle Lippincott
|
r39554 | |||
spectral
|
r39704 | # let's skip investigating things that `match` says we do not need. | ||
Martin von Zweigbergk
|
r42528 | visit = match.visitchildrenset(self._dir[:-1]) | ||
spectral
|
r40076 | visit = self._loadchildrensetlazy(visit) | ||
Augie Fackler
|
r43347 | if visit == b'this' or visit == b'all': | ||
spectral
|
r39704 | visit = None | ||
Gregory Szorc
|
r49768 | for d, subm in self._dirs.items(): | ||
spectral
|
r39704 | if visit and d[:-1] not in visit: | ||
continue | ||||
Kyle Lippincott
|
r39554 | subp1 = getnode(m1, d) | ||
subp2 = getnode(m2, d) | ||||
Joerg Sonnenberger
|
r47771 | if subp1 == self.nodeconstants.nullid: | ||
Martin von Zweigbergk
|
r25091 | subp1, subp2 = subp2, subp1 | ||
spectral
|
r39704 | writesubtree(subm, subp1, subp2, match) | ||
Martin von Zweigbergk
|
r25091 | |||
r52667 | def walksubtrees( | |||
self, matcher: Optional[matchmod.basematcher] = None | ||||
Matt Harbison
|
r52961 | ) -> Iterator['treemanifest']: | ||
Durham Goode
|
r31876 | """Returns an iterator of the subtrees of this manifest, including this | ||
manifest itself. | ||||
If `matcher` is provided, it only returns subtrees that match. | ||||
""" | ||||
Martin von Zweigbergk
|
r42528 | if matcher and not matcher.visitdir(self._dir[:-1]): | ||
Durham Goode
|
r31876 | return | ||
if not matcher or matcher(self._dir[:-1]): | ||||
yield self | ||||
self._load() | ||||
spectral
|
r39551 | # OPT: use visitchildrenset to avoid loading everything. | ||
self._loadalllazy() | ||||
Gregory Szorc
|
r49768 | for d, subm in self._dirs.items(): | ||
Durham Goode
|
r31876 | for subtree in subm.walksubtrees(matcher=matcher): | ||
yield subtree | ||||
Augie Fackler
|
r43346 | |||
Martijn Pieters
|
r38803 | class manifestfulltextcache(util.lrucachedict): | ||
"""File-backed LRU cache for the manifest cache | ||||
File consists of entries, up to EOF: | ||||
- 20 bytes node, 4 bytes length, <length> manifest data | ||||
These are written in reverse cache order (oldest to newest). | ||||
""" | ||||
r42128 | ||||
Augie Fackler
|
r43347 | _file = b'manifestfulltextcache' | ||
r42128 | ||||
Martijn Pieters
|
r38803 | def __init__(self, max): | ||
super(manifestfulltextcache, self).__init__(max) | ||||
self._dirty = False | ||||
self._read = False | ||||
self._opener = None | ||||
def read(self): | ||||
if self._read or self._opener is None: | ||||
return | ||||
try: | ||||
r42128 | with self._opener(self._file) as fp: | |||
Martijn Pieters
|
r38803 | set = super(manifestfulltextcache, self).__setitem__ | ||
# ignore trailing data, this is a cache, corruption is skipped | ||||
while True: | ||||
Augie Fackler
|
r45202 | # TODO do we need to do work here for sha1 portability? | ||
Martijn Pieters
|
r38803 | node = fp.read(20) | ||
if len(node) < 20: | ||||
break | ||||
try: | ||||
Augie Fackler
|
r43347 | size = struct.unpack(b'>L', fp.read(4))[0] | ||
Martijn Pieters
|
r38803 | except struct.error: | ||
break | ||||
value = bytearray(fp.read(size)) | ||||
if len(value) != size: | ||||
break | ||||
set(node, value) | ||||
except IOError: | ||||
# the file is allowed to be missing | ||||
pass | ||||
self._read = True | ||||
self._dirty = False | ||||
def write(self): | ||||
if not self._dirty or self._opener is None: | ||||
return | ||||
# rotate backwards to the first used node | ||||
r45335 | try: | |||
with self._opener( | ||||
self._file, b'w', atomictemp=True, checkambig=True | ||||
) as fp: | ||||
node = self._head.prev | ||||
while True: | ||||
if node.key in self._cache: | ||||
fp.write(node.key) | ||||
fp.write(struct.pack(b'>L', len(node.value))) | ||||
fp.write(node.value) | ||||
if node is self._head: | ||||
break | ||||
node = node.prev | ||||
except IOError: | ||||
# We could not write the cache (eg: permission error) | ||||
# the content can be missing. | ||||
# | ||||
# We could try harder and see if we could recreate a wcache | ||||
# directory were we coudl write too. | ||||
# | ||||
# XXX the error pass silently, having some way to issue an error | ||||
# log `ui.log` would be nice. | ||||
pass | ||||
Martijn Pieters
|
r38803 | |||
def __len__(self): | ||||
if not self._read: | ||||
self.read() | ||||
return super(manifestfulltextcache, self).__len__() | ||||
def __contains__(self, k): | ||||
if not self._read: | ||||
self.read() | ||||
return super(manifestfulltextcache, self).__contains__(k) | ||||
def __iter__(self): | ||||
if not self._read: | ||||
self.read() | ||||
return super(manifestfulltextcache, self).__iter__() | ||||
def __getitem__(self, k): | ||||
if not self._read: | ||||
self.read() | ||||
# the cache lru order can change on read | ||||
setdirty = self._cache.get(k) is not self._head | ||||
value = super(manifestfulltextcache, self).__getitem__(k) | ||||
if setdirty: | ||||
self._dirty = True | ||||
return value | ||||
def __setitem__(self, k, v): | ||||
if not self._read: | ||||
self.read() | ||||
super(manifestfulltextcache, self).__setitem__(k, v) | ||||
self._dirty = True | ||||
def __delitem__(self, k): | ||||
if not self._read: | ||||
self.read() | ||||
super(manifestfulltextcache, self).__delitem__(k) | ||||
self._dirty = True | ||||
def get(self, k, default=None): | ||||
if not self._read: | ||||
self.read() | ||||
return super(manifestfulltextcache, self).get(k, default=default) | ||||
def clear(self, clear_persisted_data=False): | ||||
super(manifestfulltextcache, self).clear() | ||||
if clear_persisted_data: | ||||
self._dirty = True | ||||
self.write() | ||||
self._read = False | ||||
Augie Fackler
|
r43346 | |||
r42662 | # and upper bound of what we expect from compression | |||
# (real live value seems to be "3") | ||||
r42669 | MAXCOMPRESSION = 3 | |||
r42662 | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r45154 | class FastdeltaUnavailable(Exception): | ||
"""Exception raised when fastdelta isn't usable on a manifest.""" | ||||
Matt Harbison
|
r52962 | class manifestrevlog: # (repository.imanifeststorage) | ||
Augie Fackler
|
r46554 | """A revlog that stores manifest texts. This is responsible for caching the | ||
Durham Goode
|
r29824 | full-text manifest contents. | ||
Augie Fackler
|
r46554 | """ | ||
Augie Fackler
|
r43346 | |||
def __init__( | ||||
self, | ||||
Joerg Sonnenberger
|
r47538 | nodeconstants, | ||
Augie Fackler
|
r43346 | opener, | ||
Augie Fackler
|
r43347 | tree=b'', | ||
Augie Fackler
|
r43346 | dirlogcache=None, | ||
treemanifest=False, | ||||
): | ||||
Durham Goode
|
r31151 | """Constructs a new manifest revlog | ||
`indexfile` - used by extensions to have two manifests at once, like | ||||
when transitioning between flatmanifeset and treemanifests. | ||||
Durham Goode
|
r32252 | |||
`treemanifest` - used to indicate this is a tree manifest revlog. Opener | ||||
options can also be used to make this a tree manifest revlog. The opener | ||||
option takes precedence, so if it is set to True, we ignore whatever | ||||
value is passed in to the constructor. | ||||
Durham Goode
|
r31151 | """ | ||
Joerg Sonnenberger
|
r47538 | self.nodeconstants = nodeconstants | ||
Durham Goode
|
r29824 | # During normal operations, we expect to deal with not more than four | ||
# revs at a time (such as during commit --amend). When rebasing large | ||||
# stacks of commits, the number can go up, hence the config knob below. | ||||
cachesize = 4 | ||||
Durham Goode
|
r32252 | optiontreemanifest = False | ||
r52659 | persistentnodemap = False | |||
Durham Goode
|
r29824 | opts = getattr(opener, 'options', None) | ||
if opts is not None: | ||||
Augie Fackler
|
r43347 | cachesize = opts.get(b'manifestcachesize', cachesize) | ||
optiontreemanifest = opts.get(b'treemanifest', False) | ||||
r52659 | persistentnodemap = opts.get(b'persistent-nodemap', False) | |||
Durham Goode
|
r29940 | |||
Durham Goode
|
r32252 | self._treeondisk = optiontreemanifest or treemanifest | ||
Durham Goode
|
r29940 | |||
Martijn Pieters
|
r38803 | self._fulltextcache = manifestfulltextcache(cachesize) | ||
Durham Goode
|
r29824 | |||
Gregory Szorc
|
r39279 | if tree: | ||
r50101 | assert self._treeondisk, (tree, b'opts is %r' % opts) | |||
Durham Goode
|
r31151 | |||
r47921 | radix = b'00manifest' | |||
if tree: | ||||
radix = b"meta/" + tree + radix | ||||
Durham Goode
|
r31151 | |||
Gregory Szorc
|
r39351 | self.tree = tree | ||
Durham Goode
|
r29941 | # The dirlogcache is kept on the root manifest log | ||
Gregory Szorc
|
r39279 | if tree: | ||
Durham Goode
|
r29941 | self._dirlogcache = dirlogcache | ||
else: | ||||
Augie Fackler
|
r43347 | self._dirlogcache = {b'': self} | ||
Durham Goode
|
r29940 | |||
Augie Fackler
|
r43346 | self._revlog = revlog.revlog( | ||
opener, | ||||
r47838 | target=(revlog_constants.KIND_MANIFESTLOG, self.tree), | |||
r47921 | radix=radix, | |||
Augie Fackler
|
r43346 | # only root indexfile is cached | ||
checkambig=not bool(tree), | ||||
mmaplargeindex=True, | ||||
upperboundcomp=MAXCOMPRESSION, | ||||
r52659 | persistentnodemap=persistentnodemap, | |||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r39350 | |||
self.index = self._revlog.index | ||||
Durham Goode
|
r29940 | |||
r51530 | def get_revlog(self): | |||
"""return an actual revlog instance if any | ||||
This exist because a lot of code leverage the fact the underlying | ||||
storage is a revlog for optimization, so giving simple way to access | ||||
the revlog instance helps such code. | ||||
""" | ||||
return self._revlog | ||||
Martijn Pieters
|
r38803 | def _setupmanifestcachehooks(self, repo): | ||
"""Persist the manifestfulltextcache on lock release""" | ||||
r51821 | if not hasattr(repo, '_wlockref'): | |||
Martijn Pieters
|
r38803 | return | ||
r42131 | self._fulltextcache._opener = repo.wcachevfs | |||
r42130 | if repo._currentlock(repo._wlockref) is None: | |||
r42127 | return | |||
Martijn Pieters
|
r38803 | reporef = weakref.ref(repo) | ||
manifestrevlogref = weakref.ref(self) | ||||
Kyle Lippincott
|
r44217 | def persistmanifestcache(success): | ||
# Repo is in an unknown state, do not persist. | ||||
if not success: | ||||
return | ||||
Martijn Pieters
|
r38803 | repo = reporef() | ||
self = manifestrevlogref() | ||||
if repo is None or self is None: | ||||
return | ||||
Gregory Szorc
|
r39356 | if repo.manifestlog.getstorage(b'') is not self: | ||
Martijn Pieters
|
r38803 | # there's a different manifest in play now, abort | ||
return | ||||
self._fulltextcache.write() | ||||
r42127 | repo._afterlock(persistmanifestcache) | |||
Martijn Pieters
|
r38803 | |||
Durham Goode
|
r29824 | @property | ||
def fulltextcache(self): | ||||
return self._fulltextcache | ||||
Matt Harbison
|
r52765 | def clearcaches(self, clear_persisted_data: bool = False) -> None: | ||
Gregory Szorc
|
r39350 | self._revlog.clearcaches() | ||
Martijn Pieters
|
r38803 | self._fulltextcache.clear(clear_persisted_data=clear_persisted_data) | ||
Gregory Szorc
|
r39351 | self._dirlogcache = {self.tree: self} | ||
Durham Goode
|
r29941 | |||
Augie Fackler
|
r36112 | def dirlog(self, d): | ||
if d: | ||||
Durham Goode
|
r29941 | assert self._treeondisk | ||
Augie Fackler
|
r36112 | if d not in self._dirlogcache: | ||
Augie Fackler
|
r43346 | mfrevlog = manifestrevlog( | ||
Joerg Sonnenberger
|
r47538 | self.nodeconstants, | ||
self.opener, | ||||
d, | ||||
self._dirlogcache, | ||||
treemanifest=self._treeondisk, | ||||
Augie Fackler
|
r43346 | ) | ||
Augie Fackler
|
r36112 | self._dirlogcache[d] = mfrevlog | ||
return self._dirlogcache[d] | ||||
Durham Goode
|
r29824 | |||
Augie Fackler
|
r43346 | def add( | ||
self, | ||||
m, | ||||
transaction, | ||||
link, | ||||
p1, | ||||
p2, | ||||
r52667 | added: Iterable[bytes], | |||
removed: Iterable[bytes], | ||||
Augie Fackler
|
r43346 | readtree=None, | ||
match=None, | ||||
): | ||||
r45626 | """add some manifest entry in to the manifest log | |||
input: | ||||
m: the manifest dict we want to store | ||||
transaction: the open transaction | ||||
p1: manifest-node of p1 | ||||
p2: manifest-node of p2 | ||||
added: file added/changed compared to parent | ||||
removed: file removed compared to parent | ||||
tree manifest input: | ||||
readtree: a function to read a subtree | ||||
match: a filematcher for the subpart of the tree manifest | ||||
""" | ||||
Augie Fackler
|
r45154 | try: | ||
if p1 not in self.fulltextcache: | ||||
raise FastdeltaUnavailable() | ||||
Durham Goode
|
r29961 | # If our first parent is in the manifest cache, we can | ||
# compute a delta here using properties we know about the | ||||
# manifest up-front, which may save time later for the | ||||
# revlog layer. | ||||
_checkforbidden(added) | ||||
# combine the changed lists into one sorted iterator | ||||
Augie Fackler
|
r43346 | work = heapq.merge( | ||
[(x, False) for x in sorted(added)], | ||||
[(x, True) for x in sorted(removed)], | ||||
) | ||||
Durham Goode
|
r29961 | |||
arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work) | ||||
Gregory Szorc
|
r39350 | cachedelta = self._revlog.rev(p1), deltatext | ||
Durham Goode
|
r29961 | text = util.buffer(arraytext) | ||
Joerg Sonnenberger
|
r47258 | rev = self._revlog.addrevision( | ||
Augie Fackler
|
r43346 | text, transaction, link, p1, p2, cachedelta | ||
) | ||||
Joerg Sonnenberger
|
r47258 | n = self._revlog.node(rev) | ||
Augie Fackler
|
r45154 | except FastdeltaUnavailable: | ||
# The first parent manifest isn't already loaded or the | ||||
# manifest implementation doesn't support fastdelta, so | ||||
# we'll just encode a fulltext of the manifest and pass | ||||
# that through to the revlog layer, and let it handle the | ||||
# delta process. | ||||
Durham Goode
|
r29961 | if self._treeondisk: | ||
Augie Fackler
|
r43347 | assert readtree, b"readtree must be set for treemanifest writes" | ||
assert match, b"match must be specified for treemanifest writes" | ||||
Gregory Szorc
|
r39351 | m1 = readtree(self.tree, p1) | ||
m2 = readtree(self.tree, p2) | ||||
Augie Fackler
|
r43346 | n = self._addtree( | ||
m, transaction, link, m1, m2, readtree, match=match | ||||
) | ||||
Durham Goode
|
r29961 | arraytext = None | ||
else: | ||||
Augie Fackler
|
r36391 | text = m.text() | ||
Joerg Sonnenberger
|
r47258 | rev = self._revlog.addrevision(text, transaction, link, p1, p2) | ||
n = self._revlog.node(rev) | ||||
Augie Fackler
|
r31346 | arraytext = bytearray(text) | ||
Durham Goode
|
r29961 | |||
Martin von Zweigbergk
|
r30209 | if arraytext is not None: | ||
self.fulltextcache[n] = arraytext | ||||
Durham Goode
|
r29961 | |||
return n | ||||
spectral
|
r39704 | def _addtree(self, m, transaction, link, m1, m2, readtree, match): | ||
Durham Goode
|
r29961 | # If the manifest is unchanged compared to one parent, | ||
# don't write a new revision | ||||
Augie Fackler
|
r43347 | if self.tree != b'' and ( | ||
m.unmodifiedsince(m1) or m.unmodifiedsince(m2) | ||||
): | ||||
Durham Goode
|
r29961 | return m.node() | ||
Augie Fackler
|
r43346 | |||
spectral
|
r39704 | def writesubtree(subm, subp1, subp2, match): | ||
Durham Goode
|
r29961 | sublog = self.dirlog(subm.dir()) | ||
Augie Fackler
|
r43346 | sublog.add( | ||
subm, | ||||
transaction, | ||||
link, | ||||
subp1, | ||||
subp2, | ||||
None, | ||||
None, | ||||
readtree=readtree, | ||||
match=match, | ||||
) | ||||
spectral
|
r39704 | m.writesubtrees(m1, m2, writesubtree, match) | ||
Augie Fackler
|
r36391 | text = m.dirtext() | ||
Durham Goode
|
r31294 | n = None | ||
Augie Fackler
|
r43347 | if self.tree != b'': | ||
Durham Goode
|
r31294 | # Double-check whether contents are unchanged to one parent | ||
Augie Fackler
|
r36391 | if text == m1.dirtext(): | ||
Durham Goode
|
r31294 | n = m1.node() | ||
Augie Fackler
|
r36391 | elif text == m2.dirtext(): | ||
Durham Goode
|
r31294 | n = m2.node() | ||
if not n: | ||||
Joerg Sonnenberger
|
r47258 | rev = self._revlog.addrevision( | ||
Augie Fackler
|
r43346 | text, transaction, link, m1.node(), m2.node() | ||
) | ||||
Joerg Sonnenberger
|
r47258 | n = self._revlog.node(rev) | ||
Durham Goode
|
r31294 | |||
Durham Goode
|
r29961 | # Save nodeid so parent manifest can calculate its nodeid | ||
m.setnode(n) | ||||
return n | ||||
Gregory Szorc
|
r39350 | def __len__(self): | ||
return len(self._revlog) | ||||
def __iter__(self): | ||||
return self._revlog.__iter__() | ||||
def rev(self, node): | ||||
return self._revlog.rev(node) | ||||
def node(self, rev): | ||||
return self._revlog.node(rev) | ||||
def lookup(self, value): | ||||
return self._revlog.lookup(value) | ||||
def parentrevs(self, rev): | ||||
return self._revlog.parentrevs(rev) | ||||
def parents(self, node): | ||||
return self._revlog.parents(node) | ||||
def linkrev(self, rev): | ||||
return self._revlog.linkrev(rev) | ||||
def checksize(self): | ||||
return self._revlog.checksize() | ||||
r51915 | def revision(self, node): | |||
return self._revlog.revision(node) | ||||
Gregory Szorc
|
r39350 | |||
r51916 | def rawdata(self, node): | |||
return self._revlog.rawdata(node) | ||||
r42949 | ||||
Gregory Szorc
|
r39350 | def revdiff(self, rev1, rev2): | ||
return self._revlog.revdiff(rev1, rev2) | ||||
def cmp(self, node, text): | ||||
return self._revlog.cmp(node, text) | ||||
def deltaparent(self, rev): | ||||
return self._revlog.deltaparent(rev) | ||||
Augie Fackler
|
r43346 | def emitrevisions( | ||
self, | ||||
nodes, | ||||
nodesorder=None, | ||||
revisiondata=False, | ||||
assumehaveparentrevisions=False, | ||||
deltamode=repository.CG_DELTAMODE_STD, | ||||
Raphaël Gomès
|
r47449 | sidedata_helpers=None, | ||
r50505 | debug_info=None, | |||
Augie Fackler
|
r43346 | ): | ||
Gregory Szorc
|
r39898 | return self._revlog.emitrevisions( | ||
Augie Fackler
|
r43346 | nodes, | ||
nodesorder=nodesorder, | ||||
revisiondata=revisiondata, | ||||
Gregory Szorc
|
r39898 | assumehaveparentrevisions=assumehaveparentrevisions, | ||
Augie Fackler
|
r43346 | deltamode=deltamode, | ||
Raphaël Gomès
|
r47449 | sidedata_helpers=sidedata_helpers, | ||
r50505 | debug_info=debug_info, | |||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r39898 | |||
Joerg Sonnenberger
|
r46373 | def addgroup( | ||
self, | ||||
deltas, | ||||
linkmapper, | ||||
transaction, | ||||
Joerg Sonnenberger
|
r47085 | alwayscache=False, | ||
Joerg Sonnenberger
|
r46373 | addrevisioncb=None, | ||
duplicaterevisioncb=None, | ||||
r50506 | debug_info=None, | |||
r50660 | delta_base_reuse_policy=None, | |||
Joerg Sonnenberger
|
r46373 | ): | ||
Augie Fackler
|
r43346 | return self._revlog.addgroup( | ||
Joerg Sonnenberger
|
r46373 | deltas, | ||
linkmapper, | ||||
transaction, | ||||
Joerg Sonnenberger
|
r47085 | alwayscache=alwayscache, | ||
Joerg Sonnenberger
|
r46373 | addrevisioncb=addrevisioncb, | ||
duplicaterevisioncb=duplicaterevisioncb, | ||||
r50506 | debug_info=debug_info, | |||
r50660 | delta_base_reuse_policy=delta_base_reuse_policy, | |||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r39350 | |||
Gregory Szorc
|
r39894 | def rawsize(self, rev): | ||
return self._revlog.rawsize(rev) | ||||
Gregory Szorc
|
r39350 | def getstrippoint(self, minlink): | ||
return self._revlog.getstrippoint(minlink) | ||||
def strip(self, minlink, transaction): | ||||
return self._revlog.strip(minlink, transaction) | ||||
def files(self): | ||||
return self._revlog.files() | ||||
def clone(self, tr, destrevlog, **kwargs): | ||||
if not isinstance(destrevlog, manifestrevlog): | ||||
Augie Fackler
|
r43347 | raise error.ProgrammingError(b'expected manifestrevlog to clone()') | ||
Gregory Szorc
|
r39350 | |||
return self._revlog.clone(tr, destrevlog._revlog, **kwargs) | ||||
Augie Fackler
|
r43346 | def storageinfo( | ||
self, | ||||
exclusivefiles=False, | ||||
sharedfiles=False, | ||||
revisionscount=False, | ||||
trackedsize=False, | ||||
storedsize=False, | ||||
): | ||||
Gregory Szorc
|
r39905 | return self._revlog.storageinfo( | ||
Augie Fackler
|
r43346 | exclusivefiles=exclusivefiles, | ||
sharedfiles=sharedfiles, | ||||
revisionscount=revisionscount, | ||||
trackedsize=trackedsize, | ||||
storedsize=storedsize, | ||||
) | ||||
Gregory Szorc
|
r39905 | |||
Gregory Szorc
|
r39350 | @property | ||
def opener(self): | ||||
return self._revlog.opener | ||||
@opener.setter | ||||
def opener(self, value): | ||||
self._revlog.opener = value | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52967 | # TODO: drop this in favor of repository.imanifestrevisionstored? | ||
AnyManifestCtx = Union['manifestctx', 'treemanifestctx'] | ||||
Matt Harbison
|
r52961 | # TODO: drop this in favor of repository.imanifestdict | ||
AnyManifestDict = Union[manifestdict, treemanifest] | ||||
r52667 | ||||
Matt Harbison
|
r53381 | class manifestlog(repository.imanifestlog): | ||
Durham Goode
|
r29825 | """A collection class representing the collection of manifest snapshots | ||
referenced by commits in the repository. | ||||
In this situation, 'manifest' refers to the abstract concept of a snapshot | ||||
of the list of files in the given commit. Consumers of the output of this | ||||
class do not care about the implementation details of the actual manifests | ||||
they receive (i.e. tree or flat or lazily loaded, etc).""" | ||||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r41067 | def __init__(self, opener, repo, rootstore, narrowmatch): | ||
Joerg Sonnenberger
|
r47538 | self.nodeconstants = repo.nodeconstants | ||
Durham Goode
|
r29959 | usetreemanifest = False | ||
Durham Goode
|
r30372 | cachesize = 4 | ||
Durham Goode
|
r29959 | |||
opts = getattr(opener, 'options', None) | ||||
if opts is not None: | ||||
Augie Fackler
|
r43347 | usetreemanifest = opts.get(b'treemanifest', usetreemanifest) | ||
cachesize = opts.get(b'manifestcachesize', cachesize) | ||||
Gregory Szorc
|
r39281 | |||
self._treemanifests = usetreemanifest | ||||
Durham Goode
|
r29959 | |||
Gregory Szorc
|
r39799 | self._rootstore = rootstore | ||
Gregory Szorc
|
r39357 | self._rootstore._setupmanifestcachehooks(repo) | ||
Martin von Zweigbergk
|
r41067 | self._narrowmatch = narrowmatch | ||
Durham Goode
|
r30219 | |||
Durham Goode
|
r30292 | # A cache of the manifestctx or treemanifestctx for each directory | ||
self._dirmancache = {} | ||||
Augie Fackler
|
r43347 | self._dirmancache[b''] = util.lrucachedict(cachesize) | ||
Durham Goode
|
r30292 | |||
Gregory Szorc
|
r38529 | self._cachesize = cachesize | ||
Durham Goode
|
r29826 | |||
Durham Goode
|
r29825 | def __getitem__(self, node): | ||
Durham Goode
|
r30290 | """Retrieves the manifest instance for the given node. Throws a | ||
LookupError if not found. | ||||
Durham Goode
|
r29825 | """ | ||
Augie Fackler
|
r43347 | return self.get(b'', node) | ||
Durham Goode
|
r29825 | |||
r52678 | @property | |||
def narrowed(self): | ||||
return not (self._narrowmatch is None or self._narrowmatch.always()) | ||||
r52667 | def get( | |||
self, tree: bytes, node: bytes, verify: bool = True | ||||
) -> AnyManifestCtx: | ||||
Durham Goode
|
r30291 | """Retrieves the manifest instance for the given node. Throws a | ||
LookupError if not found. | ||||
Durham Goode
|
r30403 | |||
`verify` - if True an exception will be thrown if the node is not in | ||||
the revlog | ||||
Durham Goode
|
r30291 | """ | ||
Gregory Szorc
|
r39271 | if node in self._dirmancache.get(tree, ()): | ||
return self._dirmancache[tree][node] | ||||
Durham Goode
|
r30292 | |||
Martin von Zweigbergk
|
r37392 | if not self._narrowmatch.always(): | ||
Martin von Zweigbergk
|
r42528 | if not self._narrowmatch.visitdir(tree[:-1]): | ||
Joerg Sonnenberger
|
r47538 | return excludeddirmanifestctx(self.nodeconstants, tree, node) | ||
Gregory Szorc
|
r39271 | if tree: | ||
Gregory Szorc
|
r39357 | if self._rootstore._treeondisk: | ||
Durham Goode
|
r30403 | if verify: | ||
Gregory Szorc
|
r39282 | # Side-effect is LookupError is raised if node doesn't | ||
# exist. | ||||
self.getstorage(tree).rev(node) | ||||
Gregory Szorc
|
r39271 | m = treemanifestctx(self, tree, node) | ||
Durham Goode
|
r30291 | else: | ||
raise error.Abort( | ||||
Augie Fackler
|
r43346 | _( | ||
Augie Fackler
|
r43347 | b"cannot ask for manifest directory '%s' in a flat " | ||
b"manifest" | ||||
Augie Fackler
|
r43346 | ) | ||
% tree | ||||
) | ||||
Durham Goode
|
r29907 | else: | ||
Durham Goode
|
r30403 | if verify: | ||
Gregory Szorc
|
r39282 | # Side-effect is LookupError is raised if node doesn't exist. | ||
Gregory Szorc
|
r39357 | self._rootstore.rev(node) | ||
Gregory Szorc
|
r39282 | |||
Gregory Szorc
|
r39281 | if self._treemanifests: | ||
Augie Fackler
|
r43347 | m = treemanifestctx(self, b'', node) | ||
Durham Goode
|
r30291 | else: | ||
Durham Goode
|
r31153 | m = manifestctx(self, node) | ||
Durham Goode
|
r30292 | |||
Joerg Sonnenberger
|
r47771 | if node != self.nodeconstants.nullid: | ||
Gregory Szorc
|
r39271 | mancache = self._dirmancache.get(tree) | ||
Durham Goode
|
r30292 | if not mancache: | ||
Gregory Szorc
|
r38529 | mancache = util.lrucachedict(self._cachesize) | ||
Gregory Szorc
|
r39271 | self._dirmancache[tree] = mancache | ||
Durham Goode
|
r30292 | mancache[node] = m | ||
Durham Goode
|
r29825 | return m | ||
Gregory Szorc
|
r39280 | def getstorage(self, tree): | ||
Gregory Szorc
|
r39357 | return self._rootstore.dirlog(tree) | ||
Gregory Szorc
|
r39280 | |||
r52667 | def clearcaches(self, clear_persisted_data: bool = False) -> None: | |||
Durham Goode
|
r30370 | self._dirmancache.clear() | ||
Gregory Szorc
|
r39357 | self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data) | ||
Durham Goode
|
r30370 | |||
r52667 | def rev(self, node) -> int: | |||
Gregory Szorc
|
r39357 | return self._rootstore.rev(node) | ||
Gregory Szorc
|
r38573 | |||
r52667 | def update_caches(self, transaction) -> None: | |||
r45291 | return self._rootstore._revlog.update_caches(transaction=transaction) | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52964 | class memmanifestctx: # (repository.imanifestrevisionwritable) | ||
Matt Harbison
|
r52960 | _manifestdict: manifestdict | ||
Matt Harbison
|
r52719 | |||
Durham Goode
|
r31153 | def __init__(self, manifestlog): | ||
self._manifestlog = manifestlog | ||||
Joerg Sonnenberger
|
r47817 | self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen) | ||
Durham Goode
|
r30342 | |||
Matt Harbison
|
r52962 | def _storage(self) -> manifestrevlog: | ||
Gregory Szorc
|
r39356 | return self._manifestlog.getstorage(b'') | ||
Durham Goode
|
r30345 | |||
Matt Harbison
|
r52964 | def copy(self) -> 'memmanifestctx': | ||
Durham Goode
|
r31153 | memmf = memmanifestctx(self._manifestlog) | ||
Durham Goode
|
r30343 | memmf._manifestdict = self.read().copy() | ||
return memmf | ||||
Matt Harbison
|
r52960 | def read(self) -> 'manifestdict': | ||
Durham Goode
|
r30342 | return self._manifestdict | ||
spectral
|
r39704 | def write(self, transaction, link, p1, p2, added, removed, match=None): | ||
Augie Fackler
|
r43346 | return self._storage().add( | ||
self._manifestdict, | ||||
transaction, | ||||
link, | ||||
p1, | ||||
p2, | ||||
added, | ||||
removed, | ||||
match=match, | ||||
) | ||||
Durham Goode
|
r30345 | |||
Matt Harbison
|
r53374 | class manifestctx(repository.imanifestrevisionstored): | ||
Durham Goode
|
r29825 | """A class representing a single revision of a manifest, including its | ||
contents, its parent revs, and its linkrev. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52960 | _data: Optional[manifestdict] | ||
Matt Harbison
|
r52719 | |||
Durham Goode
|
r31153 | def __init__(self, manifestlog, node): | ||
self._manifestlog = manifestlog | ||||
Durham Goode
|
r29926 | self._data = None | ||
Durham Goode
|
r29825 | |||
self._node = node | ||||
Durham Goode
|
r29907 | |||
# TODO: We eventually want p1, p2, and linkrev exposed on this class, | ||||
# but let's add it later when something needs it and we can load it | ||||
# lazily. | ||||
Augie Fackler
|
r43346 | # self.p1, self.p2 = store.parents(node) | ||
# rev = store.rev(node) | ||||
# self.linkrev = store.linkrev(rev) | ||||
Durham Goode
|
r29825 | |||
Matt Harbison
|
r52962 | def _storage(self) -> 'manifestrevlog': | ||
Gregory Szorc
|
r39356 | return self._manifestlog.getstorage(b'') | ||
Durham Goode
|
r30341 | |||
r52667 | def node(self) -> bytes: | |||
Durham Goode
|
r29825 | return self._node | ||
Matt Harbison
|
r52964 | def copy(self) -> memmanifestctx: | ||
Durham Goode
|
r31153 | memmf = memmanifestctx(self._manifestlog) | ||
Durham Goode
|
r30343 | memmf._manifestdict = self.read().copy() | ||
return memmf | ||||
Mateusz Kwapich
|
r30565 | @propertycache | ||
r52667 | def parents(self) -> Tuple[bytes, bytes]: | |||
Gregory Szorc
|
r39353 | return self._storage().parents(self._node) | ||
Mateusz Kwapich
|
r30565 | |||
Matt Harbison
|
r52960 | def read(self) -> 'manifestdict': | ||
Durham Goode
|
r31097 | if self._data is None: | ||
Joerg Sonnenberger
|
r47817 | nc = self._manifestlog.nodeconstants | ||
if self._node == nc.nullid: | ||||
self._data = manifestdict(nc.nodelen) | ||||
Durham Goode
|
r29926 | else: | ||
Gregory Szorc
|
r39353 | store = self._storage() | ||
Gregory Szorc
|
r39358 | if self._node in store.fulltextcache: | ||
text = pycompat.bytestr(store.fulltextcache[self._node]) | ||||
Martijn Pieters
|
r38803 | else: | ||
Gregory Szorc
|
r39353 | text = store.revision(self._node) | ||
Martijn Pieters
|
r38803 | arraytext = bytearray(text) | ||
Gregory Szorc
|
r39358 | store.fulltextcache[self._node] = arraytext | ||
Joerg Sonnenberger
|
r47817 | self._data = manifestdict(nc.nodelen, text) | ||
Durham Goode
|
r29926 | return self._data | ||
Matt Harbison
|
r52960 | def readfast(self, shallow: bool = False) -> 'manifestdict': | ||
Augie Fackler
|
r46554 | """Calls either readdelta or read, based on which would be less work. | ||
Durham Goode
|
r30294 | readdelta is called if the delta is against the p1, and therefore can be | ||
read quickly. | ||||
If `shallow` is True, nothing changes since this is a flat manifest. | ||||
Augie Fackler
|
r46554 | """ | ||
r52681 | util.nouideprecwarn( | |||
b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_parents"', | ||||
b"6.9", | ||||
stacklevel=2, | ||||
) | ||||
Gregory Szorc
|
r39353 | store = self._storage() | ||
r = store.rev(self._node) | ||||
deltaparent = store.deltaparent(r) | ||||
if deltaparent != nullrev and deltaparent in store.parentrevs(r): | ||||
Durham Goode
|
r29939 | return self.readdelta() | ||
return self.read() | ||||
Matt Harbison
|
r52960 | def readdelta(self, shallow: bool = False) -> 'manifestdict': | ||
Augie Fackler
|
r46554 | """Returns a manifest containing just the entries that are present | ||
Durham Goode
|
r30295 | in this manifest, but not in its p1 manifest. This is efficient to read | ||
if the revlog delta is already p1. | ||||
Changing the value of `shallow` has no effect on flat manifests. | ||||
Augie Fackler
|
r46554 | """ | ||
r52681 | util.nouideprecwarn( | |||
b'"readfast" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"', | ||||
b"6.9", | ||||
stacklevel=2, | ||||
) | ||||
Gregory Szorc
|
r39353 | store = self._storage() | ||
r = store.rev(self._node) | ||||
d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r)) | ||||
Joerg Sonnenberger
|
r47817 | return manifestdict(store.nodeconstants.nodelen, d) | ||
Durham Goode
|
r29938 | |||
r52668 | def read_any_fast_delta( | |||
self, | ||||
r52669 | valid_bases: Optional[Collection[int]] = None, | |||
r52668 | *, | |||
shallow: bool = False, | ||||
Matt Harbison
|
r52960 | ) -> Tuple[Optional[int], manifestdict]: | ||
r52668 | """see `imanifestrevisionstored` documentation""" | |||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
deltaparent = store.deltaparent(r) | ||||
r52669 | if valid_bases is None: | |||
# make sure the next check is True | ||||
valid_bases = (deltaparent,) | ||||
r52668 | if deltaparent != nullrev and deltaparent in valid_bases: | |||
d = mdiff.patchtext(store.revdiff(deltaparent, r)) | ||||
return ( | ||||
deltaparent, | ||||
manifestdict(store.nodeconstants.nodelen, d), | ||||
) | ||||
return (None, self.read()) | ||||
r52674 | def read_delta_parents( | |||
self, | ||||
*, | ||||
shallow: bool = False, | ||||
exact: bool = True, | ||||
Matt Harbison
|
r52960 | ) -> manifestdict: | ||
r52674 | """see `interface.imanifestrevisionbase` documentations""" | |||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
deltaparent = store.deltaparent(r) | ||||
parents = [p for p in store.parentrevs(r) if p is not nullrev] | ||||
if not exact and deltaparent in parents: | ||||
d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r)) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
elif not exact or len(parents) == 0: | ||||
return self.read() | ||||
elif len(parents) == 1: | ||||
p = parents[0] | ||||
d = mdiff.patchtext(store.revdiff(p, r)) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
else: | ||||
p1, p2 = parents | ||||
d1 = mdiff.patchtext(store.revdiff(p1, r)) | ||||
d2 = mdiff.patchtext(store.revdiff(p2, r)) | ||||
d1 = manifestdict(store.nodeconstants.nodelen, d1) | ||||
d2 = manifestdict(store.nodeconstants.nodelen, d2) | ||||
md = manifestdict(store.nodeconstants.nodelen) | ||||
for f, new_node, new_flag in d1.iterentries(): | ||||
if f not in d2: | ||||
continue | ||||
if new_node is not None: | ||||
md.set(f, new_node, new_flag) | ||||
return md | ||||
Matt Harbison
|
r53371 | def read_delta_new_entries(self, *, shallow: bool = False) -> manifestdict: | ||
r52678 | """see `interface.imanifestrevisionbase` documentations""" | |||
# If we are using narrow, returning a delta against an arbitrary | ||||
# changeset might return file outside the narrowspec. This can create | ||||
# issue when running validation server side with strict security as | ||||
# push from low priviledge usage might be seen as adding new revision | ||||
# for files they cannot touch. So we are strict if narrow is involved. | ||||
if self._manifestlog.narrowed: | ||||
return self.read_delta_parents(shallow=shallow, exact=True) | ||||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r)) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
r52667 | def find(self, key: bytes) -> Tuple[bytes, bytes]: | |||
Durham Goode
|
r30340 | return self.read().find(key) | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r53378 | class memtreemanifestctx(repository.imanifestrevisionwritable): | ||
Matt Harbison
|
r52961 | _treemanifest: treemanifest | ||
Matt Harbison
|
r52719 | |||
Augie Fackler
|
r43347 | def __init__(self, manifestlog, dir=b''): | ||
Durham Goode
|
r31153 | self._manifestlog = manifestlog | ||
Durham Goode
|
r30342 | self._dir = dir | ||
Joerg Sonnenberger
|
r47538 | self._treemanifest = treemanifest(manifestlog.nodeconstants) | ||
Durham Goode
|
r30342 | |||
Matt Harbison
|
r52962 | def _storage(self) -> manifestrevlog: | ||
Gregory Szorc
|
r39356 | return self._manifestlog.getstorage(b'') | ||
Durham Goode
|
r30345 | |||
Matt Harbison
|
r52966 | def copy(self) -> 'memtreemanifestctx': | ||
Durham Goode
|
r31153 | memmf = memtreemanifestctx(self._manifestlog, dir=self._dir) | ||
Durham Goode
|
r30343 | memmf._treemanifest = self._treemanifest.copy() | ||
return memmf | ||||
Matt Harbison
|
r52961 | def read(self) -> 'treemanifest': | ||
Durham Goode
|
r30342 | return self._treemanifest | ||
spectral
|
r39704 | def write(self, transaction, link, p1, p2, added, removed, match=None): | ||
Durham Goode
|
r30368 | def readtree(dir, node): | ||
Durham Goode
|
r31153 | return self._manifestlog.get(dir, node).read() | ||
Augie Fackler
|
r43346 | |||
return self._storage().add( | ||||
self._treemanifest, | ||||
transaction, | ||||
link, | ||||
p1, | ||||
p2, | ||||
added, | ||||
removed, | ||||
readtree=readtree, | ||||
match=match, | ||||
) | ||||
Durham Goode
|
r30345 | |||
Matt Harbison
|
r53374 | class treemanifestctx(repository.imanifestrevisionstored): | ||
Matt Harbison
|
r52961 | _data: Optional[treemanifest] | ||
Matt Harbison
|
r52719 | |||
Durham Goode
|
r31153 | def __init__(self, manifestlog, dir, node): | ||
self._manifestlog = manifestlog | ||||
Durham Goode
|
r29907 | self._dir = dir | ||
Durham Goode
|
r29926 | self._data = None | ||
Durham Goode
|
r29907 | |||
self._node = node | ||||
# TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that | ||||
# we can instantiate treemanifestctx objects for directories we don't | ||||
# have on disk. | ||||
Augie Fackler
|
r43346 | # self.p1, self.p2 = store.parents(node) | ||
# rev = store.rev(node) | ||||
# self.linkrev = store.linkrev(rev) | ||||
Durham Goode
|
r29907 | |||
Matt Harbison
|
r52962 | def _storage(self) -> manifestrevlog: | ||
Martin von Zweigbergk
|
r37391 | narrowmatch = self._manifestlog._narrowmatch | ||
if not narrowmatch.always(): | ||||
Martin von Zweigbergk
|
r42528 | if not narrowmatch.visitdir(self._dir[:-1]): | ||
Joerg Sonnenberger
|
r47538 | return excludedmanifestrevlog( | ||
self._manifestlog.nodeconstants, self._dir | ||||
) | ||||
Gregory Szorc
|
r39280 | return self._manifestlog.getstorage(self._dir) | ||
Durham Goode
|
r30221 | |||
Matt Harbison
|
r52961 | def read(self) -> 'treemanifest': | ||
Durham Goode
|
r31097 | if self._data is None: | ||
Gregory Szorc
|
r39353 | store = self._storage() | ||
Joerg Sonnenberger
|
r47771 | if self._node == self._manifestlog.nodeconstants.nullid: | ||
Joerg Sonnenberger
|
r47538 | self._data = treemanifest(self._manifestlog.nodeconstants) | ||
Gregory Szorc
|
r39353 | # TODO accessing non-public API | ||
elif store._treeondisk: | ||||
Joerg Sonnenberger
|
r47538 | m = treemanifest(self._manifestlog.nodeconstants, dir=self._dir) | ||
Augie Fackler
|
r43346 | |||
Durham Goode
|
r29926 | def gettext(): | ||
Gregory Szorc
|
r39353 | return store.revision(self._node) | ||
Augie Fackler
|
r43346 | |||
Durham Goode
|
r29926 | def readsubtree(dir, subm): | ||
Durham Goode
|
r30404 | # Set verify to False since we need to be able to create | ||
# subtrees for trees that don't exist on disk. | ||||
Durham Goode
|
r31153 | return self._manifestlog.get(dir, subm, verify=False).read() | ||
Augie Fackler
|
r43346 | |||
Durham Goode
|
r29926 | m.read(gettext, readsubtree) | ||
m.setnode(self._node) | ||||
self._data = m | ||||
else: | ||||
Gregory Szorc
|
r39353 | if self._node in store.fulltextcache: | ||
text = pycompat.bytestr(store.fulltextcache[self._node]) | ||||
Martijn Pieters
|
r38803 | else: | ||
Gregory Szorc
|
r39353 | text = store.revision(self._node) | ||
Martijn Pieters
|
r38803 | arraytext = bytearray(text) | ||
Gregory Szorc
|
r39353 | store.fulltextcache[self._node] = arraytext | ||
Joerg Sonnenberger
|
r47538 | self._data = treemanifest( | ||
self._manifestlog.nodeconstants, dir=self._dir, text=text | ||||
) | ||||
Durham Goode
|
r29926 | |||
return self._data | ||||
Durham Goode
|
r29907 | |||
r52667 | def node(self) -> bytes: | |||
Durham Goode
|
r29907 | return self._node | ||
Matt Harbison
|
r52966 | def copy(self) -> 'memtreemanifestctx': | ||
Durham Goode
|
r31153 | memmf = memtreemanifestctx(self._manifestlog, dir=self._dir) | ||
Durham Goode
|
r30343 | memmf._treemanifest = self.read().copy() | ||
return memmf | ||||
Mateusz Kwapich
|
r30565 | @propertycache | ||
r52667 | def parents(self) -> Tuple[bytes, bytes]: | |||
Gregory Szorc
|
r39353 | return self._storage().parents(self._node) | ||
Mateusz Kwapich
|
r30565 | |||
r52667 | def readdelta(self, shallow: bool = False) -> AnyManifestDict: | |||
r52668 | """see `imanifestrevisionstored` documentation""" | |||
r52681 | util.nouideprecwarn( | |||
b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_new_entries"', | ||||
b"6.9", | ||||
stacklevel=2, | ||||
) | ||||
Gregory Szorc
|
r39353 | store = self._storage() | ||
Augie Fackler
|
r36391 | if shallow: | ||
Gregory Szorc
|
r39353 | r = store.rev(self._node) | ||
d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r)) | ||||
Joerg Sonnenberger
|
r47817 | return manifestdict(store.nodeconstants.nodelen, d) | ||
Durham Goode
|
r30293 | else: | ||
# Need to perform a slow delta | ||||
Gregory Szorc
|
r39353 | r0 = store.deltaparent(store.rev(self._node)) | ||
m0 = self._manifestlog.get(self._dir, store.node(r0)).read() | ||||
Durham Goode
|
r30293 | m1 = self.read() | ||
Joerg Sonnenberger
|
r47538 | md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir) | ||
Gregory Szorc
|
r49781 | for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items(): | ||
Durham Goode
|
r30293 | if n1: | ||
md[f] = n1 | ||||
if fl1: | ||||
md.setflag(f, fl1) | ||||
return md | ||||
Durham Goode
|
r29938 | |||
r52668 | def read_any_fast_delta( | |||
self, | ||||
r52669 | valid_bases: Optional[Collection[int]] = None, | |||
r52668 | *, | |||
shallow: bool = False, | ||||
) -> Tuple[Optional[int], AnyManifestDict]: | ||||
"""see `imanifestrevisionstored` documentation""" | ||||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
deltaparent = store.deltaparent(r) | ||||
r52669 | if valid_bases is None: | |||
# make sure the next check is True | ||||
valid_bases = (deltaparent,) | ||||
r52668 | can_use_delta = deltaparent != nullrev and deltaparent in valid_bases | |||
if shallow: | ||||
if can_use_delta: | ||||
return (deltaparent, self._read_storage_delta_shallow()) | ||||
else: | ||||
d = store.revision(self._node) | ||||
return (None, manifestdict(store.nodeconstants.nodelen, d)) | ||||
else: | ||||
# note: This use "slow_delta" here is cargo culted from the previous | ||||
# implementation. I am not sure it make sense since the goal here is to | ||||
# be fast, so why are we computing a delta? On the other hand, tree | ||||
# manifest delta as fairly "cheap" and allow for skipping whole part of | ||||
# the tree that a full read would access. So it might be a good idea. | ||||
# | ||||
# If we realize we don't need delta here, we should simply use: | ||||
# | ||||
# return (None, self.read()) | ||||
if can_use_delta: | ||||
return (None, self._read_storage_slow_delta(base=deltaparent)) | ||||
else: | ||||
parents = [ | ||||
p | ||||
for p in store.parentrevs(r) | ||||
if p is not nullrev and p in valid_bases | ||||
] | ||||
if parents: | ||||
best_base = max(parents) | ||||
else: | ||||
best_base = max(valid_bases) | ||||
return (None, self._read_storage_slow_delta(base=best_base)) | ||||
Matt Harbison
|
r52960 | def _read_storage_delta_shallow(self) -> manifestdict: | ||
r52668 | store = self._storage() | |||
r = store.rev(self._node) | ||||
d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r)) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
Matt Harbison
|
r52961 | def _read_storage_slow_delta(self, base) -> 'treemanifest': | ||
r52668 | store = self._storage() | |||
if base is None: | ||||
base = store.deltaparent(store.rev(self._node)) | ||||
m0 = self._manifestlog.get(self._dir, store.node(base)).read() | ||||
m1 = self.read() | ||||
md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir) | ||||
for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items(): | ||||
if n1: | ||||
md[f] = n1 | ||||
if fl1: | ||||
md.setflag(f, fl1) | ||||
return md | ||||
r52674 | def read_delta_parents( | |||
self, | ||||
*, | ||||
shallow: bool = False, | ||||
exact: bool = True, | ||||
) -> AnyManifestDict: | ||||
"""see `interface.imanifestrevisionbase` documentations""" | ||||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
parents = [p for p in store.parentrevs(r) if p is not nullrev] | ||||
if not exact: | ||||
return self.read_any_fast_delta(parents, shallow=shallow)[1] | ||||
elif len(parents) == 0: | ||||
if shallow: | ||||
d = store.revision(self._node) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
else: | ||||
return self.read() | ||||
elif len(parents) == 1: | ||||
p = parents[0] | ||||
if shallow: | ||||
d = mdiff.patchtext(store.revdiff(p, r)) | ||||
return manifestdict(store.nodeconstants.nodelen, d) | ||||
else: | ||||
return self._read_storage_slow_delta(base=p) | ||||
else: | ||||
p1, p2 = parents | ||||
if shallow: | ||||
d1 = mdiff.patchtext(store.revdiff(p1, r)) | ||||
d2 = mdiff.patchtext(store.revdiff(p2, r)) | ||||
d1 = manifestdict(store.nodeconstants.nodelen, d1) | ||||
d2 = manifestdict(store.nodeconstants.nodelen, d2) | ||||
md = manifestdict(store.nodeconstants.nodelen) | ||||
for f, new_node, new_flag in d1.iterentries(): | ||||
if f not in d2: | ||||
continue | ||||
if new_node is not None: | ||||
md.set(f, new_node, new_flag) | ||||
return md | ||||
else: | ||||
m1 = self._manifestlog.get(self._dir, store.node(p1)).read() | ||||
m2 = self._manifestlog.get(self._dir, store.node(p2)).read() | ||||
mc = self.read() | ||||
d1 = m1.diff(mc) | ||||
d2 = m2.diff(mc) | ||||
md = treemanifest( | ||||
self._manifestlog.nodeconstants, | ||||
dir=self._dir, | ||||
) | ||||
for f, new_node, new_flag in d1.iterentries(): | ||||
if f not in d2: | ||||
continue | ||||
if new_node is not None: | ||||
md.set(f, new_node, new_flag) | ||||
return md | ||||
r52678 | def read_delta_new_entries( | |||
self, *, shallow: bool = False | ||||
) -> AnyManifestDict: | ||||
"""see `interface.imanifestrevisionbase` documentations""" | ||||
# If we are using narrow, returning a delta against an arbitrary | ||||
# changeset might return file outside the narrowspec. This can create | ||||
# issue when running validation server side with strict security as | ||||
# push from low priviledge usage might be seen as adding new revision | ||||
# for files they cannot touch. So we are strict if narrow is involved. | ||||
if self._manifestlog.narrowed: | ||||
return self.read_delta_parents(shallow=shallow, exact=True) | ||||
# delegate to existing another existing method for simplicity | ||||
store = self._storage() | ||||
r = store.rev(self._node) | ||||
bases = (store.deltaparent(r),) | ||||
return self.read_any_fast_delta(bases, shallow=shallow)[1] | ||||
Matt Harbison
|
r53371 | def readfast(self, shallow: bool = False) -> AnyManifestDict: | ||
Augie Fackler
|
r46554 | """Calls either readdelta or read, based on which would be less work. | ||
Durham Goode
|
r30294 | readdelta is called if the delta is against the p1, and therefore can be | ||
read quickly. | ||||
If `shallow` is True, it only returns the entries from this manifest, | ||||
and not any submanifests. | ||||
Augie Fackler
|
r46554 | """ | ||
r52681 | util.nouideprecwarn( | |||
b'"readdelta" is deprecated use "read_any_fast_delta" or "read_delta_parents"', | ||||
b"6.9", | ||||
stacklevel=2, | ||||
) | ||||
Gregory Szorc
|
r39353 | store = self._storage() | ||
r = store.rev(self._node) | ||||
deltaparent = store.deltaparent(r) | ||||
Augie Fackler
|
r43346 | if deltaparent != nullrev and deltaparent in store.parentrevs(r): | ||
Durham Goode
|
r30293 | return self.readdelta(shallow=shallow) | ||
if shallow: | ||||
Joerg Sonnenberger
|
r47817 | return manifestdict( | ||
store.nodeconstants.nodelen, store.revision(self._node) | ||||
) | ||||
Durham Goode
|
r30293 | else: | ||
return self.read() | ||||
Durham Goode
|
r29939 | |||
r52667 | def find(self, key: bytes) -> Tuple[bytes, bytes]: | |||
Durham Goode
|
r30340 | return self.read().find(key) | ||
Martin von Zweigbergk
|
r37390 | |||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r37390 | class excludeddir(treemanifest): | ||
"""Stand-in for a directory that is excluded from the repository. | ||||
With narrowing active on a repository that uses treemanifests, | ||||
some of the directory revlogs will be excluded from the resulting | ||||
clone. This is a huge storage win for clients, but means we need | ||||
some sort of pseudo-manifest to surface to internals so we can | ||||
detect a merge conflict outside the narrowspec. That's what this | ||||
class is: it stands in for a directory whose node is known, but | ||||
whose contents are unknown. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52719 | _files: Dict[bytes, bytes] | ||
_flags: Dict[bytes, bytes] | ||||
Joerg Sonnenberger
|
r47538 | def __init__(self, nodeconstants, dir, node): | ||
super(excludeddir, self).__init__(nodeconstants, dir) | ||||
Martin von Zweigbergk
|
r37390 | self._node = node | ||
# Add an empty file, which will be included by iterators and such, | ||||
# appearing as the directory itself (i.e. something like "dir/") | ||||
Augie Fackler
|
r43347 | self._files[b''] = node | ||
self._flags[b''] = b't' | ||||
Martin von Zweigbergk
|
r37390 | |||
# Manifests outside the narrowspec should never be modified, so avoid | ||||
# copying. This makes a noticeable difference when there are very many | ||||
# directories outside the narrowspec. Also, it makes sense for the copy to | ||||
# be of the same type as the original, which would not happen with the | ||||
# super type's copy(). | ||||
def copy(self): | ||||
return self | ||||
Augie Fackler
|
r43346 | |||
Martin von Zweigbergk
|
r37390 | class excludeddirmanifestctx(treemanifestctx): | ||
"""context wrapper for excludeddir - see that docstring for rationale""" | ||||
Augie Fackler
|
r43346 | |||
Joerg Sonnenberger
|
r47538 | def __init__(self, nodeconstants, dir, node): | ||
self.nodeconstants = nodeconstants | ||||
Martin von Zweigbergk
|
r37390 | self._dir = dir | ||
self._node = node | ||||
def read(self): | ||||
Joerg Sonnenberger
|
r47538 | return excludeddir(self.nodeconstants, self._dir, self._node) | ||
Martin von Zweigbergk
|
r37390 | |||
Matt Harbison
|
r53371 | def readfast(self, shallow: bool = False): | ||
r46864 | # special version of readfast since we don't have underlying storage | |||
return self.read() | ||||
Martin von Zweigbergk
|
r37390 | def write(self, *args): | ||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'attempt to write manifest from excluded dir %s' % self._dir | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r37390 | |||
class excludedmanifestrevlog(manifestrevlog): | ||||
"""Stand-in for excluded treemanifest revlogs. | ||||
When narrowing is active on a treemanifest repository, we'll have | ||||
references to directories we can't see due to the revlog being | ||||
skipped. This class exists to conform to the manifestrevlog | ||||
interface for those directories and proactively prevent writes to | ||||
outside the narrowspec. | ||||
""" | ||||
Joerg Sonnenberger
|
r47538 | def __init__(self, nodeconstants, dir): | ||
self.nodeconstants = nodeconstants | ||||
Martin von Zweigbergk
|
r37390 | self._dir = dir | ||
def __len__(self): | ||||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'attempt to get length of excluded dir %s' % self._dir | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r37390 | |||
def rev(self, node): | ||||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'attempt to get rev from excluded dir %s' % self._dir | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r37390 | |||
def linkrev(self, node): | ||||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'attempt to get linkrev from excluded dir %s' % self._dir | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r37390 | |||
def node(self, rev): | ||||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'attempt to get node from excluded dir %s' % self._dir | ||
Augie Fackler
|
r43346 | ) | ||
Martin von Zweigbergk
|
r37390 | |||
def add(self, *args, **kwargs): | ||||
# We should never write entries in dirlogs outside the narrow clone. | ||||
# However, the method still gets called from writesubtree() in | ||||
# _addtree(), so we need to handle it. We should possibly make that | ||||
# avoid calling add() with a clean manifest (_dirty is always False | ||||
# in excludeddir instances). | ||||
pass | ||||