##// END OF EJS Templates
config: gather constant and type into the `__init__.py`...
config: gather constant and type into the `__init__.py` This will help using them in multiple files.

File last commit:

r52995:82e2c99c default
r53322:3e79ca01 default
Show More
dirstatemap.py
898 lines | 29.8 KiB | text/x-python | PythonLexer
dirstate: split dirstatemap in its own file...
r48295 # dirstatemap.py
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Matt Harbison
typing: add `from __future__ import annotations` to most files...
r52756 from __future__ import annotations
dirstate: split dirstatemap in its own file...
r48295
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 import stat
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 from typing import (
Optional,
TYPE_CHECKING,
)
dirstate: split dirstatemap in its own file...
r48295 from .i18n import _
from . import (
error,
pathutil,
policy,
dirstate: add a synchronisation point before doing a full dirstate read...
r51123 testing,
dirstate: split dirstatemap in its own file...
r48295 txnutil,
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 typelib,
dirstate: split dirstatemap in its own file...
r48295 util,
)
Simon Sapin
dirstate-v2: Introduce a docket file...
r48474 from .dirstateutils import (
docket as docketmod,
Simon Sapin
dirstate-v2: initial Python parser...
r49035 v2,
Simon Sapin
dirstate-v2: Introduce a docket file...
r48474 )
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 if TYPE_CHECKING:
from . import (
ui as uimod,
)
dirstate: split dirstatemap in its own file...
r48295 parsers = policy.importmod('parsers')
rustmod = policy.importrust('dirstate')
propertycache = util.propertycache
Simon Sapin
dirstate: Use the Rust implementation of DirstateItem when Rust is enabled...
r48858 if rustmod is None:
DirstateItem = parsers.DirstateItem
else:
DirstateItem = rustmod.DirstateItem
dirstate: split dirstatemap in its own file...
r48295
dirstate: move the handling of special case within the dirstatemap...
r48310 rangemask = 0x7FFFFFFF
dirstate: use more than a bool to control append behavior...
r51116 WRITE_MODE_AUTO = 0
WRITE_MODE_FORCE_NEW = 1
Raphaël Gomès
dirstate-v2: add devel config option to control write behavior...
r51117 WRITE_MODE_FORCE_APPEND = 2
dirstate: use more than a bool to control append behavior...
r51116
dirstate: split dirstatemap in its own file...
r48295
dirstate: deal with read-race for pure python code...
r51132 V2_MAX_READ_ATTEMPTS = 5
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class _dirstatemapcommon:
dirstatemap: introduce a common base for the dirstatemap class...
r48931 """
Methods that are identical for both implementations of the dirstatemap
class, with and without Rust extensions enabled.
"""
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 _use_dirstate_v2: bool
_nodeconstants: typelib.NodeConstants
_ui: "uimod.ui"
_root: bytes
_filename: bytes
_nodelen: int
_dirtyparents: bool
_docket: Optional["docketmod.DirstateDocket"]
_write_mode: int
_pendingmode: Optional[bool]
identity: Optional[typelib.CacheStat]
dirstatemap: move a multiple simple functions in the common class...
r48934 # please pytype
_map = None
copymap = None
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def __init__(
self,
ui: "uimod.ui",
opener,
root: bytes,
nodeconstants: typelib.NodeConstants,
use_dirstate_v2: bool,
) -> None:
dirstatemap: use a common __init__ for dirstatemap...
r48932 self._use_dirstate_v2 = use_dirstate_v2
self._nodeconstants = nodeconstants
self._ui = ui
self._opener = opener
self._root = root
self._filename = b'dirstate'
self._nodelen = 20 # Also update Rust code when changing this!
self._parents = None
self._dirtyparents = False
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 self._docket = None
Raphaël Gomès
dirstate-v2: add devel config option to control write behavior...
r51117 write_mode = ui.config(b"devel", b"dirstate.v2.data_update_mode")
if write_mode == b"auto":
self._write_mode = WRITE_MODE_AUTO
elif write_mode == b"force-append":
self._write_mode = WRITE_MODE_FORCE_APPEND
elif write_mode == b"force-new":
self._write_mode = WRITE_MODE_FORCE_NEW
else:
# unknown value, fallback to default
self._write_mode = WRITE_MODE_AUTO
dirstatemap: use a common __init__ for dirstatemap...
r48932
# for consistent view between _pl() and _read() invocations
self._pendingmode = None
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def _set_identity(self) -> None:
dirstate: factor the identity getting/setting code in the dirstate map...
r51021 self.identity = self._get_current_identity()
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def _get_current_identity(self) -> Optional[typelib.CacheStat]:
cachestat: avoid creating cachestat for http path...
r52995 # TODO have a cleaner approach on httpstaticrepo side
path = self._opener.join(self._filename)
if path.startswith(b'https://') or path.startswith(b'http://'):
return util.uncacheable_cachestat()
dirstate: factor the identity getting/setting code in the dirstate map...
r51021 try:
cachestat: avoid creating cachestat for http path...
r52995 return util.cachestat(path)
dirstate: factor the identity getting/setting code in the dirstate map...
r51021 except FileNotFoundError:
return None
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def may_need_refresh(self) -> bool:
dirstate: only reload the dirstate when it may have changed...
r51023 if 'identity' not in vars(self):
# no existing identity, we need a refresh
return True
if self.identity is None:
return True
if not self.identity.cacheable():
# We cannot trust the entry
# XXX this is a problem on windows, NFS, or other inode less system
return True
current_identity = self._get_current_identity()
if current_identity is None:
return True
if not current_identity.cacheable():
# We cannot trust the entry
# XXX this is a problem on windows, NFS, or other inode less system
return True
return current_identity != self.identity
dirstate: factor the identity setting code in the dirstate map...
r51136
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def preload(self) -> None:
dirstatemap: move a multiple simple functions in the common class...
r48934 """Loads the underlying data, if it's not already loaded"""
self._map
def get(self, key, default=None):
return self._map.get(key, default)
def __len__(self):
return len(self._map)
def __iter__(self):
return iter(self._map)
def __contains__(self, key):
return key in self._map
def __getitem__(self, item):
return self._map[item]
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 ### disk interaction
def _opendirstatefile(self):
fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
if self._pendingmode is not None and self._pendingmode != mode:
fp.close()
raise error.Abort(
_(b'working directory state may be changed parallelly')
)
self._pendingmode = mode
return fp
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def _readdirstatefile(self, size: int = -1) -> bytes:
Raphaël Gomès
dirstate-map: add a missing debug wait point when accessing the v2 docket...
r52946 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 try:
with self._opendirstatefile() as fp:
return fp.read(size)
Manuel Jacob
py3: catch FileNotFoundError instead of checking errno == ENOENT
r50201 except FileNotFoundError:
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 # File doesn't exist, so the current state is empty
return b''
@property
Matt Harbison
typing: add type hints to `mercurial.dirstatemap`...
r52609 def docket(self) -> "docketmod.DirstateDocket":
Raphaël Gomès
dirstate-map: add a missing debug wait point when accessing the v2 docket...
r52946 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 if not self._docket:
if not self._use_dirstate_v2:
raise error.ProgrammingError(
b'dirstate only has a docket in v2 format'
)
dirstate: set identity whenever we read the dirstate's v2 docket...
r51137 self._set_identity()
Arseniy Alekseyev
dirstate-v2: actually fix the dirstate-v2 upgrade race...
r51616 data = self._readdirstatefile()
if data == b'' or data.startswith(docketmod.V2_FORMAT_MARKER):
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 self._docket = docketmod.DirstateDocket.parse(
Arseniy Alekseyev
dirstate-v2: actually fix the dirstate-v2 upgrade race...
r51616 data, self._nodeconstants
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 )
Arseniy Alekseyev
dirstate-v2: actually fix the dirstate-v2 upgrade race...
r51616 else:
raise error.CorruptedDirstate(b"dirstate is not in v2 format")
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 return self._docket
dirstate: abstract the reading of the data file in v2 in a method...
r51131 def _read_v2_data(self):
dirstate: deal with read-race for pure python code...
r51132 data = None
attempts = 0
while attempts < V2_MAX_READ_ATTEMPTS:
attempts += 1
try:
dirstate: deal with read-race for python code using rust object...
r51133 # TODO: use mmap when possible
dirstate: deal with read-race for pure python code...
r51132 data = self._opener.read(self.docket.data_filename())
except FileNotFoundError:
# read race detected between docket and data file
# reload the docket and retry
self._docket = None
if data is None:
assert attempts >= V2_MAX_READ_ATTEMPTS
msg = b"dirstate read race happened %d times in a row"
msg %= attempts
raise error.Abort(msg)
dirstate: abstract the reading of the data file in v2 in a method...
r51131 return self._opener.read(self.docket.data_filename())
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 def write_v2_no_append(self, tr, st, meta, packed):
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
old_docket = self.docket
except error.CorruptedDirstate:
# This means we've identified a dirstate-v1 file on-disk when we
# were expecting a dirstate-v2 docket. We've managed to recover
# from that unexpected situation, and now we want to write back a
# dirstate-v2 file to make the on-disk situation right again.
#
# This shouldn't be triggered since `self.docket` is cached and
# we would have called parents() or read() first, but it's here
# just in case.
old_docket = None
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 new_docket = docketmod.DirstateDocket.with_new_uuid(
self.parents(), len(packed), meta
)
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 if old_docket is not None and old_docket.uuid == new_docket.uuid:
Arseniy Alekseyev
dirstate-v2: complain early on docket name collision...
r50992 raise error.ProgrammingError(b'dirstate docket name collision')
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 data_filename = new_docket.data_filename()
self._opener.write(data_filename, packed)
dirstate: explicitly backup the datafile...
r50976 # tell the transaction that we are adding a new file
if tr is not None:
tr.addbackup(data_filename, location=b'plain')
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 # Write the new docket after the new data file has been
# written. Because `st` was opened with `atomictemp=True`,
# the actual `.hg/dirstate` file is only affected on close.
st.write(new_docket.serialize())
st.close()
# Remove the old data file after the new docket pointing to
# the new data file was written.
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 if old_docket is not None and old_docket.uuid:
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 data_filename = old_docket.data_filename()
dirstate: explicitly backup the datafile...
r50976 if tr is not None:
tr.addbackup(data_filename, location=b'plain')
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 unlink = lambda _tr=None: self._opener.unlink(data_filename)
if tr:
category = b"dirstate-v2-clean-" + old_docket.uuid
tr.addpostclose(category, unlink)
else:
unlink()
self._docket = new_docket
### reading/setting parents
def parents(self):
if not self._parents:
if self._use_dirstate_v2:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
self.docket
except error.CorruptedDirstate as e:
# fall back to dirstate-v1 if we fail to read v2
self._v1_parents(e)
else:
self._parents = self.docket.parents
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 else:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 self._v1_parents()
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034
return self._parents
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 def _v1_parents(self, from_v2_exception=None):
read_len = self._nodelen * 2
st = self._readdirstatefile(read_len)
l = len(st)
if l == read_len:
self._parents = (
st[: self._nodelen],
st[self._nodelen : 2 * self._nodelen],
)
elif l == 0:
self._parents = (
self._nodeconstants.nullid,
self._nodeconstants.nullid,
)
else:
hint = None
if from_v2_exception is not None:
hint = _(b"falling back to dirstate-v1 from v2 also failed")
raise error.Abort(
_(b'working directory state appears damaged!'), hint
)
dirstatemap: introduce a common base for the dirstatemap class...
r48931
class dirstatemap(_dirstatemapcommon):
dirstate: split dirstatemap in its own file...
r48295 """Map encapsulating the dirstate's contents.
The dirstate contains the following state:
- `identity` is the identity of the dirstate file, which can be used to
detect when changes have occurred to the dirstate file.
- `parents` is a pair containing the parents of the working copy. The
parents are updated by calling `setparents`.
- the state map maps filenames to tuples of (state, mode, size, mtime),
where state is a single character representing 'normal', 'added',
'removed', or 'merged'. It is read by treating the dirstate as a
dirstate: update the documentation of the dirstatemap API...
r48810 dict. File state is updated by calling various methods (see each
documentation for details):
- `reset_state`,
- `set_tracked`
- `set_untracked`
- `set_clean`
- `set_possibly_dirty`
dirstate: split dirstatemap in its own file...
r48295
- `copymap` maps destination filenames to their source filename.
The dirstate also provides the following views onto the state:
- `filefoldmap` is a dict mapping normalized filenames to the denormalized
form that they appear as in the dirstate.
- `dirfoldmap` is a dict mapping normalized directory names to the
denormalized form that they appear as in the dirstate.
"""
dirstatemap: arrange methods by category...
r48935 ### Core data storage and access
dirstate: split dirstatemap in its own file...
r48295 @propertycache
def _map(self):
self._map = {}
self.read()
return self._map
@propertycache
def copymap(self):
self.copymap = {}
self._map
return self.copymap
def clear(self):
self._map.clear()
self.copymap.clear()
self.setparents(self._nodeconstants.nullid, self._nodeconstants.nullid)
util.clearcachedproperty(self, b"_dirs")
util.clearcachedproperty(self, b"_alldirs")
util.clearcachedproperty(self, b"filefoldmap")
util.clearcachedproperty(self, b"dirfoldmap")
def items(self):
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 return self._map.items()
dirstate: split dirstatemap in its own file...
r48295
# forward for python2,3 compat
iteritems = items
Simon Sapin
debugstate: Always call dirstatemap.debug_iter()...
r48835 def debug_iter(self, all):
"""
Simon Sapin
debugsate: Change debug_iter() to yield tuples instead of DirstateItem...
r48836 Return an iterator of (filename, state, mode, size, mtime) tuples
Simon Sapin
debugstate: Always call dirstatemap.debug_iter()...
r48835 `all` is unused when Rust is not enabled
"""
Raphaël Gomès
black: format the codebase with 23.3.0...
r52596 for filename, item in self.items():
Simon Sapin
debugsate: Change debug_iter() to yield tuples instead of DirstateItem...
r48836 yield (filename, item.state, item.mode, item.size, item.mtime)
Simon Sapin
dirstate-v2: Separate iterators for dirfoldmap and debugdirstate...
r48483
dirstate: split dirstatemap in its own file...
r48295 def keys(self):
return self._map.keys()
dirstatemap: arrange methods by category...
r48935 ### reading/setting parents
def setparents(self, p1, p2, fold_p2=False):
self._parents = (p1, p2)
self._dirtyparents = True
copies = {}
if fold_p2:
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 for f, s in self._map.items():
dirstatemap: arrange methods by category...
r48935 # Discard "merged" markers when moving away from a merge state
dirstate-item: use the `p2_info` property to replace more verbose call...
r48960 if s.p2_info:
dirstatemap: arrange methods by category...
r48935 source = self.copymap.pop(f, None)
if source:
copies[f] = source
s.drop_merge_data()
return copies
### disk interaction
def read(self):
dirstate: add a synchronisation point before doing a full dirstate read...
r51123 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 if self._use_dirstate_v2:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
self.docket
except error.CorruptedDirstate:
# fall back to dirstate-v1 if we fail to read v2
self._set_identity()
st = self._readdirstatefile()
else:
if not self.docket.uuid:
return
testing.wait_on_cfg(self._ui, b'dirstate.post-docket-read-file')
st = self._read_v2_data()
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 else:
dirstate: set identity whenever we read the dirstate's v2 docket...
r51137 self._set_identity()
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 st = self._readdirstatefile()
dirstatemap: arrange methods by category...
r48935 if not st:
return
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 # TODO: adjust this estimate for dirstate-v2
safehasattr: drop usage in favor of hasattr...
r51821 if hasattr(parsers, 'dict_new_presized'):
dirstatemap: arrange methods by category...
r48935 # Make an estimate of the number of files in the dirstate based on
# its size. This trades wasting some memory for avoiding costly
# resizes. Each entry have a prefix of 17 bytes followed by one or
# two path names. Studies on various large-scale real-world repositories
# found 54 bytes a reasonable upper limit for the average path names.
# Copy entries are ignored for the sake of this estimate.
self._map = parsers.dict_new_presized(len(st) // 71)
# Python's garbage collector triggers a GC each time a certain number
# of container objects (the number being defined by
# gc.get_threshold()) are allocated. parse_dirstate creates a tuple
# for each file in the dirstate. The C version then immediately marks
# them as not to be tracked by the collector. However, this has no
# effect on when GCs are triggered, only on what objects the GC looks
# into. This means that O(number of files) GCs are unavoidable.
# Depending on when in the process's lifetime the dirstate is parsed,
# this can get very expensive. As a workaround, disable GC while
# parsing the dirstate.
#
# (we cannot decorate the function directly since it is in a C module)
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 if self._use_dirstate_v2:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
self.docket
except error.CorruptedDirstate:
# fall back to dirstate-v1 if we fail to parse v2
parse_dirstate = util.nogc(parsers.parse_dirstate)
p = parse_dirstate(self._map, self.copymap, st)
else:
p = self.docket.parents
meta = self.docket.tree_metadata
parse_dirstate = util.nogc(v2.parse_dirstate)
parse_dirstate(self._map, self.copymap, st, meta)
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 else:
parse_dirstate = util.nogc(parsers.parse_dirstate)
p = parse_dirstate(self._map, self.copymap, st)
dirstatemap: arrange methods by category...
r48935 if not self._dirtyparents:
self.setparents(*p)
# Avoid excess attribute lookups by fast pathing certain checks
self.__contains__ = self._map.__contains__
self.__getitem__ = self._map.__getitem__
self.get = self._map.get
dirstate: cleanup remaining of "now" during write...
r49222 def write(self, tr, st):
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 if self._use_dirstate_v2:
dirstate: remove need_delay logic...
r49221 packed, meta = v2.pack_dirstate(self._map, self.copymap)
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 self.write_v2_no_append(tr, st, meta, packed)
else:
packed = parsers.pack_dirstate(
dirstate: remove need_delay logic...
r49221 self._map, self.copymap, self.parents()
Simon Sapin
dirstate-v2: Add support when Rust is not enabled...
r49037 )
st.write(packed)
st.close()
dirstatemap: arrange methods by category...
r48935 self._dirtyparents = False
@propertycache
def identity(self):
self._map
return self.identity
### code related to maintaining and accessing "extra" property
# (e.g. "has_dir")
dirstate-map: factor out the change to _dirs and _alldirs on adding...
r48487 def _dirs_incr(self, filename, old_entry=None):
Raphaël Gomès
dirstatemap: move `_dirs_incr` and `_dirs_decr` methods out of the common...
r50009 """increment the dirstate counter if applicable"""
dirstate-map: factor out the change to _dirs and _alldirs on adding...
r48487 if (
old_entry is None or old_entry.removed
) and "_dirs" in self.__dict__:
self._dirs.addpath(filename)
if old_entry is None and "_alldirs" in self.__dict__:
self._alldirs.addpath(filename)
dirstate-map: factor out the change to _dirs and _alldirs on removing...
r48489 def _dirs_decr(self, filename, old_entry=None, remove_variant=False):
Raphaël Gomès
dirstatemap: move `_dirs_incr` and `_dirs_decr` methods out of the common...
r50009 """decrement the dirstate counter if applicable"""
dirstate-map: factor out the change to _dirs and _alldirs on dropping...
r48488 if old_entry is not None:
if "_dirs" in self.__dict__ and not old_entry.removed:
self._dirs.delpath(filename)
dirstate-map: factor out the change to _dirs and _alldirs on removing...
r48489 if "_alldirs" in self.__dict__ and not remove_variant:
dirstate-map: factor out the change to _dirs and _alldirs on dropping...
r48488 self._alldirs.delpath(filename)
dirstate-map: factor out the change to _dirs and _alldirs on removing...
r48489 elif remove_variant and "_alldirs" in self.__dict__:
self._alldirs.addpath(filename)
dirstate-map: factor out the change to _dirs and _alldirs on dropping...
r48488 if "filefoldmap" in self.__dict__:
normed = util.normcase(filename)
self.filefoldmap.pop(normed, None)
dirstatemap: arrange methods by category...
r48935 @propertycache
def filefoldmap(self):
"""Returns a dictionary mapping normalized case paths to their
non-normalized versions.
"""
try:
makefilefoldmap = parsers.make_file_foldmap
except AttributeError:
pass
else:
return makefilefoldmap(
self._map, util.normcasespec, util.normcasefallback
)
f = {}
normcase = util.normcase
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 for name, s in self._map.items():
dirstatemap: arrange methods by category...
r48935 if not s.removed:
f[normcase(name)] = name
f[b'.'] = b'.' # prevents useless util.fspath() invocation
return f
@propertycache
def dirfoldmap(self):
f = {}
normcase = util.normcase
for name in self._dirs:
f[normcase(name)] = name
return f
def hastrackeddir(self, d):
"""
Returns True if the dirstate contains a tracked (not removed) file
in this directory.
"""
return d in self._dirs
def hasdir(self, d):
"""
Returns True if the dirstate contains a file (tracked or removed)
in this directory.
"""
return d in self._alldirs
@propertycache
def _dirs(self):
return pathutil.dirs(self._map, only_tracked=True)
@propertycache
def _alldirs(self):
return pathutil.dirs(self._map)
### code related to manipulation of entries and copy-sources
Raphaël Gomès
dirstatemap: move `reset_state` out of common methods...
r49993 def reset_state(
self,
filename,
wc_tracked=False,
p1_tracked=False,
p2_info=False,
has_meaningful_mtime=True,
parentfiledata=None,
):
"""Set a entry to a given state, diregarding all previous state
This is to be used by the part of the dirstate API dedicated to
adjusting the dirstate after a update/merge.
note: calling this might result to no entry existing at all if the
dirstate map does not see any point at having one for this file
anymore.
"""
# copy information are now outdated
# (maybe new information should be in directly passed to this function)
self.copymap.pop(filename, None)
if not (p1_tracked or p2_info or wc_tracked):
old_entry = self._map.get(filename)
self._drop_entry(filename)
self._dirs_decr(filename, old_entry=old_entry)
return
old_entry = self._map.get(filename)
self._dirs_incr(filename, old_entry)
entry = DirstateItem(
wc_tracked=wc_tracked,
p1_tracked=p1_tracked,
p2_info=p2_info,
has_meaningful_mtime=has_meaningful_mtime,
parentfiledata=parentfiledata,
)
Raphaël Gomès
dirstatemap: remove `_insert_entry`...
r49994 self._map[filename] = entry
Raphaël Gomès
dirstatemap: move `reset_state` out of common methods...
r49993
Raphaël Gomès
dirstatemap: move `set_tracked` out of common methods and plug in Rust...
r49989 def set_tracked(self, filename):
new = False
entry = self.get(filename)
if entry is None:
self._dirs_incr(filename)
entry = DirstateItem(
wc_tracked=True,
)
Raphaël Gomès
dirstatemap: remove `_insert_entry`...
r49994 self._map[filename] = entry
Raphaël Gomès
dirstatemap: move `set_tracked` out of common methods and plug in Rust...
r49989 new = True
elif not entry.tracked:
self._dirs_incr(filename, entry)
entry.set_tracked()
self._refresh_entry(filename, entry)
new = True
else:
# XXX This is probably overkill for more case, but we need this to
# fully replace the `normallookup` call with `set_tracked` one.
# Consider smoothing this in the future.
entry.set_possibly_dirty()
self._refresh_entry(filename, entry)
return new
Raphaël Gomès
dirstatemap: move `set_untracked` out of the common methods...
r50000 def set_untracked(self, f):
"""Mark a file as no longer tracked in the dirstate map"""
entry = self.get(f)
if entry is None:
return False
else:
self._dirs_decr(f, old_entry=entry, remove_variant=not entry.added)
if not entry.p2_info:
self.copymap.pop(f, None)
entry.set_untracked()
self._refresh_entry(f, entry)
return True
Raphaël Gomès
dirstatemap: move `set_clean` out of common methods...
r49996 def set_clean(self, filename, mode, size, mtime):
"""mark a file as back to a clean state"""
entry = self[filename]
size = size & rangemask
entry.set_clean(mode, size, mtime)
self._refresh_entry(filename, entry)
self.copymap.pop(filename, None)
Raphaël Gomès
dirstatemap: move `set_possibly_dirty` out of the common methods...
r49998 def set_possibly_dirty(self, filename):
"""record that the current state of the file on disk is unknown"""
entry = self[filename]
entry.set_possibly_dirty()
self._refresh_entry(filename, entry)
dirstatemap: add a common `_refresh_entry` method for dirstatemap...
r48938 def _refresh_entry(self, f, entry):
Raphaël Gomès
dirstatemap: move `_refresh_entry` out of the common methods...
r50008 """record updated state of an entry"""
dirstatemap: add a common `_refresh_entry` method for dirstatemap...
r48938 if not entry.any_tracked:
self._map.pop(f, None)
dirstatemap: add a common `_drop_entry` method for dirstatemap...
r48944 def _drop_entry(self, f):
Raphaël Gomès
dirstatemap: move `_drop_entry` out of the common methods...
r50007 """remove any entry for file f
This should also drop associated copy information
Raphaël Gomès
black: format the codebase with 23.3.0...
r52596 The fact we actually need to drop it is the responsability of the caller
"""
dirstatemap: add a common `_drop_entry` method for dirstatemap...
r48944 self._map.pop(f, None)
dirstatemap: use a common implement for reset_state...
r48945 self.copymap.pop(f, None)
dirstatemap: add a common `_drop_entry` method for dirstatemap...
r48944
dirstate: split dirstatemap in its own file...
r48295
if rustmod is not None:
dirstatemap: introduce a common base for the dirstatemap class...
r48931 class dirstatemap(_dirstatemapcommon):
dirstatemap: arrange methods by category...
r48935 ### Core data storage and access
@propertycache
def _map(self):
"""
Fills the Dirstatemap when called.
"""
# ignore HG_PENDING because identity is used only for writing
dirstate: factor the identity setting code in the dirstate map...
r51136 self._set_identity()
dirstatemap: arrange methods by category...
r48935
dirstate: add a synchronisation point before doing a full dirstate read...
r51123 testing.wait_on_cfg(self._ui, b'dirstate.pre-read-file')
dirstatemap: arrange methods by category...
r48935 if self._use_dirstate_v2:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
self.docket
except error.CorruptedDirstate as e:
# fall back to dirstate-v1 if we fail to read v2
parents = self._v1_map(e)
dirstatemap: arrange methods by category...
r48935 else:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 parents = self.docket.parents
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 identity = self._get_rust_identity()
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 testing.wait_on_cfg(
self._ui, b'dirstate.post-docket-read-file'
Raphaël Gomès
rust-dirstate: remember the data file uuid dirstate was loaded with...
r51138 )
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 if not self.docket.uuid:
data = b''
self._map = rustmod.DirstateMap.new_empty()
else:
data = self._read_v2_data()
self._map = rustmod.DirstateMap.new_v2(
data,
self.docket.data_size,
self.docket.tree_metadata,
self.docket.uuid,
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 identity,
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 )
parents = self.docket.parents
Simon Sapin
dirstate: Pass the final DirstateItem to _rustmap.addfile()...
r48865 else:
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 parents = self._v1_map()
dirstatemap: arrange methods by category...
r48935
if parents and not self._dirtyparents:
self.setparents(*parents)
self.__contains__ = self._map.__contains__
self.__getitem__ = self._map.__getitem__
self.get = self._map.get
return self._map
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 def _get_rust_identity(self):
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 self._set_identity()
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 identity = None
if self.identity is not None and self.identity.stat is not None:
stat_info = self.identity.stat
identity = rustmod.DirstateIdentity(
mode=stat_info.st_mode,
dev=stat_info.st_dev,
ino=stat_info.st_ino,
nlink=stat_info.st_nlink,
uid=stat_info.st_uid,
gid=stat_info.st_gid,
size=stat_info.st_size,
mtime=stat_info[stat.ST_MTIME],
mtime_nsec=0,
ctime=stat_info[stat.ST_CTIME],
ctime_nsec=0,
)
return identity
def _v1_map(self, from_v2_exception=None):
identity = self._get_rust_identity()
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 try:
self._map, parents = rustmod.DirstateMap.new_v1(
Raphaël Gomès
rust-dirstate-map: use a more precise identity...
r52947 self._readdirstatefile(), identity
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 )
except OSError as e:
if from_v2_exception is not None:
raise e from from_v2_exception
raise
return parents
dirstatemap: arrange methods by category...
r48935 @property
def copymap(self):
return self._map.copymap()
def debug_iter(self, all):
"""
Return an iterator of (filename, state, mode, size, mtime) tuples
`all`: also include with `state == b' '` dirstate tree nodes that
don't have an associated `DirstateItem`.
"""
return self._map.debug_iter(all)
def clear(self):
self._map.clear()
self.setparents(
self._nodeconstants.nullid, self._nodeconstants.nullid
)
util.clearcachedproperty(self, b"_dirs")
util.clearcachedproperty(self, b"_alldirs")
util.clearcachedproperty(self, b"dirfoldmap")
def items(self):
return self._map.items()
# forward for python2,3 compat
iteritems = items
def keys(self):
return iter(self._map)
### reading/setting parents
def setparents(self, p1, p2, fold_p2=False):
self._parents = (p1, p2)
self._dirtyparents = True
copies = {}
if fold_p2:
Raphaël Gomès
rust-dirstatemap: implement part of the `setparents` logic...
r50011 copies = self._map.setparents_fixup()
dirstatemap: arrange methods by category...
r48935 return copies
### disk interaction
@propertycache
def identity(self):
self._map
return self.identity
dirstate: cleanup remaining of "now" during write...
r49222 def write(self, tr, st):
dirstatemap: arrange methods by category...
r48935 if not self._use_dirstate_v2:
p1, p2 = self.parents()
dirstate: remove need_delay logic...
r49221 packed = self._map.write_v1(p1, p2)
dirstatemap: arrange methods by category...
r48935 st.write(packed)
st.close()
self._dirtyparents = False
return
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 write_mode = self._write_mode
try:
docket = self.docket
except error.CorruptedDirstate:
# fall back to dirstate-v1 if we fail to parse v2
docket = None
dirstatemap: arrange methods by category...
r48935 # We can only append to an existing data file if there is one
Raphaël Gomès
rust-dirstate: fall back to v1 if reading v2 failed...
r51553 if docket is None or docket.uuid is None:
dirstate: use more than a bool to control append behavior...
r51116 write_mode = WRITE_MODE_FORCE_NEW
packed, meta, append = self._map.write_v2(write_mode)
dirstatemap: arrange methods by category...
r48935 if append:
docket = self.docket
data_filename = docket.data_filename()
dirstate: explicitly backup the datafile...
r50976 # We mark it for backup to make sure a future `hg rollback` (or
# `hg recover`?) call find the data it needs to restore a
# working repository.
#
# The backup can use a hardlink because the format is resistant
# to trailing "dead" data.
if tr is not None:
tr.addbackup(data_filename, location=b'plain')
dirstatemap: arrange methods by category...
r48935 with self._opener(data_filename, b'r+b') as fp:
fp.seek(docket.data_size)
assert fp.tell() == docket.data_size
written = fp.write(packed)
if written is not None: # py2 may return None
assert written == len(packed), (written, len(packed))
docket.data_size += len(packed)
docket.parents = self.parents()
docket.tree_metadata = meta
st.write(docket.serialize())
st.close()
else:
Simon Sapin
dirstate: Move more methods to the _dirstatemapcommon base class...
r49034 self.write_v2_no_append(tr, st, meta, packed)
dirstatemap: arrange methods by category...
r48935 # Reload from the newly-written file
util.clearcachedproperty(self, b"_map")
self._dirtyparents = False
### code related to maintaining and accessing "extra" property
# (e.g. "has_dir")
@propertycache
def filefoldmap(self):
"""Returns a dictionary mapping normalized case paths to their
non-normalized versions.
"""
return self._map.filefoldmapasdict()
def hastrackeddir(self, d):
return self._map.hastrackeddir(d)
def hasdir(self, d):
return self._map.hasdir(d)
@propertycache
def dirfoldmap(self):
f = {}
normcase = util.normcase
for name in self._map.tracked_dirs():
f[normcase(name)] = name
return f
### code related to manipulation of entries and copy-sources
Raphaël Gomès
dirstatemap: move `set_tracked` out of common methods and plug in Rust...
r49989 def set_tracked(self, f):
return self._map.set_tracked(f)
Raphaël Gomès
dirstatemap: move `set_untracked` out of the common methods...
r50000 def set_untracked(self, f):
return self._map.set_untracked(f)
Raphaël Gomès
dirstatemap: move `set_clean` out of common methods...
r49996 def set_clean(self, filename, mode, size, mtime):
self._map.set_clean(filename, mode, size, mtime)
Raphaël Gomès
dirstatemap: move `set_possibly_dirty` out of the common methods...
r49998 def set_possibly_dirty(self, f):
self._map.set_possibly_dirty(f)
Raphaël Gomès
dirstatemap: move `reset_state` out of common methods...
r49993 def reset_state(
self,
filename,
wc_tracked=False,
p1_tracked=False,
p2_info=False,
has_meaningful_mtime=True,
parentfiledata=None,
):
return self._map.reset_state(
filename,
wc_tracked,
p1_tracked,
p2_info,
has_meaningful_mtime,
parentfiledata,
)