pub struct DirState {Show 19 fields
pub filename: PathBuf,
pub sha1_provider: Box<dyn SHA1Provider + Send + Sync>,
pub header_state: MemoryState,
pub dirblock_state: MemoryState,
pub changes_aborted: bool,
pub dirblocks: Vec<Dirblock>,
pub ghosts: Vec<Vec<u8>>,
pub parents: Vec<Vec<u8>>,
pub end_of_header: Option<u64>,
pub cutoff_time: Option<i64>,
pub num_entries: usize,
pub lock_state: Option<LockState>,
pub known_hash_changes: HashSet<EntryKey>,
pub worth_saving_limit: i64,
pub fdatasync: bool,
pub use_filesystem_for_exec: bool,
pub bisect_page_size: usize,
pub id_index: Option<IdIndex>,
pub packed_stat_index: Option<HashMap<Vec<u8>, Vec<u8>>>,
}Expand description
In-memory DirState, the Rust counterpart to bzrformats.dirstate.DirState.
This commit introduces the struct and a constructor mirroring Python’s
__init__. Behaviour (reading, writing, entry lookup, change processing)
is added in follow-up commits; for now the struct is a passive container
so later ports have a stable place to hang methods.
Fields§
§filename: PathBufPath to the dirstate file on disk (Python’s _filename).
sha1_provider: Box<dyn SHA1Provider + Send + Sync>Provider used to compute sha1s and stat+sha1 tuples for working-tree
files. Boxed so callers can swap in an alternate implementation for
testing, matching Python’s _sha1_provider attribute.
header_state: MemoryStateState of the header (NotInMemory until _read_header runs).
dirblock_state: MemoryStateState of the per-row dirblock data.
changes_aborted: boolIf an error was detected while updating the dirstate we refuse to
write it back. Mirrors Python’s _changes_aborted flag.
dirblocks: Vec<Dirblock>The in-memory dirblocks, sorted by dirname. Python stores this as
[(dirname, [entry, ...])] in _dirblocks.
ghosts: Vec<Vec<u8>>Ghost parent revision ids: parents that are referenced but not present locally.
parents: Vec<Vec<u8>>Parent revision ids for the current tree, in order. The first entry is the current parent; subsequent entries are merged parents.
end_of_header: Option<u64>Offset in filename where the header ends and the dirblock text
begins, populated after the header has been parsed.
cutoff_time: Option<i64>Cutoff mtime/ctime for trusting cached sha1s. None until
_sha_cutoff_time has been computed for the current now.
num_entries: usizeDeclared entry count from the header, or None before the header is
read. Used to validate the dirblock parse.
lock_state: Option<LockState>Current read/write lock state.
known_hash_changes: HashSet<EntryKey>Set of keys whose hash is known to have changed since load. Used by
_mark_modified to decide whether a save is worthwhile.
worth_saving_limit: i64Below this many hash-only changes a save is skipped.
-1 means never save hash changes; 0 means always save them.
fdatasync: boolCall fdatasync after writing the state file if true.
use_filesystem_for_exec: boolTrust the filesystem’s executable bit when building tree data.
bisect_page_size: usizeBisect chunk size when reading the state file in pages; mirrors
_bisect_page_size.
id_index: Option<IdIndex>Lazily-populated index of file_id → [(dirname, basename, file_id)].
None until DirState::get_or_build_id_index is called, at
which point it is rebuilt from the current dirblocks.
Invalidate by setting to None whenever dirblocks change.
packed_stat_index: Option<HashMap<Vec<u8>, Vec<u8>>>Lazily-populated index of packed_stat → sha1 for every file
entry in tree 0. None until DirState::get_or_build_packed_stat_index
is called, mirroring Python’s _packed_stat_index attribute.
Invalidate by setting to None whenever tree-0 entries change.
Implementations§
Source§impl DirState
impl DirState
Sourcepub fn new<P: Into<PathBuf>>(
path: P,
sha1_provider: Box<dyn SHA1Provider + Send + Sync>,
worth_saving_limit: i64,
use_filesystem_for_exec: bool,
fdatasync: bool,
) -> Self
pub fn new<P: Into<PathBuf>>( path: P, sha1_provider: Box<dyn SHA1Provider + Send + Sync>, worth_saving_limit: i64, use_filesystem_for_exec: bool, fdatasync: bool, ) -> Self
Create a new, empty DirState object.
The returned state has no data loaded from disk — header_state and
dirblock_state are both NotInMemory. Call a future load method
to populate it. This mirrors the Python constructor at
bzrformats/dirstate.py DirState.__init__.
Sourcepub fn empty_tree_dirblocks() -> Vec<Dirblock>
pub fn empty_tree_dirblocks() -> Vec<Dirblock>
Build the empty dirblock layout that initialize and
set_state_from_scratch start from: a single root entry under
the empty dirname, plus a sibling block ready to receive
children.
Sourcepub fn initialize<T: Transport + ?Sized>(
transport: &mut T,
path: PathBuf,
sha1_provider: Box<dyn SHA1Provider + Send + Sync>,
) -> Result<DirState, TransportError>
pub fn initialize<T: Transport + ?Sized>( transport: &mut T, path: PathBuf, sha1_provider: Box<dyn SHA1Provider + Send + Sync>, ) -> Result<DirState, TransportError>
Mirror Python’s DirState.initialize: open transport,
acquire a write lock, populate an empty dirstate (one root
node with ROOT_ID, no parents), serialise it, and return the
state plus the still-locked transport. The caller is
responsible for unlocking when done.
Sourcepub fn on_file(
path: PathBuf,
sha1_provider: Box<dyn SHA1Provider + Send + Sync>,
worth_saving_limit: i64,
use_filesystem_for_exec: bool,
fdatasync: bool,
) -> DirState
pub fn on_file( path: PathBuf, sha1_provider: Box<dyn SHA1Provider + Send + Sync>, worth_saving_limit: i64, use_filesystem_for_exec: bool, fdatasync: bool, ) -> DirState
Mirror Python’s DirState.on_file: build a DirState whose
metadata fields match the configured worth-saving / fdatasync /
use-filesystem-for-exec flags. Does not touch disk; the caller
must subsequently lock and read via a Transport.
Sourcepub fn set_state_from_scratch(
&mut self,
inventory: Vec<(Vec<u8>, Vec<u8>, Kind, Vec<u8>, bool)>,
parent_trees: Vec<(Vec<u8>, Vec<(Vec<u8>, Vec<u8>, TreeData)>)>,
parent_ghosts: Vec<Vec<u8>>,
) -> Result<(), TransportError>
pub fn set_state_from_scratch( &mut self, inventory: Vec<(Vec<u8>, Vec<u8>, Kind, Vec<u8>, bool)>, parent_trees: Vec<(Vec<u8>, Vec<(Vec<u8>, Vec<u8>, TreeData)>)>, parent_ghosts: Vec<Vec<u8>>, ) -> Result<(), TransportError>
Mirror Python’s DirState.set_state_from_scratch: reset to an
empty tree, then layer the given inventory and parent trees on
top.
Returns either of the underlying errors as Other(String) to
keep the signature simple — callers that need to discriminate
can call set_state_from_inventory and set_parent_trees
directly.
Sourcepub fn iter_entries(&self) -> impl Iterator<Item = &Entry>
pub fn iter_entries(&self) -> impl Iterator<Item = &Entry>
Yield a reference to every entry across every dirblock, in
dirblock order. Mirrors Python’s _iter_entries in the simple
case (without the implicit _read_dirblocks_if_needed —
callers are expected to have populated dirblocks already).
Sourcepub fn entry_at(&self, block_index: usize, entry_index: usize) -> Option<&Entry>
pub fn entry_at(&self, block_index: usize, entry_index: usize) -> Option<&Entry>
Look up the entry at (block_index, entry_index) in dirblock
order, if any. Used by resumable iterators that hold an index
cursor rather than a borrow of the dirblocks.
Sourcepub fn dirblock_count(&self) -> usize
pub fn dirblock_count(&self) -> usize
Number of dirblocks. Paired with DirState::entry_at to drive
an index cursor over DirState::iter_entries.
Sourcepub fn dirblock_entry_count(&self, block_index: usize) -> usize
pub fn dirblock_entry_count(&self, block_index: usize) -> usize
Number of entries in dirblock block_index, or 0 if out of range.
Sourcepub fn build_id_index(&self) -> IdIndex
pub fn build_id_index(&self) -> IdIndex
Build an IdIndex from the current dirblocks. Pure — no
cache interaction; callers that want Python’s cached behaviour
should use DirState::get_or_build_id_index instead.
Sourcepub fn get_or_build_id_index(&mut self) -> &IdIndex
pub fn get_or_build_id_index(&mut self) -> &IdIndex
Return a reference to the cached IdIndex, rebuilding it
from self.dirblocks on first call after the cache was last
invalidated. Mirrors Python’s DirState._get_id_index.
The cache lives in self.id_index; any code that mutates
self.dirblocks must set self.id_index = None afterwards to
force a rebuild on the next access.
Sourcepub fn build_packed_stat_index(&self) -> HashMap<Vec<u8>, Vec<u8>>
pub fn build_packed_stat_index(&self) -> HashMap<Vec<u8>, Vec<u8>>
Rebuild the packed_stat → sha1 map from every tree-0 file
entry. Pure — no cache interaction.
Sourcepub fn get_or_build_packed_stat_index(&mut self) -> &HashMap<Vec<u8>, Vec<u8>>
pub fn get_or_build_packed_stat_index(&mut self) -> &HashMap<Vec<u8>, Vec<u8>>
Return a reference to the cached packed_stat → sha1 map,
rebuilding it on first call after the cache was last
invalidated. Mirrors Python’s DirState._get_packed_stat_index.
The cache lives in self.packed_stat_index; any code that
mutates tree-0 file entries must set self.packed_stat_index = None afterwards to force a rebuild on the next access.
Sourcepub fn read_header(&mut self, data: &[u8]) -> Result<(), HeaderError>
pub fn read_header(&mut self, data: &[u8]) -> Result<(), HeaderError>
Parse the header of the dirstate file from data and populate the
in-memory fields that Python’s _read_header would populate.
data must contain the full dirstate file contents (or at minimum
enough bytes to cover the header); this mirrors Python’s
state_file.readline() loop operating on a buffered file. On
success the parents, ghosts, num_entries, and end_of_header
fields are set and header_state transitions to
InMemoryUnmodified.
Sourcepub fn load_bytes(&mut self, data: &[u8]) -> Result<(), LoadError>
pub fn load_bytes(&mut self, data: &[u8]) -> Result<(), LoadError>
Load a complete dirstate from its on-disk bytes.
Reads the header (parents, ghosts, entry count), parses the
dirblock rows for 1 + num_present_parents trees, and splits the
root block into its sentinel shape. After this, iter_entries
yields every entry. This is the read-side counterpart to the
header/parse/split helpers, composed into one call.
Sourcepub fn split_root_dirblock_into_contents(
&mut self,
) -> Result<(), SplitRootError>
pub fn split_root_dirblock_into_contents( &mut self, ) -> Result<(), SplitRootError>
Split self.dirblocks[0] — which the parser fills with both root
entries and contents-of-root entries — into the two sentinel
blocks Python’s _read_dirblocks / _split_root_dirblock_into_contents
produces: block 0 holds entries whose basename is empty (the root
itself and any parent-tree variants), and block 1 holds the rest.
Returns an error if the layout does not match the expected post-parse shape (fewer than two blocks, or block 1 is not the empty sentinel).
Sourcepub fn find_block_index_from_key(&self, key: &EntryKey) -> (usize, bool)
pub fn find_block_index_from_key(&self, key: &EntryKey) -> (usize, bool)
Locate the block for a given key. Mirrors
DirState._find_block_index_from_key, without the
_last_block_index / _split_path_cache memoisation layers
(those live on the Python object and are a follow-up port).
Sourcepub fn find_entry_index(&self, key: &EntryKey, block: &[Entry]) -> (usize, bool)
pub fn find_entry_index(&self, key: &EntryKey, block: &[Entry]) -> (usize, bool)
Locate the entry index for a key within a block. Mirrors
DirState._find_entry_index, in the simpler uncached form.
Sourcepub fn get_block_entry_index(
&self,
dirname: &[u8],
basename: &[u8],
tree_index: usize,
) -> BlockEntryIndex
pub fn get_block_entry_index( &self, dirname: &[u8], basename: &[u8], tree_index: usize, ) -> BlockEntryIndex
Look up a (dirname, basename) path in the given tree. Mirrors
DirState._get_block_entry_index.
Sourcepub fn get_lines(&self) -> Vec<Vec<u8>>
pub fn get_lines(&self) -> Vec<Vec<u8>>
Serialise the in-memory state to the byte chunks that make up the
on-disk file. Mirrors Python’s DirState.get_lines for the
common “we have in-memory data to write” branch; it does not
handle the fast-path shortcut that re-reads an unmodified file
from disk (that shortcut belongs on the soon-to-be-ported
save method).
Sourcepub fn mark_modified(
&mut self,
hash_changed_entries: &[EntryKey],
header_modified: bool,
)
pub fn mark_modified( &mut self, hash_changed_entries: &[EntryKey], header_modified: bool, )
Mark the dirstate as modified. Mirrors Python’s
DirState._mark_modified.
If hash_changed_entries is non-empty, only the hash cache is
affected: the provided entry keys are added to
known_hash_changes and the dirblock_state transitions from
NotInMemory/InMemoryUnmodified into InMemoryHashModified
(a full InMemoryModified state takes precedence and is not
downgraded).
If hash_changed_entries is empty the whole dirblock state is
considered dirty: dirblock_state becomes InMemoryModified
regardless of its previous value. header_modified is an
orthogonal flag that promotes header_state to
InMemoryModified as well.
Sourcepub fn mark_unmodified(&mut self)
pub fn mark_unmodified(&mut self)
Mark the dirstate as unmodified — both header and dirblock state
return to InMemoryUnmodified and the hash-change set is
cleared. Mirrors Python’s DirState._mark_unmodified.
Sourcepub fn set_data(&mut self, parent_ids: Vec<Vec<u8>>, dirblocks: Vec<Dirblock>)
pub fn set_data(&mut self, parent_ids: Vec<Vec<u8>>, dirblocks: Vec<Dirblock>)
Replace the entire in-memory state with parent_ids and
dirblocks, marking both the header and the dirblock data
fully modified. Mirrors Python’s DirState._set_data: the
caller owns any sort/shape invariants on dirblocks; this
method does not validate them.
Any cached id_index is invalidated. Python’s
_packed_stat_index has no equivalent on the Rust struct yet
and is therefore not touched here.
Sourcepub fn set_tree0(
&mut self,
key: &EntryKey,
details: TreeData,
) -> Result<(), MakeAbsentError>
pub fn set_tree0( &mut self, key: &EntryKey, details: TreeData, ) -> Result<(), MakeAbsentError>
Overwrite the tree-0 slot of the entry at key with the given
details. Returns an error if key is not present; otherwise
does no other bookkeeping — no id_index changes, no cross-ref
rewrites, no state bump. This is the narrow primitive the
py_update_entry hash-refresh path needs: callers that want
structural changes should use DirState::update_minimal or
DirState::add.
Sourcepub fn tree0_minikind(&self, key: &EntryKey) -> Option<Kind>
pub fn tree0_minikind(&self, key: &EntryKey) -> Option<Kind>
Return the live tree-0 minikind for key, or None when no
entry with that key is present. Used by callers that need to
refresh a stale snapshot against current dirblock contents
(notably set_state_from_inventory’s zipper-merge loop, which
used to rely on Python-side tuple aliasing to observe mid-loop
rewrites).
Sourcepub fn compute_sha_cutoff_time(&mut self) -> i64
pub fn compute_sha_cutoff_time(&mut self) -> i64
Compute and cache the SHA cutoff time: the boundary mtime/ctime
such that files newer than this are considered “racy” and skip
the cached-sha optimisation. Mirrors Python’s
DirState._sha_cutoff_time: returns now - 3 and stores it on
the instance so subsequent calls (within the same lock window)
reuse the same value rather than re-reading the wall clock.
The 3-second window is the legacy bzr value, picked so that stats made within a typical filesystem timestamp resolution of “now” are not trusted.
Sourcepub fn observed_sha1(
&mut self,
key: &EntryKey,
sha1: &[u8],
st_mode: u32,
st_size: u64,
st_mtime: i64,
st_ctime: i64,
st_dev: u64,
st_ino: u64,
) -> Result<Option<TreeData>, UpdateEntryError>
pub fn observed_sha1( &mut self, key: &EntryKey, sha1: &[u8], st_mode: u32, st_size: u64, st_mtime: i64, st_ctime: i64, st_dev: u64, st_ino: u64, ) -> Result<Option<TreeData>, UpdateEntryError>
Record an observed sha1 for key’s tree-0 row when the file’s
stat falls in the cacheable window. Mirrors Python’s
DirState._observed_sha1: silently ignores non-file kinds and
files whose mtime/ctime land after the cutoff.
Takes the stat fields unpacked so callers can feed in whichever
shape they already have (Python’s os.stat_result, Rust’s
[Metadata], synthetic fixture data).
Record the observed sha1 for the entry at key and return the
new tree-0 TreeData so callers that hold a mirror of the
entry row (e.g. Python tuple) can write it back in place
without a second lookup.
Returns Ok(None) when no update happened — non-regular-file,
or the stat falls inside the uncacheable window.
Sourcepub fn process_entry(
&mut self,
pstate: &mut ProcessEntryState,
entry_key: &EntryKey,
entry_trees: &[TreeData],
path_info: Option<&ProcessPathInfo>,
transport: &dyn Transport,
) -> Result<(Option<DirstateChange>, Option<bool>), ProcessEntryError>
pub fn process_entry( &mut self, pstate: &mut ProcessEntryState, entry_key: &EntryKey, entry_trees: &[TreeData], path_info: Option<&ProcessPathInfo>, transport: &dyn Transport, ) -> Result<(Option<DirstateChange>, Option<bool>), ProcessEntryError>
Refresh the tree-0 slot of key from the filesystem. Mirrors
Python’s py_update_entry: if the stat hasn’t changed since
the last time we saved, re-use the cached link-or-sha1;
otherwise read the file (or symlink) and rewrite the tree-0
slot. Returns the sha1 hex or symlink target, or None when
the on-disk kind is not supported (e.g. block/char devices),
when the row is a directory and the cached stat matches
(nothing to report), or when we skip the sha because the
Compare one dirstate entry against what’s on disk (or nothing,
if the path is absent in the target) and yield a
DirstateChange describing any differences. Ports Python’s
ProcessEntryPython._process_entry.
Returns (None, None) when the entry is uninteresting (no row
in either side of the comparison), (None, Some(false)) when
both sides match and pstate.include_unchanged is off,
(Some(change), Some(true)) for a real change, and
(Some(change), Some(false)) for an unchanged-but-included
report.
Sourcepub fn iter_changes_next(
&mut self,
iter: &mut IterChangesIter,
pstate: &mut ProcessEntryState,
transport: &dyn Transport,
) -> Result<Option<DirstateChange>, ProcessEntryError>
pub fn iter_changes_next( &mut self, iter: &mut IterChangesIter, pstate: &mut ProcessEntryState, transport: &dyn Transport, ) -> Result<Option<DirstateChange>, ProcessEntryError>
Advance the lazy iter_changes state machine and return the
next change to yield, or Ok(None) when the walk is done.
Mirrors Python’s ProcessEntryPython.iter_changes generator:
call repeatedly to get one change at a time.
Each call may emit 0 or more changes; leftover changes are
buffered on iter.pending so subsequent calls drain them
before resuming the walk.
Sourcepub fn update_entry(
&mut self,
key: &EntryKey,
abspath: &[u8],
stat: &StatInfo,
transport: &dyn Transport,
) -> Result<Option<Vec<u8>>, UpdateEntryError>
pub fn update_entry( &mut self, key: &EntryKey, abspath: &[u8], stat: &StatInfo, transport: &dyn Transport, ) -> Result<Option<Vec<u8>>, UpdateEntryError>
Sourcepub fn bootstrap_new_parent_slot(&mut self)
pub fn bootstrap_new_parent_slot(&mut self)
Append a NULL_PARENT_DETAILS row to every entry’s tree slot
list. Mirrors Python’s inline loop in update_basis_by_delta:
when the current dirstate has no parents and a new parent is
being introduced, each row needs space for the new parent’s
tree-1 slot before update_basis_by_delta can fill it in.
Sourcepub fn wipe_state(&mut self)
pub fn wipe_state(&mut self)
Forget all in-memory state, returning the object to the same
shape a freshly constructed DirState has before any load.
Mirrors Python’s DirState._wipe_state.
Python additionally clears _split_path_cache; that field has
no equivalent on the Rust struct yet (the still un-ported
memoisation layer on _find_block_index_from_key), so this
function resets what it can and leaves a note for the future
port to extend.
Sourcepub fn worth_saving(&self) -> bool
pub fn worth_saving(&self) -> bool
Whether the current in-memory state is worth persisting. Mirrors
DirState._worth_saving: full-dirblock or header modifications
always save; hash-only changes save only once they exceed
worth_saving_limit, and -1 disables hash-only saves entirely.
Sourcepub fn save_to<T: Transport + ?Sized>(
&mut self,
transport: &mut T,
) -> Result<bool, TransportError>
pub fn save_to<T: Transport + ?Sized>( &mut self, transport: &mut T, ) -> Result<bool, TransportError>
Persist the in-memory state through transport, assuming a
write lock is already held. This is the post-lock-upgrade core
of Python’s DirState.save: honours changes_aborted and
worth_saving as early-return gates, serialises get_lines()
via write_all, optionally fdatasyncs, and finishes with
mark_unmodified.
The caller owns the read→write lock-upgrade dance that Python’s
save performs via temporary_write_lock — the Transport
trait deliberately does not model it, because lock-upgrade
semantics belong to the Python LockToken plumbing rather than
to dirstate. A caller that wants the full Python behaviour
performs the upgrade, calls save_to, then restores the read
lock.
Returns Ok(true) if the state was actually written, Ok(false)
if an early-return gate prevented the write, and Err if the
transport is not write-locked or any write_all/fdatasync
call failed.
Sourcepub fn num_present_parents(&self) -> usize
pub fn num_present_parents(&self) -> usize
Number of parent entries present in each dirstate record row.
Mirrors Python’s DirState._num_present_parents — total
parents minus ghost parents.
Sourcepub fn set_state_from_inventory(
&mut self,
new_entries: Vec<(Vec<u8>, Vec<u8>, Kind, Vec<u8>, bool)>,
) -> Result<(), BasisApplyError>
pub fn set_state_from_inventory( &mut self, new_entries: Vec<(Vec<u8>, Vec<u8>, Kind, Vec<u8>, bool)>, ) -> Result<(), BasisApplyError>
Replace the entire tree-0 state with the rows produced by
walking new_inv.iter_entries_by_dir(). Mirrors Python’s
DirState.set_state_from_inventory: zips the existing dirstate
entries (in iteration order) against the incoming inventory
entries, calling DirState::update_minimal and
DirState::make_absent to drive the dirstate into the new
shape.
Each element of new_entries is a pre-sorted tuple
(path_utf8, file_id, minikind, fingerprint, executable). The
caller is expected to have built it from
iter_entries_by_dir, which yields paths in the order the
dirstate needs. fingerprint is normally empty for non
tree-reference entries; the tree-reference case carries the
reference_revision bytes.
Sourcepub fn set_parent_trees(
&mut self,
trees: Vec<Vec<u8>>,
ghosts: Vec<Vec<u8>>,
parent_tree_entries: Vec<Vec<(Vec<u8>, Vec<u8>, TreeData)>>,
) -> Result<(), EntriesToStateError>
pub fn set_parent_trees( &mut self, trees: Vec<Vec<u8>>, ghosts: Vec<Vec<u8>>, parent_tree_entries: Vec<Vec<(Vec<u8>, Vec<u8>, TreeData)>>, ) -> Result<(), EntriesToStateError>
Replace the parent trees. Mirrors Python’s
DirState.set_parent_trees.
trees gives the revision-id of every parent (including
ghosts) in order. ghosts is the list of revision-ids that
are ghosts — must be a subset of trees. parent_tree_entries
is one list per non-ghost parent tree, in the same order as
non-ghost parents appear in trees; each list is the result of
walking that tree via iter_entries_by_dir and mapping each
entry to (path_utf8, file_id, minikind, fingerprint, size, executable, tree_data) (i.e. path/file_id plus the 5-tuple
returned by inv_entry_to_details).
The method rebuilds the full dirblocks layout from: (a) the
current tree-0 rows already in self.dirblocks (non-absent,
non-relocated), and (b) the per-parent-tree entry lists.
Cross-tree relocation pointers are emitted in both the
vertical and horizontal axes, matching the legacy matrix
construction. Ghost parents occupy a tree slot but contribute
no entries — their slot is always NULL_PARENT_DETAILS.
Sourcepub fn entries_to_current_state(
&mut self,
new_entries: Vec<Entry>,
) -> Result<(), EntriesToStateError>
pub fn entries_to_current_state( &mut self, new_entries: Vec<Entry>, ) -> Result<(), EntriesToStateError>
Rebuild self.dirblocks from a pre-sorted, flat list of
entries. Mirrors Python’s DirState._entries_to_current_state.
new_entries must start with the root row (dirname and
basename both empty); otherwise
EntriesToStateError::MissingRootRow is returned. The
resulting layout contains the two sentinel empty-dirname blocks
followed by one block per distinct subdirectory, then fed
through DirState::split_root_dirblock_into_contents to
separate the root row from the root-contents rows.
This function does not re-sort entries — callers that hand in a sorted list skip the cost, and Python’s comment calls this out explicitly.
Sourcepub fn ensure_block(
&mut self,
parent_block_index: isize,
parent_row_index: isize,
dirname: &[u8],
) -> Result<usize, EnsureBlockError>
pub fn ensure_block( &mut self, parent_block_index: isize, parent_row_index: isize, dirname: &[u8], ) -> Result<usize, EnsureBlockError>
Ensure a block for dirname exists in self.dirblocks, creating
it if necessary. Mirrors Python’s DirState._ensure_block.
parent_block_index and parent_row_index identify the entry
whose directory is being ensured. The root row is special-cased:
(parent_block_index=0, parent_row_index=0, dirname=b"")
shortcuts to block index 1 — the sentinel contents-of-root
block produced by split_root_dirblock_into_contents.
On success returns the index of the block for dirname. On
failure — the dirname does not end with the basename stored at
the given parent coordinates — returns
EnsureBlockError::BadDirname to match Python’s
AssertionError("bad dirname ...").
Sourcepub fn discard_merge_parents(&mut self)
pub fn discard_merge_parents(&mut self)
Discard any parent trees beyond the first. Mirrors Python’s
DirState._discard_merge_parents.
After this function returns the dirstate contains either 1 or
2 trees per row: current + first parent, or just current if
the first parent was a ghost (Python keeps the parent slot but
replaces its tree data with a NULL_PARENT_DETAILS placeholder
so every row still has two tree slots). Entries whose tree-0
and tree-1 minikinds both fall into the “dead pattern” set
{(a,r), (a,a), (r,r), (r,a)} — i.e. absent or relocated in
both the current tree and the first parent — are removed from
their dirblock entirely.
The header is marked modified so the change survives a save.
This invalidates the cached id_index; callers must not hold
a reference to the old one across this call.
Sourcepub fn make_absent(&mut self, key: &EntryKey) -> Result<bool, MakeAbsentError>
pub fn make_absent(&mut self, key: &EntryKey) -> Result<bool, MakeAbsentError>
Mark key as absent for tree 0, following Python’s
DirState._make_absent.
Behaviour:
- Scan trees 1.. of the entry at
key. For each non-absent, non-relocated row, rememberkeyas still-referenced; for each relocated row, remember the relocation target’s key (same file_id, new dirname/basename). - If
keyis not still-referenced by any remaining tree, remove its entry row from the block and dropkeyfrom the id index. - For every remaining-key, set its tree-0 slot to
NULL_PARENT_DETAILS. Assert that the slot isn’t already absent (mirroring Python’sbad rowassertion). - Mark the dirstate modified.
Returns true when the entry row was removed in step (2),
matching Python’s last_reference return.
Sourcepub fn update_basis_apply_adds(
&mut self,
adds: &mut Vec<BasisAdd>,
) -> Result<(), BasisApplyError>
pub fn update_basis_apply_adds( &mut self, adds: &mut Vec<BasisAdd>, ) -> Result<(), BasisApplyError>
Apply a sequence of “adds” to tree 1, mirroring Python’s
DirState._update_basis_apply_adds. adds is a flat list of
per-entry records produced by update_basis_by_delta: each
describes a new entry to insert (or, when real_add is false,
the add half of a split rename). The caller is responsible for
collecting and translating Python inventory entries into
BasisAdd records — this function only touches dirblocks.
Sorts adds in-place by new_path to match Python’s
adds.sort(key=lambda x: x[1]). The resulting lexicographic
order ensures every parent dirblock is visited before its
children.
Invariants that produce an InconsistentDelta error — mirroring
Python’s _raise_invalid — are carried as
BasisApplyError::Invalid values so the pyo3 layer can wrap
them in the Python InconsistentDelta exception. Assertions
about internal state that should never happen (such as
_find_entry_index missing a key the linear scan locates) are
reported as BasisApplyError::Internal.
Side effects:
- may call
DirState::ensure_blockto materialise a dirblock for a missing parent directory; - mutates tree-1 slots of existing entries;
- inserts new entries with
[NULL_PARENT_DETAILS, new_details]; - converts cross-directory renames to tree-0 relocation rows when the new tree-1 entry’s tree-0 slot is absent but the file_id exists at a different path in tree 0;
- ensures a child dirblock exists for directory-kind adds;
- invalidates
id_indexandpacked_stat_indexcaches.
Sourcepub fn after_delta_check_parents(
&mut self,
parents: &[(Vec<u8>, Vec<u8>)],
tree_index: usize,
) -> Result<(), BasisApplyError>
pub fn after_delta_check_parents( &mut self, parents: &[(Vec<u8>, Vec<u8>)], tree_index: usize, ) -> Result<(), BasisApplyError>
Check that every (dirname_utf8, file_id) pair in parents
exists in tree_index at the given path with the given id
and is a directory. Mirrors Python’s
DirState._after_delta_check_parents.
Returns BasisApplyError::Invalid on the first parent that
is missing ("This parent is not present.") or not a
directory ("This parent is not a directory.").
Sourcepub fn check_delta_ids_absent(
&mut self,
new_ids: &[Vec<u8>],
tree_index: usize,
) -> Result<(), BasisApplyError>
pub fn check_delta_ids_absent( &mut self, new_ids: &[Vec<u8>], tree_index: usize, ) -> Result<(), BasisApplyError>
Verify that none of new_ids is already present at a live
entry in tree_index. Mirrors Python’s
DirState._check_delta_ids_absent — used by both
update_by_delta and update_basis_by_delta to guard against
a delta that resurrects an already-present file id.
On a conflict, returns BasisApplyError::Invalid carrying
the first offending path / file id.
Sourcepub fn update_minimal(
&mut self,
key: EntryKey,
tree0_details: TreeData,
path_utf8: Option<&[u8]>,
fullscan: bool,
) -> Result<(), BasisApplyError>
pub fn update_minimal( &mut self, key: EntryKey, tree0_details: TreeData, path_utf8: Option<&[u8]>, fullscan: bool, ) -> Result<(), BasisApplyError>
Update a single entry in tree 0 — either insert a new row or
replace its tree-0 details. Mirrors Python’s
DirState.update_minimal.
§Parameters
key:(dirname, basename, file_id)identifying the entry.tree0_details: replacement data for the tree-0 slot (thenew_detailstuple Python builds from minikind, fingerprint, size, executable, packed_stat).path_utf8:dirname + "/" + basenamewithout the leading slash, orb""for the root; used when building relocation pointers. Required whenever the method takes the cross-reference branch.fullscan: when true, skip the conflicting-entry check thatset_state_from_inventorydisables for bulk loads.
Returns Ok(()) on success, or
BasisApplyError::Invalid / BasisApplyError::Internal
for user-visible delta conflicts and internal invariant
violations (matching Python’s _raise_invalid /
AssertionError / “no path”).
Sourcepub fn add_path(
&mut self,
path: &str,
file_id: &[u8],
kind: Kind,
stat: Option<StatInfo>,
fingerprint: &[u8],
) -> Result<(), AddError>
pub fn add_path( &mut self, path: &str, file_id: &[u8], kind: Kind, stat: Option<StatInfo>, fingerprint: &[u8], ) -> Result<(), AddError>
High-level entry point mirroring Python’s DirState.add from
the top: takes a path string (any of "", "foo",
"foo/bar"), normalises the basename, validates ./..,
packs the stat, and dispatches to DirState::add.
Returns AddError::InvalidNormalization when NFC would point
at an inaccessible path, AddError::InvalidEntryName when the
basename is . or ... Other failures bubble up from
DirState::add.
Sourcepub fn add(
&mut self,
utf8path: &[u8],
dirname: &[u8],
basename: &[u8],
file_id: &[u8],
kind: Kind,
size: u64,
packed_stat: &[u8],
fingerprint: &[u8],
) -> Result<(), AddError>
pub fn add( &mut self, utf8path: &[u8], dirname: &[u8], basename: &[u8], file_id: &[u8], kind: Kind, size: u64, packed_stat: &[u8], fingerprint: &[u8], ) -> Result<(), AddError>
Add a new tracked entry. Mirrors Python’s DirState.add after
path normalisation: the caller is responsible for handing in
utf8path with its dirname/basename split already done, and
for supplying the packed_stat bytes (use pack_stat on the
os.lstat result, or None to substitute NULLSTAT).
kind is the filesystem kind; crate::osutils::Kind already
constrains it to the four valid variants.
The method performs the same duplicate-id detection Python does:
if file_id is already tracked at a live (non-absent) path it
returns AddError::DuplicateFileId. If the file_id existed
previously at a different path marked absent, that old row is
rewritten as a relocation pointer to the new path via
DirState::update_minimal, matching Python’s rename_from
fix-up. In that case the resulting entry’s parent-tree slot 0
stores a relocation row pointing back at the old path, so
history-aware tooling can still resolve the id.
The target dirblock is created (ensure_block) if missing, and a
child block is ensured when the new entry is a directory — both
matching Python’s post-insert _ensure_block call.
Sourcepub fn set_path_id(
&mut self,
path: &[u8],
new_id: &[u8],
) -> Result<(), SetPathIdError>
pub fn set_path_id( &mut self, path: &[u8], new_id: &[u8], ) -> Result<(), SetPathIdError>
Change the file id of the root path. Mirrors Python’s
DirState.set_path_id, which only supports path=b"".
Python’s original implementation called _make_absent on the
old root entry (which mutated the shared tree-0 slot to
NULL_PARENT_DETAILS when parent trees kept the entry alive)
and then called update_minimal with
packed_stat=entry[1][0][4]. The packed_stat observed by
update_minimal therefore depended on whether the mutation
had reset it: empty bytes when parents held the entry alive,
the original stat otherwise. This port reproduces that rule
explicitly.
Sourcepub fn apply_removals(
&mut self,
removals: &[(Vec<u8>, Vec<u8>)],
) -> Result<(), BasisApplyError>
pub fn apply_removals( &mut self, removals: &[(Vec<u8>, Vec<u8>)], ) -> Result<(), BasisApplyError>
Apply a sequence of “removals” to tree 0, mirroring Python’s
DirState._apply_removals. Each record is a
(file_id, path) tuple; the method sorts them in reverse
path order (so deeper paths are removed first), locates the
entry in tree 0, asserts it is present with the expected
file_id, and calls DirState::make_absent.
After each removal the directory block that used to hold the removed entry’s children is scanned for live tree-0 rows — any surviving row flags an inconsistent delta, matching Python’s “file id was deleted but its children were not deleted” guard.
Sourcepub fn validate(&self) -> Result<(), ValidateError>
pub fn validate(&self) -> Result<(), ValidateError>
Mirrors Python’s DirState._validate. Walks the dirblocks
and cross-references tree state invariants: root-block
sentinel, dirblock ordering, per-block entry ordering,
per-tree id→path consistency (absent / relocation /
file-or-dir rules), parent-entry presence, and id_index
back-references when the cache is populated.
Returns Ok(()) when all invariants hold, or a
ValidateError describing the first violation — which the
pyo3 layer turns into AssertionError to match Python.
Sourcepub fn update_basis_by_delta_from_inventory_delta(
&mut self,
delta: &InventoryDelta,
new_revid: Vec<u8>,
) -> Result<(), BasisApplyError>
pub fn update_basis_by_delta_from_inventory_delta( &mut self, delta: &InventoryDelta, new_revid: Vec<u8>, ) -> Result<(), BasisApplyError>
Rebase the basis tree onto new_revid. Mirrors Python’s
DirState.update_basis_by_delta — the sibling of
DirState::update_by_delta that rebases the basis tree.
This encapsulates the full Python entrypoint:
discard_merge_parents()to drop all parents past the first.- Ghost-check: returns
BasisApplyError::NotImplementedwhen any ghost parent remains, matching Python’sNotImplementedError. - When the dirstate has no parents, extend every entry’s
tree list with a
NULL_PARENT_DETAILSrow and appendnew_revidtoparents. - Replace
parents[0]withnew_revid. - Apply the pre-flattened, pre-sorted delta.
- Mark modified and clear id_index.
High-level entry point taking a native
crate::inventory_delta::InventoryDeltadirectly — does the per-row file_id validation + inv_entry flattening Python’s shim used to do before calling into Rust, then dispatches toDirState::update_basis_by_delta.
pub fn update_basis_by_delta( &mut self, entries: Vec<FlatBasisDeltaEntry>, new_revid: Vec<u8>, ) -> Result<(), BasisApplyError>
Sourcepub fn update_by_delta_from_inventory_delta(
&mut self,
delta: &InventoryDelta,
) -> Result<(), BasisApplyError>
pub fn update_by_delta_from_inventory_delta( &mut self, delta: &InventoryDelta, ) -> Result<(), BasisApplyError>
Apply a pre-flattened inventory delta to tree 0. Mirrors
Python’s DirState.update_by_delta — the workhorse for
apply_inventory_delta in dirstate-based trees.
Each entries element is the Python-side extraction of one
delta row: (old_path, new_path, file_id, parent_id, minikind, executable, fingerprint). The Python caller is
responsible for delta .check()/.sort() and for looking up
inv_entry.parent_id / kind → minikind / reference_revision
before calling this method.
This function:
- validates no repeated file_id,
- accumulates
removals,insertions,new_ids,parents, - expands each rename into delete+add pairs for all
descendant entries by walking
DirState::iter_child_entries, - calls
check_delta_ids_absent,apply_removals,apply_insertions, andafter_delta_check_parentsin order — matching Python’s try/except block exactly. High-level entry point taking a nativecrate::inventory_delta::InventoryDeltadirectly — does the per-row flattening Python’s shim used to do and dispatches toDirState::update_by_delta.
pub fn update_by_delta( &mut self, entries: Vec<FlatDeltaEntry>, ) -> Result<(), BasisApplyError>
Sourcepub fn apply_insertions(
&mut self,
adds: Vec<(EntryKey, Kind, bool, Vec<u8>, Vec<u8>)>,
) -> Result<(), BasisApplyError>
pub fn apply_insertions( &mut self, adds: Vec<(EntryKey, Kind, bool, Vec<u8>, Vec<u8>)>, ) -> Result<(), BasisApplyError>
Apply a sequence of “insertions” to tree 0. Mirrors Python’s
DirState._apply_insertions: sort the adds and, for each,
call DirState::update_minimal. A NotVersioned error
from update_minimal is reshaped into Invalid with reason
"Missing parent", matching Python’s
except NotVersionedError: self._raise_invalid(..., "Missing parent").
Sourcepub fn update_basis_apply_changes(
&mut self,
changes: &[(Vec<u8>, Vec<u8>, Vec<u8>, TreeData)],
) -> Result<(), BasisApplyError>
pub fn update_basis_apply_changes( &mut self, changes: &[(Vec<u8>, Vec<u8>, Vec<u8>, TreeData)], ) -> Result<(), BasisApplyError>
Apply a sequence of “changes” to tree 1. Mirrors Python’s
DirState._update_basis_apply_changes. Each change updates
the tree-1 slot of an existing entry whose file_id matches
at the new path. The entry must already exist and be live
(tree-1 minikind not absent/relocated); otherwise the caller
sees BasisApplyError::Invalid.
Invalidates id_index and packed_stat_index caches.
Sourcepub fn update_basis_apply_deletes(
&mut self,
deletes: &[(Vec<u8>, Option<Vec<u8>>, Vec<u8>, bool)],
) -> Result<(), BasisApplyError>
pub fn update_basis_apply_deletes( &mut self, deletes: &[(Vec<u8>, Option<Vec<u8>>, Vec<u8>, bool)], ) -> Result<(), BasisApplyError>
Apply a sequence of “deletes” to tree 1. Mirrors Python’s
DirState._update_basis_apply_deletes. Each delete either
removes an entry row entirely (when the active tree is also
absent/relocated) or sets its tree-1 slot to NULL_PARENT_DETAILS
so the file id survives in the active tree. The post-delete
dirblock integrity check walks child blocks to ensure no live
rows were left behind; that check follows Python exactly.
Each tuple is (old_path, Option<new_path>, file_id, real_delete)
where real_delete must equal new_path.is_none() — otherwise
the caller sees BasisApplyError::Invalid("bad delete delta").
Invalidates id_index and packed_stat_index caches.
Sourcepub fn get_entry_by_file_id(
&mut self,
tree_index: usize,
file_id: &[u8],
include_deleted: bool,
) -> GetEntryResult
pub fn get_entry_by_file_id( &mut self, tree_index: usize, file_id: &[u8], include_deleted: bool, ) -> GetEntryResult
Look up the dirstate entry for file_id in tree_index,
following any relocation chain the entries describe. Mirrors
the fileid_utf8 branch of Python’s DirState._get_entry.
If include_deleted is true, an entry whose tree data is
absent (Kind::Absent) is returned rather than hidden.
Returns GetEntryResult::NotFound if no key for file_id
exists in the id index, or GetEntryResult::Entry with the
located entry key on success. Unknown minikinds are impossible
by construction — Kind only parses the six valid codes.
The result is returned as an owned EntryKey rather than a
borrow because the caller may need to keep self borrowable
for other lookups; callers that need the full entry can
re-fetch it via DirState::find_block_index_from_key and
DirState::find_entry_index.
Sourcepub fn maybe_remove_row(
entries: &mut Vec<Entry>,
index: usize,
id_index: &mut IdIndex,
) -> bool
pub fn maybe_remove_row( entries: &mut Vec<Entry>, index: usize, id_index: &mut IdIndex, ) -> bool
Remove entries[index] from entries (and drop it from
id_index) if none of its trees hold a live record — i.e.
every tree column is b'a' (absent) or b'r' (relocation).
Mirrors Python’s DirState._maybe_remove_row.
Returns true if the row was removed, false otherwise.
Sourcepub fn sort_entries(entries: &mut [Entry])
pub fn sort_entries(entries: &mut [Entry])
Sort entries into canonical dirblock order. Mirrors Python’s
DirState._sort_entries: the sort key is
(dirname.split(b"/"), basename, file_id), which matches the
order _entries_to_current_state expects before writing.
The Python version caches dirname → split because real-world
calls re-sort ~10× more entries than distinct directories;
Rust’s sort_by_cached_key gets the same amortisation
automatically.
Sourcepub fn entries_for_path(&self, path_utf8: &[u8]) -> Vec<&Entry>
pub fn entries_for_path(&self, path_utf8: &[u8]) -> Vec<&Entry>
Return references to every dirstate entry whose key (dirname, basename) matches path_utf8, across all file ids. Mirrors
Python’s DirState._entries_for_path: a path can be represented
by multiple rows when the same location held different file ids
in different parent trees, so the lookup walks the block
starting at the first matching entry and stops at the first
non-match. Returns an empty list when no block exists for the
parent directory.
Sourcepub fn get_entry_by_path(
&self,
tree_index: usize,
path_utf8: &[u8],
) -> Option<&Entry>
pub fn get_entry_by_path( &self, tree_index: usize, path_utf8: &[u8], ) -> Option<&Entry>
Look up the dirstate entry at path_utf8 in tree_index and
return a reference to it, or None if the path is not present
in that tree. Mirrors the path_utf8 branch of Python’s
DirState._get_entry (the file-id fallback is a follow-up port
once _get_id_index exists in Rust).
path_utf8 is split on the last / into a (dirname, basename)
pair matching osutils.split, then fed through
DirState::get_block_entry_index. The result points at a
live (non-absent, non-relocated) entry only when path_present
is true; otherwise None is returned.
Sourcepub fn iter_child_entries(
&self,
tree_index: usize,
path_utf8: &[u8],
) -> impl Iterator<Item = &Entry>
pub fn iter_child_entries( &self, tree_index: usize, path_utf8: &[u8], ) -> impl Iterator<Item = &Entry>
Walk the subtree rooted at path_utf8 and return every live
entry (kind not in b'a'/b'r') in tree_index, in the order
Python’s DirState._iter_child_entries yields them.
The walk is breadth-first: all immediate children of path_utf8
first, then all children of those (grouped by whichever parent
they were enqueued from). Directory entries whose tree data says
they’re directories (b'd') are recursed into; absent and
relocated entries are filtered out of the output but do not
suppress the recursion into other entries.
An empty path_utf8 walks the top of the tree. Asking for the
children of a non-directory yields nothing.
Sourcepub fn bisect<F>(
&self,
paths: Vec<Vec<u8>>,
file_size: u64,
read_range: F,
) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
pub fn bisect<F>( &self, paths: Vec<Vec<u8>>, file_size: u64, read_range: F, ) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
Bisect the on-disk dirstate for rows at the given paths.
Mirrors Python’s DirState._bisect.
read_range(offset, len) must return the bytes at [offset, offset+len) from the dirstate file. file_size is the full
file length (used to bound the initial bisect window). The
caller must have already loaded the header (so
end_of_header and num_present_parents() are populated)
and must hold a read or write lock on the file.
Returns a map from path_utf8 → list of entries at that path
(an entry is the usual (key, [tree_data, ...]) shape).
Missing paths do not appear in the map.
Sourcepub fn bisect_dirblocks<F>(
&self,
dir_list: Vec<Vec<u8>>,
file_size: u64,
read_range: F,
) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
pub fn bisect_dirblocks<F>( &self, dir_list: Vec<Vec<u8>>, file_size: u64, read_range: F, ) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
Bisect the on-disk dirstate for every entry whose dirname is
in dir_list. Mirrors Python’s DirState._bisect_dirblocks.
Sourcepub fn bisect_recursive<F>(
&self,
paths: Vec<Vec<u8>>,
file_size: u64,
read_range: F,
) -> Result<HashMap<(Vec<u8>, Vec<u8>, Vec<u8>), Vec<TreeData>>, BisectError>
pub fn bisect_recursive<F>( &self, paths: Vec<Vec<u8>>, file_size: u64, read_range: F, ) -> Result<HashMap<(Vec<u8>, Vec<u8>, Vec<u8>), Vec<TreeData>>, BisectError>
Recursive variant of bisect: for every path in paths find
the row and, if it is a directory, recursively bisect for its
children. Renames are followed via the fingerprint pointer.
Mirrors DirState._bisect_recursive.
Returns a map from (dirname, basename, file_id) → list of
tree-data rows.