Skip to main content

OpLog

Struct OpLog 

Source
pub struct OpLog { /* private fields */ }

Implementations§

Source§

impl OpLog

Source

pub fn open(root: &Path) -> Result<Self>

Source

pub fn put(&self, rec: &OperationRecord) -> Result<()>

Persist a record. Idempotent on existing op_ids (the bytes must match by content addressing).

Crash safety: the tempfile’s data is fsync’d before rename, so a successful return implies a durable file at the final path. The containing directory is not fsync’d; on a crash between rename and the directory’s metadata flush, the file can be lost. For a content-addressed log this is acceptable — a lost record can be re-derived from the same source — but callers that also persist references to the op_id (e.g. branch heads) should fsync those refs after put returns.

Source

pub fn get(&self, op_id: &OpId) -> Result<Option<OperationRecord>>

Source

pub fn repack(&self, threshold: usize) -> Result<usize>

Consolidate loose op records into a deterministic, content- addressed packfile (#261 slice 1). Returns the number of ops moved into the new pack.

threshold is the minimum number of loose ops required to trigger a repack — under that, returns 0 and leaves the log alone. The idea: small stores stay loose; only repack when the file count starts to matter.

Determinism: the pack name is the SHA-256 of the sorted op_ids (newline-joined), so two independent runs against the same set of loose ops produce a byte-identical pack. Re-running on an empty loose directory is a no-op.

Crash safety: the .pack.tmp and .idx.tmp files are fsync’d before rename; loose files are deleted only after both renames succeed. A crash mid-repack leaves both loose and partial-pack files; a subsequent get finds the loose version, and a subsequent repack cleans up.

Source

pub fn evict(&self, victims: &BTreeSet<OpId>) -> Result<usize>

Remove every op_id in victims from the log, across both loose files and packfiles (#261 slice 2). Used by lex op gc after a retention plan identifies which ops to drop. Idempotent — calling twice with the same set is a no-op on the second pass.

Pack handling: any pack containing one or more victims is rewritten to a new content-addressed pack with only the surviving ops; the old pack and its index file are deleted. A pack whose every op is a victim is deleted outright.

Returns the count of ops actually removed (loose files deleted + packed ops dropped). Pre-existing absences don’t contribute.

Source

pub fn delete(&self, op_id: &OpId) -> Result<()>

Remove a record from the log. Used by crate::migrate to delete the old <op_id>.json files after a format migration has written their replacements. Idempotent on missing files.

Not part of the day-to-day op-log API — the log is append-only by design (#129). The only legitimate caller is the migration tool, which is supervising a destructive, --confirm-gated batch.

Source

pub fn walk_back( &self, head: &OpId, limit: Option<usize>, ) -> Result<Vec<OperationRecord>>

Walk parents transitively. Newest-first, BFS, dedup’d by op_id. Stops at parentless ops or after limit records.

Source

pub fn walk_forward( &self, head: &OpId, limit: Option<usize>, ) -> Result<Vec<OperationRecord>>

Same set as walk_back but oldest-first, and topological: every op comes after all of its parents. Used by branch_head (and every other consumer) for left-to-right transition replay, so this is the one definition of “the order a head is replayed in”.

walk_back is a breadth-first walk, and its reverse is not a topological order once a merge joins lines of different lengths: an op reachable by a short path is emitted before its own descendant on the long one, so it lands after that descendant here. Replaying it then overwrote a later change with an earlier one (#1062). The order is now repaired by Self::linearize, which keeps the BFS order wherever it was already topological — every linear history, and any merge of equal-length lines — so those replay exactly as they always did.

Source

pub fn linearize(records: Vec<OperationRecord>) -> Vec<OperationRecord>

Reorder records into a topological order: every record comes after all of its parents that are in the set (a parent missing from the set — an incomplete local log — imposes no constraint, the same leniency the walks have). Among the records ready at any point, the one listed first in records goes first, so an input that is already topological comes back unchanged, and the result is a pure function of the input order.

Source

pub fn continues_from(records: &[OperationRecord], since: &OpId) -> bool

Whether records is exactly a continuation of since: every record descends from since through parents that are themselves in records (or are since), so none of them is an ancestor of since and none has an ancestor outside the set. This is the shape Self::walk_forward_since returns for a plain fast-forward, and NOT the shape it returns for a merge: there it also walks the second parent’s history back to genesis, which is history since already contains (#1062). Replaying such a set on top of a state computed for since re-applies old changes over newer ones.

Source

pub fn walk_forward_since( &self, head: &OpId, since: &OpId, ) -> Result<Option<Vec<OperationRecord>>>

Like Self::walk_forward, but bounded: walk from head back toward genesis and stop as soon as since is reached, without visiting since’s own parents or including since itself in the result. Returns oldest-first, suitable for incrementally extending a transition map already computed as of since.

Returns Ok(None) if since is never reached (not an ancestor of head — e.g. after a branch reset or a merge that reordered history): callers should fall back to a full walk_forward in that case, since there is nothing valid to incrementally extend.

This is the piece walk_forward itself doesn’t provide: its own limit truncates the result after a full walk_back to genesis has already completed (see its body above), so it can’t turn an O(N)-in-total-history walk into an O(ops since a checkpoint) one. head == since returns Ok(Some(vec![])) without touching the op log at all.

Source

pub fn lca(&self, a: &OpId, b: &OpId) -> Result<Option<OpId>>

Common ancestor of two op_ids in the DAG.

On tree-shaped histories and chain merges this is the lowest common ancestor — the closest shared op. On criss-cross merges (two ops each with two parents from independent histories) there can be multiple incomparable common ancestors; this picks one deterministically (the first hit when traversing b’s ancestors newest-first), but not via a recursive merge. None if no shared ancestor exists.

Tier-1 merge in #129 covers linear and tree-shaped histories; criss-cross resolution is deferred to a future tier (Git’s recursive strategy is the reference).

Source

pub fn list_all(&self) -> Result<Vec<OperationRecord>>

Every record in the log. Order is whatever the directory listing produces — undefined and not stable. Used by the [crate::predicate] evaluator when no narrower candidate set is available.

Source

pub fn ops_since( &self, head: &OpId, base: Option<&OpId>, ) -> Result<Vec<OperationRecord>>

Ops in head’s history that are not in base’s history. base = None means “include all of head’s history” (used for independent-histories case where the LCA is None).

Auto Trait Implementations§

§

impl Freeze for OpLog

§

impl RefUnwindSafe for OpLog

§

impl Send for OpLog

§

impl Sync for OpLog

§

impl Unpin for OpLog

§

impl UnsafeUnpin for OpLog

§

impl UnwindSafe for OpLog

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.