Skip to main content

DirState

Struct DirState 

Source
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: PathBuf

Path 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: MemoryState

State of the header (NotInMemory until _read_header runs).

§dirblock_state: MemoryState

State of the per-row dirblock data.

§changes_aborted: bool

If 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: usize

Declared 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: i64

Below this many hash-only changes a save is skipped. -1 means never save hash changes; 0 means always save them.

§fdatasync: bool

Call fdatasync after writing the state file if true.

§use_filesystem_for_exec: bool

Trust the filesystem’s executable bit when building tree data.

§bisect_page_size: usize

Bisect 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

Source

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__.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

pub fn dirblock_count(&self) -> usize

Number of dirblocks. Paired with DirState::entry_at to drive an index cursor over DirState::iter_entries.

Source

pub fn dirblock_entry_count(&self, block_index: usize) -> usize

Number of entries in dirblock block_index, or 0 if out of range.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn update_entry( &mut self, key: &EntryKey, abspath: &[u8], stat: &StatInfo, transport: &dyn Transport, ) -> Result<Option<Vec<u8>>, UpdateEntryError>

Refresh the tree-0 slot of key from the filesystem. Mirrors Python’s py_update_entry:

Arguments are (key, abspath, stat, transport) — see the doc comment on StatInfo for the stat fields, and the Transport trait for read_link semantics.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 ...").

Source

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.

Source

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:

  1. Scan trees 1.. of the entry at key. For each non-absent, non-relocated row, remember key as still-referenced; for each relocated row, remember the relocation target’s key (same file_id, new dirname/basename).
  2. If key is not still-referenced by any remaining tree, remove its entry row from the block and drop key from the id index.
  3. For every remaining-key, set its tree-0 slot to NULL_PARENT_DETAILS. Assert that the slot isn’t already absent (mirroring Python’s bad row assertion).
  4. Mark the dirstate modified.

Returns true when the entry row was removed in step (2), matching Python’s last_reference return.

Source

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_block to 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_index and packed_stat_index caches.
Source

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.").

Source

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.

Source

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 (the new_details tuple Python builds from minikind, fingerprint, size, executable, packed_stat).
  • path_utf8: dirname + "/" + basename without the leading slash, or b"" 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 that set_state_from_inventory disables 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”).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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:

  1. discard_merge_parents() to drop all parents past the first.
  2. Ghost-check: returns BasisApplyError::NotImplemented when any ghost parent remains, matching Python’s NotImplementedError.
  3. When the dirstate has no parents, extend every entry’s tree list with a NULL_PARENT_DETAILS row and append new_revid to parents.
  4. Replace parents[0] with new_revid.
  5. Apply the pre-flattened, pre-sorted delta.
  6. Mark modified and clear id_index. High-level entry point taking a native crate::inventory_delta::InventoryDelta directly — does the per-row file_id validation + inv_entry flattening Python’s shim used to do before calling into Rust, then dispatches to DirState::update_basis_by_delta.
Source

pub fn update_basis_by_delta( &mut self, entries: Vec<FlatBasisDeltaEntry>, new_revid: Vec<u8>, ) -> Result<(), BasisApplyError>

Source

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:

  1. validates no repeated file_id,
  2. accumulates removals, insertions, new_ids, parents,
  3. expands each rename into delete+add pairs for all descendant entries by walking DirState::iter_child_entries,
  4. calls check_delta_ids_absent, apply_removals, apply_insertions, and after_delta_check_parents in order — matching Python’s try/except block exactly. High-level entry point taking a native crate::inventory_delta::InventoryDelta directly — does the per-row flattening Python’s shim used to do and dispatches to DirState::update_by_delta.
Source

pub fn update_by_delta( &mut self, entries: Vec<FlatDeltaEntry>, ) -> Result<(), BasisApplyError>

Source

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").

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn bisect<F>( &self, paths: Vec<Vec<u8>>, file_size: u64, read_range: F, ) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
where F: FnMut(u64, usize) -> Result<Vec<u8>, 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.

Source

pub fn bisect_dirblocks<F>( &self, dir_list: Vec<Vec<u8>>, file_size: u64, read_range: F, ) -> Result<HashMap<Vec<u8>, Vec<Entry>>, BisectError>
where F: FnMut(u64, usize) -> Result<Vec<u8>, BisectError>,

Bisect the on-disk dirstate for every entry whose dirname is in dir_list. Mirrors Python’s DirState._bisect_dirblocks.

Source

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>
where F: FnMut(u64, usize) -> Result<Vec<u8>, 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.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Ungil for T
where T: Send,