parsers.py
404 lines
| 12.6 KiB
| text/x-python
|
PythonLexer
|
r7700 | # parsers.py - Python implementation of parsers.c | ||
# | ||||
|
r47575 | # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others | ||
|
r7700 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r7700 | |||
|
r27339 | from __future__ import absolute_import | ||
import struct | ||||
import zlib | ||||
|
r47771 | from ..node import ( | ||
nullrev, | ||||
sha1nodeconstants, | ||||
) | ||||
r43925 | from .. import ( | |||
r48009 | error, | |||
r43925 | pycompat, | |||
util, | ||||
) | ||||
|
r43346 | |||
r44486 | from ..revlogutils import nodemap as nodemaputil | |||
r47616 | from ..revlogutils import constants as revlog_constants | |||
r44486 | ||||
|
r36976 | stringio = pycompat.bytesio | ||
|
r7700 | |||
|
r31220 | |||
|
r7700 | _pack = struct.pack | ||
_unpack = struct.unpack | ||||
_compress = zlib.compress | ||||
_decompress = zlib.decompress | ||||
|
r21809 | # Some code below makes tuples directly because it's more convenient. However, | ||
# code outside this module should always use dirstatetuple. | ||||
def dirstatetuple(*x): | ||||
# x is a tuple | ||||
return x | ||||
|
r43346 | |||
|
r29133 | def gettype(q): | ||
return int(q & 0xFFFF) | ||||
|
r7945 | |||
|
r43346 | |||
|
r29133 | def offset_type(offset, type): | ||
|
r31529 | return int(int(offset) << 16 | type) | ||
|
r29133 | |||
|
r43346 | |||
|
r29133 | class BaseIndexObject(object): | ||
|
r47380 | # Format of an index entry according to Python's `struct` language | ||
r47619 | index_format = revlog_constants.INDEX_ENTRY_V1 | |||
|
r47380 | # Size of a C unsigned long long int, platform independent | ||
big_int_size = struct.calcsize(b'>Q') | ||||
# Size of a C long int, platform independent | ||||
int_size = struct.calcsize(b'>i') | ||||
# An empty index entry, used as a default value to be overridden, or nullrev | ||||
r47913 | null_item = (0, 0, 0, -1, -1, -1, -1, sha1nodeconstants.nullid, 0, 0) | |||
|
r47136 | |||
r47736 | @util.propertycache | |||
def entry_size(self): | ||||
return self.index_format.size | ||||
r43974 | @property | |||
def nodemap(self): | ||||
|
r44018 | msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]" | ||
r43974 | util.nouideprecwarn(msg, b'5.3', stacklevel=2) | |||
return self._nodemap | ||||
r43925 | @util.propertycache | |||
r43974 | def _nodemap(self): | |||
|
r47771 | nodemap = nodemaputil.NodeMap({sha1nodeconstants.nullid: nullrev}) | ||
r43925 | for r in range(0, len(self)): | |||
n = self[r][7] | ||||
nodemap[n] = r | ||||
return nodemap | ||||
r43934 | def has_node(self, node): | |||
"""return True if the node exist in the index""" | ||||
r43974 | return node in self._nodemap | |||
r43934 | ||||
r43952 | def rev(self, node): | |||
"""return a revision for a node | ||||
If the node is unknown, raise a RevlogError""" | ||||
r43974 | return self._nodemap[node] | |||
r43952 | ||||
r43954 | def get_rev(self, node): | |||
"""return a revision for a node | ||||
If the node is unknown, return None""" | ||||
r43974 | return self._nodemap.get(node) | |||
r43954 | ||||
r43933 | def _stripnodes(self, start): | |||
r43974 | if '_nodemap' in vars(self): | |||
r43933 | for r in range(start, len(self)): | |||
n = self[r][7] | ||||
r43974 | del self._nodemap[n] | |||
r43933 | ||||
r43925 | def clearcaches(self): | |||
r43974 | self.__dict__.pop('_nodemap', None) | |||
r43925 | ||||
|
r29133 | def __len__(self): | ||
|
r38887 | return self._lgt + len(self._extra) | ||
|
r29133 | |||
|
r38886 | def append(self, tup): | ||
r43974 | if '_nodemap' in vars(self): | |||
self._nodemap[tup[7]] = len(self) | ||||
r47914 | data = self._pack_entry(tup) | |||
|
r46548 | self._extra.append(data) | ||
|
r7945 | |||
r47914 | def _pack_entry(self, entry): | |||
assert entry[8] == 0 | ||||
assert entry[9] == 0 | ||||
return self.index_format.pack(*entry[:8]) | ||||
|
r39251 | def _check_index(self, i): | ||
|
r29133 | if not isinstance(i, int): | ||
|
r43347 | raise TypeError(b"expecting int indexes") | ||
|
r39250 | if i < 0 or i >= len(self): | ||
|
r29133 | raise IndexError | ||
|
r7945 | |||
|
r29133 | def __getitem__(self, i): | ||
|
r39082 | if i == -1: | ||
|
r47136 | return self.null_item | ||
|
r39251 | self._check_index(i) | ||
|
r29133 | if i >= self._lgt: | ||
|
r46548 | data = self._extra[i - self._lgt] | ||
else: | ||||
index = self._calculate_index(i) | ||||
r47736 | data = self._data[index : index + self.entry_size] | |||
r47913 | r = self._unpack_entry(data) | |||
|
r46548 | if self._lgt and i == 0: | ||
r = (offset_type(0, gettype(r[0])),) + r[1:] | ||||
|
r29133 | return r | ||
r47913 | def _unpack_entry(self, data): | |||
r = self.index_format.unpack(data) | ||||
r = r + (0, 0) | ||||
return r | ||||
r47811 | def pack_header(self, header): | |||
"""pack header information as binary""" | ||||
v_fmt = revlog_constants.INDEX_HEADER | ||||
return v_fmt.pack(header) | ||||
def entry_binary(self, rev): | ||||
r47808 | """return the raw binary string representing a revision""" | |||
entry = self[rev] | ||||
r47913 | p = revlog_constants.INDEX_ENTRY_V1.pack(*entry[:8]) | |||
r47808 | if rev == 0: | |||
r47811 | p = p[revlog_constants.INDEX_HEADER.size :] | |||
r47808 | return p | |||
|
r43346 | |||
|
r29133 | class IndexObject(BaseIndexObject): | ||
def __init__(self, data): | ||||
r47808 | assert len(data) % self.entry_size == 0, ( | |||
len(data), | ||||
self.entry_size, | ||||
len(data) % self.entry_size, | ||||
) | ||||
|
r29133 | self._data = data | ||
r47736 | self._lgt = len(data) // self.entry_size | |||
|
r29133 | self._extra = [] | ||
def _calculate_index(self, i): | ||||
r47736 | return i * self.entry_size | |||
|
r13253 | |||
|
r29133 | def __delitem__(self, i): | ||
|
r34332 | if not isinstance(i, slice) or not i.stop == -1 or i.step is not None: | ||
|
r43347 | raise ValueError(b"deleting slices only supports a:-1 with step 1") | ||
|
r39251 | i = i.start | ||
self._check_index(i) | ||||
r43933 | self._stripnodes(i) | |||
|
r29133 | if i < self._lgt: | ||
r47736 | self._data = self._data[: i * self.entry_size] | |||
|
r29133 | self._lgt = i | ||
self._extra = [] | ||||
else: | ||||
|
r43346 | self._extra = self._extra[: i - self._lgt] | ||
|
r29133 | |||
r44794 | class PersistentNodeMapIndexObject(IndexObject): | |||
"""a Debug oriented class to test persistent nodemap | ||||
We need a simple python object to test API and higher level behavior. See | ||||
the Rust implementation for more serious usage. This should be used only | ||||
through the dedicated `devel.persistent-nodemap` config. | ||||
""" | ||||
r44795 | def nodemap_data_all(self): | |||
"""Return bytes containing a full serialization of a nodemap | ||||
The nodemap should be valid for the full set of revisions in the | ||||
index.""" | ||||
return nodemaputil.persistent_data(self) | ||||
r44805 | def nodemap_data_incremental(self): | |||
"""Return bytes containing a incremental update to persistent nodemap | ||||
This containst the data for an append-only update of the data provided | ||||
in the last call to `update_nodemap_data`. | ||||
""" | ||||
if self._nm_root is None: | ||||
return None | ||||
r44809 | docket = self._nm_docket | |||
r44808 | changed, data = nodemaputil.update_persistent_data( | |||
r44809 | self, self._nm_root, self._nm_max_idx, self._nm_docket.tip_rev | |||
r44805 | ) | |||
r44809 | ||||
self._nm_root = self._nm_max_idx = self._nm_docket = None | ||||
return docket, changed, data | ||||
r44805 | ||||
r44807 | def update_nodemap_data(self, docket, nm_data): | |||
"""provide full block of persisted binary data for a nodemap | ||||
r44801 | ||||
The data are expected to come from disk. See `nodemap_data_all` for a | ||||
produceur of such data.""" | ||||
if nm_data is not None: | ||||
r44805 | self._nm_root, self._nm_max_idx = nodemaputil.parse_data(nm_data) | |||
if self._nm_root: | ||||
r44809 | self._nm_docket = docket | |||
r44805 | else: | |||
r44809 | self._nm_root = self._nm_max_idx = self._nm_docket = None | |||
r44801 | ||||
r44794 | ||||
|
r29133 | class InlinedIndexObject(BaseIndexObject): | ||
def __init__(self, data, inline=0): | ||||
self._data = data | ||||
self._lgt = self._inline_scan(None) | ||||
self._inline_scan(self._lgt) | ||||
self._extra = [] | ||||
|
r7700 | |||
|
r29133 | def _inline_scan(self, lgt): | ||
off = 0 | ||||
if lgt is not None: | ||||
self._offsets = [0] * lgt | ||||
count = 0 | ||||
r47736 | while off <= len(self._data) - self.entry_size: | |||
|
r47136 | start = off + self.big_int_size | ||
|
r43346 | (s,) = struct.unpack( | ||
|
r47136 | b'>i', | ||
self._data[start : start + self.int_size], | ||||
|
r43346 | ) | ||
|
r29133 | if lgt is not None: | ||
self._offsets[count] = off | ||||
count += 1 | ||||
r47736 | off += self.entry_size + s | |||
|
r29133 | if off != len(self._data): | ||
|
r43347 | raise ValueError(b"corrupted data") | ||
|
r29133 | return count | ||
|
r14421 | |||
|
r29133 | def __delitem__(self, i): | ||
|
r34332 | if not isinstance(i, slice) or not i.stop == -1 or i.step is not None: | ||
|
r43347 | raise ValueError(b"deleting slices only supports a:-1 with step 1") | ||
|
r39251 | i = i.start | ||
self._check_index(i) | ||||
r43933 | self._stripnodes(i) | |||
|
r29133 | if i < self._lgt: | ||
self._offsets = self._offsets[:i] | ||||
self._lgt = i | ||||
self._extra = [] | ||||
else: | ||||
|
r43346 | self._extra = self._extra[: i - self._lgt] | ||
|
r7700 | |||
|
r29133 | def _calculate_index(self, i): | ||
return self._offsets[i] | ||||
|
r7700 | |||
|
r43346 | |||
|
r47438 | def parse_index2(data, inline, revlogv2=False): | ||
|
r29133 | if not inline: | ||
|
r47438 | cls = IndexObject2 if revlogv2 else IndexObject | ||
return cls(data), None | ||||
cls = InlinedIndexObject2 if revlogv2 else InlinedIndexObject | ||||
return cls(data, inline), (0, data) | ||||
class Index2Mixin(object): | ||||
r47619 | index_format = revlog_constants.INDEX_ENTRY_V2 | |||
|
r47438 | |||
|
r47844 | def replace_sidedata_info( | ||
r48017 | self, rev, sidedata_offset, sidedata_length, offset_flags | |||
|
r47844 | ): | ||
|
r47451 | """ | ||
Replace an existing index entry's sidedata offset and length with new | ||||
ones. | ||||
This cannot be used outside of the context of sidedata rewriting, | ||||
r48017 | inside the transaction that creates the revision `rev`. | |||
|
r47451 | """ | ||
r48017 | if rev < 0: | |||
|
r47451 | raise KeyError | ||
r48017 | self._check_index(rev) | |||
|
r47451 | sidedata_format = b">Qi" | ||
packed_size = struct.calcsize(sidedata_format) | ||||
r48017 | if rev >= self._lgt: | |||
|
r47451 | packed = _pack(sidedata_format, sidedata_offset, sidedata_length) | ||
r48017 | old = self._extra[rev - self._lgt] | |||
|
r47844 | offset_flags = struct.pack(b">Q", offset_flags) | ||
new = offset_flags + old[8:64] + packed + old[64 + packed_size :] | ||||
r48017 | self._extra[rev - self._lgt] = new | |||
|
r47451 | else: | ||
msg = b"cannot rewrite entries outside of this transaction" | ||||
raise KeyError(msg) | ||||
r47913 | def _unpack_entry(self, data): | |||
return self.index_format.unpack(data) | ||||
r47914 | def _pack_entry(self, entry): | |||
return self.index_format.pack(*entry) | ||||
r47811 | def entry_binary(self, rev): | |||
r47808 | """return the raw binary string representing a revision""" | |||
entry = self[rev] | ||||
p = revlog_constants.INDEX_ENTRY_V2.pack(*entry) | ||||
return p | ||||
r48009 | def pack_header(self, header): | |||
"""pack header information as binary""" | ||||
msg = 'version header should go in the docket, not the index: %d' | ||||
msg %= header | ||||
raise error.ProgrammingError(msg) | ||||
|
r47438 | |||
class IndexObject2(Index2Mixin, IndexObject): | ||||
pass | ||||
class InlinedIndexObject2(Index2Mixin, InlinedIndexObject): | ||||
def _inline_scan(self, lgt): | ||||
sidedata_length_pos = 72 | ||||
off = 0 | ||||
if lgt is not None: | ||||
self._offsets = [0] * lgt | ||||
count = 0 | ||||
r47736 | while off <= len(self._data) - self.entry_size: | |||
|
r47438 | start = off + self.big_int_size | ||
(data_size,) = struct.unpack( | ||||
b'>i', | ||||
self._data[start : start + self.int_size], | ||||
) | ||||
start = off + sidedata_length_pos | ||||
(side_data_size,) = struct.unpack( | ||||
b'>i', self._data[start : start + self.int_size] | ||||
) | ||||
if lgt is not None: | ||||
self._offsets[count] = off | ||||
count += 1 | ||||
r47736 | off += self.entry_size + data_size + side_data_size | |||
|
r47438 | if off != len(self._data): | ||
raise ValueError(b"corrupted data") | ||||
return count | ||||
|
r7700 | |||
|
r43346 | |||
r44794 | def parse_index_devel_nodemap(data, inline): | |||
|
r46554 | """like parse_index2, but alway return a PersistentNodeMapIndexObject""" | ||
r44794 | return PersistentNodeMapIndexObject(data), None | |||
|
r7700 | def parse_dirstate(dmap, copymap, st): | ||
|
r43346 | parents = [st[:20], st[20:40]] | ||
|
r17425 | # dereference fields so they will be local in loop | ||
|
r43347 | format = b">cllll" | ||
|
r7945 | e_size = struct.calcsize(format) | ||
|
r7700 | pos1 = 40 | ||
l = len(st) | ||||
# the inner loop | ||||
while pos1 < l: | ||||
pos2 = pos1 + e_size | ||||
|
r43347 | e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster | ||
|
r7700 | pos1 = pos2 + e[4] | ||
f = st[pos2:pos1] | ||||
|
r43347 | if b'\0' in f: | ||
f, c = f.split(b'\0') | ||||
|
r7700 | copymap[f] = c | ||
dmap[f] = e[:4] | ||||
return parents | ||||
|
r18567 | |||
|
r43346 | |||
|
r18567 | def pack_dirstate(dmap, copymap, pl, now): | ||
now = int(now) | ||||
|
r28861 | cs = stringio() | ||
|
r18567 | write = cs.write | ||
|
r43347 | write(b"".join(pl)) | ||
|
r43376 | for f, e in pycompat.iteritems(dmap): | ||
|
r43347 | if e[0] == b'n' and e[3] == now: | ||
|
r18567 | # The file was last modified "simultaneously" with the current | ||
# write to dirstate (i.e. within the same second for file- | ||||
# systems with a granularity of 1 sec). This commonly happens | ||||
# for at least a couple of files on 'update'. | ||||
# The user could change the file without changing its size | ||||
|
r19652 | # within the same second. Invalidate the file's mtime in | ||
|
r18567 | # dirstate, forcing future 'status' calls to compare the | ||
|
r19652 | # contents of the file if the size is the same. This prevents | ||
# mistakenly treating such files as clean. | ||||
|
r21809 | e = dirstatetuple(e[0], e[1], e[2], -1) | ||
|
r18567 | dmap[f] = e | ||
if f in copymap: | ||||
|
r43347 | f = b"%s\0%s" % (f, copymap[f]) | ||
e = _pack(b">cllll", e[0], e[1], e[2], e[3], len(f)) | ||||
|
r18567 | write(e) | ||
write(f) | ||||
return cs.getvalue() | ||||