parsers.py
974 lines
| 31.4 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r7700 | # parsers.py - Python implementation of parsers.c | ||
# | ||||
Raphaël Gomès
|
r47575 | # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others | ||
Martin Geisler
|
r7700 | # | ||
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. | ||
Martin Geisler
|
r7700 | |||
Gregory Szorc
|
r27339 | |||
Gregory Szorc
|
r49728 | import io | ||
Simon Sapin
|
r49035 | import stat | ||
Gregory Szorc
|
r27339 | import struct | ||
import zlib | ||||
Joerg Sonnenberger
|
r47771 | from ..node import ( | ||
nullrev, | ||||
sha1nodeconstants, | ||||
) | ||||
Augie Fackler
|
r48366 | from ..thirdparty import attr | ||
r43925 | from .. import ( | |||
r48009 | error, | |||
r48186 | revlogutils, | |||
r43925 | util, | |||
) | ||||
Augie Fackler
|
r43346 | |||
r44486 | from ..revlogutils import nodemap as nodemaputil | |||
r47616 | from ..revlogutils import constants as revlog_constants | |||
r44486 | ||||
Gregory Szorc
|
r49728 | stringio = io.BytesIO | ||
Martin Geisler
|
r7700 | |||
Pulkit Goyal
|
r31220 | |||
Martin Geisler
|
r7700 | _pack = struct.pack | ||
_unpack = struct.unpack | ||||
_compress = zlib.compress | ||||
_decompress = zlib.decompress | ||||
r48296 | ||||
r48303 | # a special value used internally for `size` if the file come from the other parent | |||
FROM_P2 = -2 | ||||
r48305 | # a special value used internally for `size` if the file is modified/merged/added | |||
NONNORMAL = -1 | ||||
r48329 | # a special value used internally for `time` if the time is ambigeous | |||
AMBIGUOUS_TIME = -1 | ||||
Simon Sapin
|
r49035 | # Bits of the `flags` byte inside a node in the file format | ||
DIRSTATE_V2_WDIR_TRACKED = 1 << 0 | ||||
DIRSTATE_V2_P1_TRACKED = 1 << 1 | ||||
DIRSTATE_V2_P2_INFO = 1 << 2 | ||||
r49085 | DIRSTATE_V2_MODE_EXEC_PERM = 1 << 3 | |||
DIRSTATE_V2_MODE_IS_SYMLINK = 1 << 4 | ||||
DIRSTATE_V2_HAS_FALLBACK_EXEC = 1 << 5 | ||||
DIRSTATE_V2_FALLBACK_EXEC = 1 << 6 | ||||
DIRSTATE_V2_HAS_FALLBACK_SYMLINK = 1 << 7 | ||||
DIRSTATE_V2_FALLBACK_SYMLINK = 1 << 8 | ||||
DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED = 1 << 9 | ||||
DIRSTATE_V2_HAS_MODE_AND_SIZE = 1 << 10 | ||||
DIRSTATE_V2_HAS_MTIME = 1 << 11 | ||||
DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS = 1 << 12 | ||||
DIRSTATE_V2_DIRECTORY = 1 << 13 | ||||
DIRSTATE_V2_ALL_UNKNOWN_RECORDED = 1 << 14 | ||||
DIRSTATE_V2_ALL_IGNORED_RECORDED = 1 << 15 | ||||
Simon Sapin
|
r49035 | |||
r48303 | ||||
r48464 | @attr.s(slots=True, init=False) | |||
Gregory Szorc
|
r49801 | class DirstateItem: | ||
r48296 | """represent a dirstate entry | |||
r48924 | It hold multiple attributes | |||
# about file tracking | ||||
- wc_tracked: is the file tracked by the working copy | ||||
- p1_tracked: is the file tracked in working copy first parent | ||||
r48950 | - p2_info: the file has been involved in some merge operation. Either | |||
because it was actually merged, or because the p2 version was | ||||
Simon Sapin
|
r49002 | ahead, or because some rename moved it there. In either case | ||
r48950 | `hg status` will want it displayed as modified. | |||
r48924 | ||||
# about the file state expected from p1 manifest: | ||||
- mode: the file mode in p1 | ||||
- size: the file size in p1 | ||||
r48950 | These value can be set to None, which mean we don't have a meaningful value | |||
to compare with. Either because we don't really care about them as there | ||||
`status` is known without having to look at the disk or because we don't | ||||
know these right now and a full comparison will be needed to find out if | ||||
the file is clean. | ||||
r48924 | # about the file state on disk last time we saw it: | |||
- mtime: the last known clean mtime for the file. | ||||
r48950 | This value can be set to None if no cachable state exist. Either because we | |||
do not care (see previous section) or because we could not cache something | ||||
yet. | ||||
r48284 | """ | |||
r48739 | _wc_tracked = attr.ib() | |||
_p1_tracked = attr.ib() | ||||
r48950 | _p2_info = attr.ib() | |||
Augie Fackler
|
r48366 | _mode = attr.ib() | ||
_size = attr.ib() | ||||
Simon Sapin
|
r49079 | _mtime_s = attr.ib() | ||
_mtime_ns = attr.ib() | ||||
r49068 | _fallback_exec = attr.ib() | |||
_fallback_symlink = attr.ib() | ||||
r49227 | _mtime_second_ambiguous = attr.ib() | |||
r48296 | ||||
r48706 | def __init__( | |||
self, | ||||
wc_tracked=False, | ||||
p1_tracked=False, | ||||
r48950 | p2_info=False, | |||
has_meaningful_data=True, | ||||
has_meaningful_mtime=True, | ||||
r48706 | parentfiledata=None, | |||
r49069 | fallback_exec=None, | |||
fallback_symlink=None, | ||||
r48706 | ): | |||
r48739 | self._wc_tracked = wc_tracked | |||
self._p1_tracked = p1_tracked | ||||
r48950 | self._p2_info = p2_info | |||
r49069 | self._fallback_exec = fallback_exec | |||
self._fallback_symlink = fallback_symlink | ||||
r49068 | ||||
r48950 | self._mode = None | |||
self._size = None | ||||
Simon Sapin
|
r49079 | self._mtime_s = None | ||
self._mtime_ns = None | ||||
r49227 | self._mtime_second_ambiguous = False | |||
r48739 | if parentfiledata is None: | |||
r48950 | has_meaningful_mtime = False | |||
has_meaningful_data = False | ||||
r49201 | elif parentfiledata[2] is None: | |||
has_meaningful_mtime = False | ||||
r48950 | if has_meaningful_data: | |||
r48706 | self._mode = parentfiledata[0] | |||
self._size = parentfiledata[1] | ||||
r48950 | if has_meaningful_mtime: | |||
r49227 | ( | |||
self._mtime_s, | ||||
self._mtime_ns, | ||||
self._mtime_second_ambiguous, | ||||
) = parentfiledata[2] | ||||
r48464 | ||||
r48465 | @classmethod | |||
Simon Sapin
|
r49079 | def from_v2_data(cls, flags, size, mtime_s, mtime_ns): | ||
Simon Sapin
|
r49035 | """Build a new DirstateItem object from V2 data""" | ||
has_mode_size = bool(flags & DIRSTATE_V2_HAS_MODE_AND_SIZE) | ||||
r49083 | has_meaningful_mtime = bool(flags & DIRSTATE_V2_HAS_MTIME) | |||
Simon Sapin
|
r49035 | mode = None | ||
Simon Sapin
|
r49066 | |||
if flags & +DIRSTATE_V2_EXPECTED_STATE_IS_MODIFIED: | ||||
# we do not have support for this flag in the code yet, | ||||
# force a lookup for this file. | ||||
has_mode_size = False | ||||
has_meaningful_mtime = False | ||||
r49070 | fallback_exec = None | |||
if flags & DIRSTATE_V2_HAS_FALLBACK_EXEC: | ||||
fallback_exec = flags & DIRSTATE_V2_FALLBACK_EXEC | ||||
fallback_symlink = None | ||||
if flags & DIRSTATE_V2_HAS_FALLBACK_SYMLINK: | ||||
fallback_symlink = flags & DIRSTATE_V2_FALLBACK_SYMLINK | ||||
Simon Sapin
|
r49035 | if has_mode_size: | ||
assert stat.S_IXUSR == 0o100 | ||||
if flags & DIRSTATE_V2_MODE_EXEC_PERM: | ||||
mode = 0o755 | ||||
else: | ||||
mode = 0o644 | ||||
if flags & DIRSTATE_V2_MODE_IS_SYMLINK: | ||||
mode |= stat.S_IFLNK | ||||
else: | ||||
mode |= stat.S_IFREG | ||||
r49231 | ||||
second_ambiguous = flags & DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS | ||||
Simon Sapin
|
r49035 | return cls( | ||
wc_tracked=bool(flags & DIRSTATE_V2_WDIR_TRACKED), | ||||
p1_tracked=bool(flags & DIRSTATE_V2_P1_TRACKED), | ||||
p2_info=bool(flags & DIRSTATE_V2_P2_INFO), | ||||
has_meaningful_data=has_mode_size, | ||||
Simon Sapin
|
r49066 | has_meaningful_mtime=has_meaningful_mtime, | ||
r49231 | parentfiledata=(mode, size, (mtime_s, mtime_ns, second_ambiguous)), | |||
r49070 | fallback_exec=fallback_exec, | |||
fallback_symlink=fallback_symlink, | ||||
Simon Sapin
|
r49035 | ) | ||
@classmethod | ||||
r48465 | def from_v1_data(cls, state, mode, size, mtime): | |||
"""Build a new DirstateItem object from V1 data | ||||
Since the dirstate-v1 format is frozen, the signature of this function | ||||
is not expected to change, unlike the __init__ one. | ||||
""" | ||||
r48739 | if state == b'm': | |||
r48966 | return cls(wc_tracked=True, p1_tracked=True, p2_info=True) | |||
r48739 | elif state == b'a': | |||
r48968 | return cls(wc_tracked=True) | |||
r48739 | elif state == b'r': | |||
if size == NONNORMAL: | ||||
r48950 | p1_tracked = True | |||
p2_info = True | ||||
r48739 | elif size == FROM_P2: | |||
r48950 | p1_tracked = False | |||
p2_info = True | ||||
r48739 | else: | |||
r48950 | p1_tracked = True | |||
p2_info = False | ||||
return cls(p1_tracked=p1_tracked, p2_info=p2_info) | ||||
r48739 | elif state == b'n': | |||
if size == FROM_P2: | ||||
r48970 | return cls(wc_tracked=True, p2_info=True) | |||
r48739 | elif size == NONNORMAL: | |||
r48972 | return cls(wc_tracked=True, p1_tracked=True) | |||
r48739 | elif mtime == AMBIGUOUS_TIME: | |||
r48974 | return cls( | |||
wc_tracked=True, | ||||
p1_tracked=True, | ||||
has_meaningful_mtime=False, | ||||
r49227 | parentfiledata=(mode, size, (42, 0, False)), | |||
r48974 | ) | |||
r48739 | else: | |||
r48975 | return cls( | |||
wc_tracked=True, | ||||
p1_tracked=True, | ||||
r49227 | parentfiledata=(mode, size, (mtime, 0, False)), | |||
r48975 | ) | |||
r48739 | else: | |||
raise RuntimeError(b'unknown state: %s' % state) | ||||
r48465 | ||||
r48466 | def set_possibly_dirty(self): | |||
"""Mark a file as "possibly dirty" | ||||
This means the next status call will have to actually check its content | ||||
to make sure it is correct. | ||||
""" | ||||
Simon Sapin
|
r49079 | self._mtime_s = None | ||
self._mtime_ns = None | ||||
r48466 | ||||
r48788 | def set_clean(self, mode, size, mtime): | |||
"""mark a file as "clean" cancelling potential "possibly dirty call" | ||||
Note: this function is a descendant of `dirstate.normal` and is | ||||
currently expected to be call on "normal" entry only. There are not | ||||
reason for this to not change in the future as long as the ccode is | ||||
updated to preserve the proper state of the non-normal files. | ||||
""" | ||||
self._wc_tracked = True | ||||
self._p1_tracked = True | ||||
self._mode = mode | ||||
self._size = size | ||||
r49227 | self._mtime_s, self._mtime_ns, self._mtime_second_ambiguous = mtime | |||
r48788 | ||||
r48804 | def set_tracked(self): | |||
"""mark a file as tracked in the working copy | ||||
This will ultimately be called by command like `hg add`. | ||||
""" | ||||
self._wc_tracked = True | ||||
r48950 | # `set_tracked` is replacing various `normallookup` call. So we mark | |||
# the files as needing lookup | ||||
r48804 | # | |||
# Consider dropping this in the future in favor of something less broad. | ||||
Simon Sapin
|
r49079 | self._mtime_s = None | ||
self._mtime_ns = None | ||||
r48804 | ||||
r48701 | def set_untracked(self): | |||
"""mark a file as untracked in the working copy | ||||
This will ultimately be called by command like `hg remove`. | ||||
""" | ||||
r48739 | self._wc_tracked = False | |||
self._mode = None | ||||
self._size = None | ||||
Simon Sapin
|
r49079 | self._mtime_s = None | ||
self._mtime_ns = None | ||||
r48701 | ||||
r48874 | def drop_merge_data(self): | |||
Raphaël Gomès
|
r50010 | """remove all "merge-only" information from a DirstateItem | ||
r48874 | ||||
This is to be call by the dirstatemap code when the second parent is dropped | ||||
""" | ||||
r48950 | if self._p2_info: | |||
self._p2_info = False | ||||
self._mode = None | ||||
self._size = None | ||||
Simon Sapin
|
r49079 | self._mtime_s = None | ||
self._mtime_ns = None | ||||
r48874 | ||||
r48301 | @property | |||
r48325 | def mode(self): | |||
Raphaël Gomès
|
r49920 | return self._v1_mode() | ||
r48325 | ||||
@property | ||||
r48326 | def size(self): | |||
Raphaël Gomès
|
r49920 | return self._v1_size() | ||
r48326 | ||||
@property | ||||
r48327 | def mtime(self): | |||
Raphaël Gomès
|
r49920 | return self._v1_mtime() | ||
r48327 | ||||
Simon Sapin
|
r49079 | def mtime_likely_equal_to(self, other_mtime): | ||
self_sec = self._mtime_s | ||||
if self_sec is None: | ||||
return False | ||||
self_ns = self._mtime_ns | ||||
r49227 | other_sec, other_ns, second_ambiguous = other_mtime | |||
r49228 | if self_sec != other_sec: | |||
# seconds are different theses mtime are definitly not equal | ||||
return False | ||||
elif other_ns == 0 or self_ns == 0: | ||||
# at least one side as no nano-seconds information | ||||
if self._mtime_second_ambiguous: | ||||
# We cannot trust the mtime in this case | ||||
return False | ||||
else: | ||||
# the "seconds" value was reliable on its own. We are good to go. | ||||
return True | ||||
else: | ||||
# We have nano second information, let us use them ! | ||||
return self_ns == other_ns | ||||
Simon Sapin
|
r49079 | |||
r48327 | @property | |||
r48301 | def state(self): | |||
""" | ||||
States are: | ||||
n normal | ||||
m needs merging | ||||
r marked for removal | ||||
a marked for addition | ||||
XXX This "state" is a bit obscure and mostly a direct expression of the | ||||
dirstatev1 format. It would make sense to ultimately deprecate it in | ||||
favor of the more "semantic" attributes. | ||||
""" | ||||
r48923 | if not self.any_tracked: | |||
r48900 | return b'?' | |||
Raphaël Gomès
|
r49920 | return self._v1_state() | ||
r48301 | ||||
r48302 | @property | |||
r49068 | def has_fallback_exec(self): | |||
"""True if "fallback" information are available for the "exec" bit | ||||
Fallback information can be stored in the dirstate to keep track of | ||||
filesystem attribute tracked by Mercurial when the underlying file | ||||
system or operating system does not support that property, (e.g. | ||||
Windows). | ||||
Not all version of the dirstate on-disk storage support preserving this | ||||
information. | ||||
""" | ||||
return self._fallback_exec is not None | ||||
@property | ||||
def fallback_exec(self): | ||||
""" "fallback" information for the executable bit | ||||
True if the file should be considered executable when we cannot get | ||||
this information from the files system. False if it should be | ||||
considered non-executable. | ||||
See has_fallback_exec for details.""" | ||||
return self._fallback_exec | ||||
@fallback_exec.setter | ||||
def set_fallback_exec(self, value): | ||||
"""control "fallback" executable bit | ||||
Set to: | ||||
- True if the file should be considered executable, | ||||
- False if the file should be considered non-executable, | ||||
- None if we do not have valid fallback data. | ||||
See has_fallback_exec for details.""" | ||||
if value is None: | ||||
self._fallback_exec = None | ||||
else: | ||||
self._fallback_exec = bool(value) | ||||
@property | ||||
def has_fallback_symlink(self): | ||||
"""True if "fallback" information are available for symlink status | ||||
Fallback information can be stored in the dirstate to keep track of | ||||
filesystem attribute tracked by Mercurial when the underlying file | ||||
system or operating system does not support that property, (e.g. | ||||
Windows). | ||||
Not all version of the dirstate on-disk storage support preserving this | ||||
information.""" | ||||
return self._fallback_symlink is not None | ||||
@property | ||||
def fallback_symlink(self): | ||||
""" "fallback" information for symlink status | ||||
True if the file should be considered executable when we cannot get | ||||
this information from the files system. False if it should be | ||||
considered non-executable. | ||||
See has_fallback_exec for details.""" | ||||
return self._fallback_symlink | ||||
@fallback_symlink.setter | ||||
def set_fallback_symlink(self, value): | ||||
"""control "fallback" symlink status | ||||
Set to: | ||||
- True if the file should be considered a symlink, | ||||
- False if the file should be considered not a symlink, | ||||
- None if we do not have valid fallback data. | ||||
See has_fallback_symlink for details.""" | ||||
if value is None: | ||||
self._fallback_symlink = None | ||||
else: | ||||
self._fallback_symlink = bool(value) | ||||
@property | ||||
r48320 | def tracked(self): | |||
"""True is the file is tracked in the working copy""" | ||||
r48740 | return self._wc_tracked | |||
r48320 | ||||
@property | ||||
r48899 | def any_tracked(self): | |||
"""True is the file is tracked anywhere (wc or parents)""" | ||||
r48950 | return self._wc_tracked or self._p1_tracked or self._p2_info | |||
r48899 | ||||
@property | ||||
r48315 | def added(self): | |||
"""True if the file has been added""" | ||||
r48950 | return self._wc_tracked and not (self._p1_tracked or self._p2_info) | |||
r48315 | ||||
@property | ||||
r48898 | def maybe_clean(self): | |||
"""True if the file has a chance to be in the "clean" state""" | ||||
if not self._wc_tracked: | ||||
return False | ||||
r48950 | elif not self._p1_tracked: | |||
r48898 | return False | |||
r48950 | elif self._p2_info: | |||
r48898 | return False | |||
return True | ||||
@property | ||||
r48955 | def p1_tracked(self): | |||
"""True if the file is tracked in the first parent manifest""" | ||||
return self._p1_tracked | ||||
@property | ||||
r48954 | def p2_info(self): | |||
"""True if the file needed to merge or apply any input from p2 | ||||
See the class documentation for details. | ||||
""" | ||||
return self._wc_tracked and self._p2_info | ||||
@property | ||||
r48304 | def removed(self): | |||
"""True if the file has been removed""" | ||||
r48950 | return not self._wc_tracked and (self._p1_tracked or self._p2_info) | |||
r48304 | ||||
Raphaël Gomès
|
r49036 | def v2_data(self): | ||
"""Returns (flags, mode, size, mtime) for v2 serialization""" | ||||
flags = 0 | ||||
if self._wc_tracked: | ||||
flags |= DIRSTATE_V2_WDIR_TRACKED | ||||
if self._p1_tracked: | ||||
flags |= DIRSTATE_V2_P1_TRACKED | ||||
if self._p2_info: | ||||
flags |= DIRSTATE_V2_P2_INFO | ||||
Simon Sapin
|
r49043 | if self._mode is not None and self._size is not None: | ||
Raphaël Gomès
|
r49036 | flags |= DIRSTATE_V2_HAS_MODE_AND_SIZE | ||
if self.mode & stat.S_IXUSR: | ||||
flags |= DIRSTATE_V2_MODE_EXEC_PERM | ||||
if stat.S_ISLNK(self.mode): | ||||
flags |= DIRSTATE_V2_MODE_IS_SYMLINK | ||||
Simon Sapin
|
r49079 | if self._mtime_s is not None: | ||
r49083 | flags |= DIRSTATE_V2_HAS_MTIME | |||
r49230 | if self._mtime_second_ambiguous: | |||
flags |= DIRSTATE_V2_MTIME_SECOND_AMBIGUOUS | ||||
r49070 | ||||
if self._fallback_exec is not None: | ||||
flags |= DIRSTATE_V2_HAS_FALLBACK_EXEC | ||||
if self._fallback_exec: | ||||
flags |= DIRSTATE_V2_FALLBACK_EXEC | ||||
if self._fallback_symlink is not None: | ||||
flags |= DIRSTATE_V2_HAS_FALLBACK_SYMLINK | ||||
if self._fallback_symlink: | ||||
flags |= DIRSTATE_V2_FALLBACK_SYMLINK | ||||
r49067 | # Note: we do not need to do anything regarding | |||
# DIRSTATE_V2_ALL_UNKNOWN_RECORDED and DIRSTATE_V2_ALL_IGNORED_RECORDED | ||||
# since we never set _DIRSTATE_V2_HAS_DIRCTORY_MTIME | ||||
Simon Sapin
|
r49079 | return (flags, self._size or 0, self._mtime_s or 0, self._mtime_ns or 0) | ||
Raphaël Gomès
|
r49036 | |||
Raphaël Gomès
|
r49920 | def _v1_state(self): | ||
r48298 | """return a "state" suitable for v1 serialization""" | |||
r48950 | if not self.any_tracked: | |||
r48739 | # the object has no state to record, this is -currently- | |||
# unsupported | ||||
raise RuntimeError('untracked item') | ||||
r48748 | elif self.removed: | |||
r48739 | return b'r' | |||
r48964 | elif self._p1_tracked and self._p2_info: | |||
r48739 | return b'm' | |||
r48748 | elif self.added: | |||
r48739 | return b'a' | |||
r48748 | else: | |||
r48739 | return b'n' | |||
r48298 | ||||
Raphaël Gomès
|
r49920 | def _v1_mode(self): | ||
r48298 | """return a "mode" suitable for v1 serialization""" | |||
r48739 | return self._mode if self._mode is not None else 0 | |||
r48298 | ||||
Raphaël Gomès
|
r49920 | def _v1_size(self): | ||
r48298 | """return a "size" suitable for v1 serialization""" | |||
r48923 | if not self.any_tracked: | |||
r48739 | # the object has no state to record, this is -currently- | |||
# unsupported | ||||
raise RuntimeError('untracked item') | ||||
r48950 | elif self.removed and self._p1_tracked and self._p2_info: | |||
r48749 | return NONNORMAL | |||
r48961 | elif self._p2_info: | |||
r48739 | return FROM_P2 | |||
r48749 | elif self.removed: | |||
return 0 | ||||
elif self.added: | ||||
r48739 | return NONNORMAL | |||
r48950 | elif self._size is None: | |||
return NONNORMAL | ||||
r48749 | else: | |||
r48739 | return self._size | |||
r48298 | ||||
Raphaël Gomès
|
r49920 | def _v1_mtime(self): | ||
r48298 | """return a "mtime" suitable for v1 serialization""" | |||
r48923 | if not self.any_tracked: | |||
r48739 | # the object has no state to record, this is -currently- | |||
# unsupported | ||||
raise RuntimeError('untracked item') | ||||
r48750 | elif self.removed: | |||
r48739 | return 0 | |||
Simon Sapin
|
r49079 | elif self._mtime_s is None: | ||
r48739 | return AMBIGUOUS_TIME | |||
r48950 | elif self._p2_info: | |||
r48739 | return AMBIGUOUS_TIME | |||
r48950 | elif not self._p1_tracked: | |||
r48750 | return AMBIGUOUS_TIME | |||
r49229 | elif self._mtime_second_ambiguous: | |||
return AMBIGUOUS_TIME | ||||
r48739 | else: | |||
Simon Sapin
|
r49079 | return self._mtime_s | ||
r48298 | ||||
Augie Fackler
|
r43346 | |||
Maciej Fijalkowski
|
r29133 | def gettype(q): | ||
return int(q & 0xFFFF) | ||||
Matt Mackall
|
r7945 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class BaseIndexObject: | ||
r48042 | # Can I be passed to an algorithme implemented in Rust ? | |||
rust_ext_compat = 0 | ||||
Raphaël Gomès
|
r47380 | # Format of an index entry according to Python's `struct` language | ||
r47619 | index_format = revlog_constants.INDEX_ENTRY_V1 | |||
Raphaël Gomès
|
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 | ||||
r48023 | null_item = ( | |||
0, | ||||
0, | ||||
0, | ||||
-1, | ||||
-1, | ||||
-1, | ||||
-1, | ||||
sha1nodeconstants.nullid, | ||||
0, | ||||
0, | ||||
revlog_constants.COMP_MODE_INLINE, | ||||
r48030 | revlog_constants.COMP_MODE_INLINE, | |||
r49330 | revlog_constants.RANK_UNKNOWN, | |||
r48023 | ) | |||
Raphaël Gomès
|
r47136 | |||
r47736 | @util.propertycache | |||
def entry_size(self): | ||||
return self.index_format.size | ||||
r43925 | @util.propertycache | |||
r43974 | def _nodemap(self): | |||
Joerg Sonnenberger
|
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 | ||||
Maciej Fijalkowski
|
r29133 | def __len__(self): | ||
Martin von Zweigbergk
|
r38887 | return self._lgt + len(self._extra) | ||
Maciej Fijalkowski
|
r29133 | |||
Martin von Zweigbergk
|
r38886 | def append(self, tup): | ||
r43974 | if '_nodemap' in vars(self): | |||
self._nodemap[tup[7]] = len(self) | ||||
r48041 | data = self._pack_entry(len(self), tup) | |||
Joerg Sonnenberger
|
r46548 | self._extra.append(data) | ||
Matt Mackall
|
r7945 | |||
r48041 | def _pack_entry(self, rev, entry): | |||
r47914 | assert entry[8] == 0 | |||
assert entry[9] == 0 | ||||
return self.index_format.pack(*entry[:8]) | ||||
Martin von Zweigbergk
|
r39251 | def _check_index(self, i): | ||
Maciej Fijalkowski
|
r29133 | if not isinstance(i, int): | ||
Augie Fackler
|
r43347 | raise TypeError(b"expecting int indexes") | ||
Martin von Zweigbergk
|
r39250 | if i < 0 or i >= len(self): | ||
r49327 | raise IndexError(i) | |||
Matt Mackall
|
r7945 | |||
Maciej Fijalkowski
|
r29133 | def __getitem__(self, i): | ||
Augie Fackler
|
r39082 | if i == -1: | ||
Raphaël Gomès
|
r47136 | return self.null_item | ||
Martin von Zweigbergk
|
r39251 | self._check_index(i) | ||
Maciej Fijalkowski
|
r29133 | if i >= self._lgt: | ||
Joerg Sonnenberger
|
r46548 | data = self._extra[i - self._lgt] | ||
else: | ||||
index = self._calculate_index(i) | ||||
r47736 | data = self._data[index : index + self.entry_size] | |||
r48041 | r = self._unpack_entry(i, data) | |||
Joerg Sonnenberger
|
r46548 | if self._lgt and i == 0: | ||
r48186 | offset = revlogutils.offset_type(0, gettype(r[0])) | |||
r = (offset,) + r[1:] | ||||
Maciej Fijalkowski
|
r29133 | return r | ||
r48041 | def _unpack_entry(self, rev, data): | |||
r47913 | r = self.index_format.unpack(data) | |||
r48030 | r = r + ( | |||
0, | ||||
0, | ||||
revlog_constants.COMP_MODE_INLINE, | ||||
revlog_constants.COMP_MODE_INLINE, | ||||
r49330 | revlog_constants.RANK_UNKNOWN, | |||
r48030 | ) | |||
r47913 | 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 | |||
Augie Fackler
|
r43346 | |||
Maciej Fijalkowski
|
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, | ||||
) | ||||
Maciej Fijalkowski
|
r29133 | self._data = data | ||
r47736 | self._lgt = len(data) // self.entry_size | |||
Maciej Fijalkowski
|
r29133 | self._extra = [] | ||
def _calculate_index(self, i): | ||||
r47736 | return i * self.entry_size | |||
Matt Mackall
|
r13253 | |||
Maciej Fijalkowski
|
r29133 | def __delitem__(self, i): | ||
Alex Gaynor
|
r34332 | if not isinstance(i, slice) or not i.stop == -1 or i.step is not None: | ||
Augie Fackler
|
r43347 | raise ValueError(b"deleting slices only supports a:-1 with step 1") | ||
Martin von Zweigbergk
|
r39251 | i = i.start | ||
self._check_index(i) | ||||
r43933 | self._stripnodes(i) | |||
Maciej Fijalkowski
|
r29133 | if i < self._lgt: | ||
r47736 | self._data = self._data[: i * self.entry_size] | |||
Maciej Fijalkowski
|
r29133 | self._lgt = i | ||
self._extra = [] | ||||
else: | ||||
Augie Fackler
|
r43346 | self._extra = self._extra[: i - self._lgt] | ||
Maciej Fijalkowski
|
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 | ||||
Maciej Fijalkowski
|
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 = [] | ||||
Martin Geisler
|
r7700 | |||
Maciej Fijalkowski
|
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: | |||
Raphaël Gomès
|
r47136 | start = off + self.big_int_size | ||
Augie Fackler
|
r43346 | (s,) = struct.unpack( | ||
Raphaël Gomès
|
r47136 | b'>i', | ||
self._data[start : start + self.int_size], | ||||
Augie Fackler
|
r43346 | ) | ||
Maciej Fijalkowski
|
r29133 | if lgt is not None: | ||
self._offsets[count] = off | ||||
count += 1 | ||||
r47736 | off += self.entry_size + s | |||
Maciej Fijalkowski
|
r29133 | if off != len(self._data): | ||
Augie Fackler
|
r43347 | raise ValueError(b"corrupted data") | ||
Maciej Fijalkowski
|
r29133 | return count | ||
Augie Fackler
|
r14421 | |||
Maciej Fijalkowski
|
r29133 | def __delitem__(self, i): | ||
Alex Gaynor
|
r34332 | if not isinstance(i, slice) or not i.stop == -1 or i.step is not None: | ||
Augie Fackler
|
r43347 | raise ValueError(b"deleting slices only supports a:-1 with step 1") | ||
Martin von Zweigbergk
|
r39251 | i = i.start | ||
self._check_index(i) | ||||
r43933 | self._stripnodes(i) | |||
Maciej Fijalkowski
|
r29133 | if i < self._lgt: | ||
self._offsets = self._offsets[:i] | ||||
self._lgt = i | ||||
self._extra = [] | ||||
else: | ||||
Augie Fackler
|
r43346 | self._extra = self._extra[: i - self._lgt] | ||
Martin Geisler
|
r7700 | |||
Maciej Fijalkowski
|
r29133 | def _calculate_index(self, i): | ||
return self._offsets[i] | ||||
Martin Geisler
|
r7700 | |||
Augie Fackler
|
r43346 | |||
pacien
|
r49618 | def parse_index2(data, inline, format=revlog_constants.REVLOGV1): | ||
if format == revlog_constants.CHANGELOGV2: | ||||
return parse_index_cl_v2(data) | ||||
Maciej Fijalkowski
|
r29133 | if not inline: | ||
pacien
|
r49618 | if format == revlog_constants.REVLOGV2: | ||
cls = IndexObject2 | ||||
else: | ||||
cls = IndexObject | ||||
Raphaël Gomès
|
r47438 | return cls(data), None | ||
r48036 | cls = InlinedIndexObject | |||
Raphaël Gomès
|
r47438 | return cls(data, inline), (0, data) | ||
r48044 | def parse_index_cl_v2(data): | |||
return IndexChangelogV2(data), None | ||||
r48036 | class IndexObject2(IndexObject): | |||
r47619 | index_format = revlog_constants.INDEX_ENTRY_V2 | |||
Raphaël Gomès
|
r47438 | |||
Raphaël Gomès
|
r47844 | def replace_sidedata_info( | ||
r48033 | self, | |||
rev, | ||||
sidedata_offset, | ||||
sidedata_length, | ||||
offset_flags, | ||||
compression_mode, | ||||
Raphaël Gomès
|
r47844 | ): | ||
Raphaël Gomès
|
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`. | |||
Raphaël Gomès
|
r47451 | """ | ||
r48017 | if rev < 0: | |||
Raphaël Gomès
|
r47451 | raise KeyError | ||
r48017 | self._check_index(rev) | |||
r48018 | if rev < self._lgt: | |||
Raphaël Gomès
|
r47451 | msg = b"cannot rewrite entries outside of this transaction" | ||
raise KeyError(msg) | ||||
r48018 | else: | |||
entry = list(self[rev]) | ||||
entry[0] = offset_flags | ||||
entry[8] = sidedata_offset | ||||
entry[9] = sidedata_length | ||||
r48033 | entry[11] = compression_mode | |||
r48018 | entry = tuple(entry) | |||
r48041 | new = self._pack_entry(rev, entry) | |||
r48018 | self._extra[rev - self._lgt] = new | |||
Raphaël Gomès
|
r47451 | |||
r48041 | def _unpack_entry(self, rev, data): | |||
r48030 | data = self.index_format.unpack(data) | |||
entry = data[:10] | ||||
data_comp = data[10] & 3 | ||||
sidedata_comp = (data[10] & (3 << 2)) >> 2 | ||||
r49330 | return entry + (data_comp, sidedata_comp, revlog_constants.RANK_UNKNOWN) | |||
r47913 | ||||
r48041 | def _pack_entry(self, rev, entry): | |||
r48030 | data = entry[:10] | |||
data_comp = entry[10] & 3 | ||||
sidedata_comp = (entry[11] & 3) << 2 | ||||
data += (data_comp | sidedata_comp,) | ||||
return self.index_format.pack(*data) | ||||
r47914 | ||||
r47811 | def entry_binary(self, rev): | |||
r47808 | """return the raw binary string representing a revision""" | |||
entry = self[rev] | ||||
r48041 | return self._pack_entry(rev, entry) | |||
r47808 | ||||
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) | ||||
Raphaël Gomès
|
r47438 | |||
r48044 | class IndexChangelogV2(IndexObject2): | |||
index_format = revlog_constants.INDEX_ENTRY_CL_V2 | ||||
r49331 | null_item = ( | |||
IndexObject2.null_item[: revlog_constants.ENTRY_RANK] | ||||
+ (0,) # rank of null is 0 | ||||
+ IndexObject2.null_item[revlog_constants.ENTRY_RANK :] | ||||
) | ||||
r48044 | def _unpack_entry(self, rev, data, r=True): | |||
items = self.index_format.unpack(data) | ||||
r49328 | return ( | |||
items[revlog_constants.INDEX_ENTRY_V2_IDX_OFFSET], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSED_LENGTH], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_UNCOMPRESSED_LENGTH], | ||||
rev, | ||||
rev, | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_1], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_PARENT_2], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_NODEID], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_OFFSET], | ||||
items[ | ||||
revlog_constants.INDEX_ENTRY_V2_IDX_SIDEDATA_COMPRESSED_LENGTH | ||||
], | ||||
items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] & 3, | ||||
(items[revlog_constants.INDEX_ENTRY_V2_IDX_COMPRESSION_MODE] >> 2) | ||||
& 3, | ||||
r49331 | items[revlog_constants.INDEX_ENTRY_V2_IDX_RANK], | |||
r49328 | ) | |||
r48044 | ||||
def _pack_entry(self, rev, entry): | ||||
r49328 | ||||
base = entry[revlog_constants.ENTRY_DELTA_BASE] | ||||
link_rev = entry[revlog_constants.ENTRY_LINK_REV] | ||||
assert base == rev, (base, rev) | ||||
assert link_rev == rev, (link_rev, rev) | ||||
data = ( | ||||
entry[revlog_constants.ENTRY_DATA_OFFSET], | ||||
entry[revlog_constants.ENTRY_DATA_COMPRESSED_LENGTH], | ||||
entry[revlog_constants.ENTRY_DATA_UNCOMPRESSED_LENGTH], | ||||
entry[revlog_constants.ENTRY_PARENT_1], | ||||
entry[revlog_constants.ENTRY_PARENT_2], | ||||
entry[revlog_constants.ENTRY_NODE_ID], | ||||
entry[revlog_constants.ENTRY_SIDEDATA_OFFSET], | ||||
entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSED_LENGTH], | ||||
entry[revlog_constants.ENTRY_DATA_COMPRESSION_MODE] & 3 | ||||
| (entry[revlog_constants.ENTRY_SIDEDATA_COMPRESSION_MODE] & 3) | ||||
<< 2, | ||||
r49331 | entry[revlog_constants.ENTRY_RANK], | |||
r49328 | ) | |||
r48044 | return self.index_format.pack(*data) | |||
r44794 | def parse_index_devel_nodemap(data, inline): | |||
Augie Fackler
|
r46554 | """like parse_index2, but alway return a PersistentNodeMapIndexObject""" | ||
r44794 | return PersistentNodeMapIndexObject(data), None | |||
Martin Geisler
|
r7700 | def parse_dirstate(dmap, copymap, st): | ||
Augie Fackler
|
r43346 | parents = [st[:20], st[20:40]] | ||
Mads Kiilerich
|
r17425 | # dereference fields so they will be local in loop | ||
Augie Fackler
|
r43347 | format = b">cllll" | ||
Matt Mackall
|
r7945 | e_size = struct.calcsize(format) | ||
Martin Geisler
|
r7700 | pos1 = 40 | ||
l = len(st) | ||||
# the inner loop | ||||
while pos1 < l: | ||||
pos2 = pos1 + e_size | ||||
Augie Fackler
|
r43347 | e = _unpack(b">cllll", st[pos1:pos2]) # a literal here is faster | ||
Martin Geisler
|
r7700 | pos1 = pos2 + e[4] | ||
f = st[pos2:pos1] | ||||
Augie Fackler
|
r43347 | if b'\0' in f: | ||
f, c = f.split(b'\0') | ||||
Martin Geisler
|
r7700 | copymap[f] = c | ||
r48465 | dmap[f] = DirstateItem.from_v1_data(*e[:4]) | |||
Martin Geisler
|
r7700 | return parents | ||
Siddharth Agarwal
|
r18567 | |||
Augie Fackler
|
r43346 | |||
r49221 | def pack_dirstate(dmap, copymap, pl): | |||
timeless
|
r28861 | cs = stringio() | ||
Siddharth Agarwal
|
r18567 | write = cs.write | ||
Augie Fackler
|
r43347 | write(b"".join(pl)) | ||
Gregory Szorc
|
r49768 | for f, e in dmap.items(): | ||
Siddharth Agarwal
|
r18567 | if f in copymap: | ||
Augie Fackler
|
r43347 | f = b"%s\0%s" % (f, copymap[f]) | ||
r48298 | e = _pack( | |||
b">cllll", | ||||
Raphaël Gomès
|
r49920 | e._v1_state(), | ||
e._v1_mode(), | ||||
e._v1_size(), | ||||
e._v1_mtime(), | ||||
r48298 | len(f), | |||
) | ||||
Siddharth Agarwal
|
r18567 | write(e) | ||
write(f) | ||||
return cs.getvalue() | ||||