Skip to main content

ObjectStore

Trait ObjectStore 

Source
pub trait ObjectStore: Send + Sync {
Show 42 methods // Required methods fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>>; fn put_blob(&self, blob: &Blob) -> Result<ContentHash>; fn has_blob(&self, hash: &ContentHash) -> Result<bool>; fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>>; fn put_tree(&self, tree: &Tree) -> Result<ContentHash>; fn has_tree(&self, hash: &ContentHash) -> Result<bool>; fn get_state(&self, id: &ChangeId) -> Result<Option<State>>; fn put_state(&self, state: &State) -> Result<()>; fn has_state(&self, id: &ChangeId) -> Result<bool>; fn list_states(&self) -> Result<Vec<ChangeId>>; fn get_action(&self, id: &ActionId) -> Result<Option<Action>>; fn put_action(&self, action: &mut Action) -> Result<ActionId>; fn list_actions(&self) -> Result<Vec<ActionId>>; fn list_blobs(&self) -> Result<Vec<ContentHash>>; fn list_trees(&self) -> Result<Vec<ContentHash>>; // Provided methods fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>> { ... } fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> { ... } fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf> { ... } fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool> { ... } fn clear_recent_caches(&self) { ... } fn put_blob_with_hash( &self, blob: &Blob, hash: ContentHash, ) -> Result<ContentHash> { ... } fn put_blob_bytes_with_hash( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash> { ... } fn put_tree_serialized( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash> { ... } fn put_state_serialized(&self, data: &[u8], id: ChangeId) -> Result<()> { ... } fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()> { ... } fn get_pack_object( &self, id: &PackObjectId, ) -> Result<Option<(ObjectType, Vec<u8>)>> { ... } fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()> { ... } fn install_pack( &self, pack_data: &[u8], index_data: &[u8], ) -> Result<Vec<PackObjectId>> { ... } fn install_pack_streaming( &self, pack_path: &Path, index_path: &Path, ) -> Result<Vec<PackObjectId>> { ... } fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)> { ... } fn prune_loose_objects(&self) -> Result<(u64, u64)> { ... } fn begin_snapshot_write_batch(&self) -> Result<()> { ... } fn flush_snapshot_write_batch(&self) -> Result<()> { ... } fn abort_snapshot_write_batch(&self) { ... } fn has_redactions_for_blob(&self, _blob: &ContentHash) -> Result<bool> { ... } fn get_redactions_bytes_for_blob( &self, _blob: &ContentHash, ) -> Result<Option<Vec<u8>>> { ... } fn put_redactions_bytes_for_blob( &self, _blob: &ContentHash, _bytes: &[u8], ) -> Result<()> { ... } fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>> { ... } fn has_state_visibility_for_state(&self, _state: &ChangeId) -> Result<bool> { ... } fn get_state_visibility_bytes_for_state( &self, _state: &ChangeId, ) -> Result<Option<Vec<u8>>> { ... } fn put_state_visibility_bytes_for_state( &self, _state: &ChangeId, _bytes: &[u8], ) -> Result<()> { ... } fn list_states_with_visibility(&self) -> Result<Vec<ChangeId>> { ... }
}
Expand description

Trait for object storage backends.

Required Methods§

Provided Methods§

Source

fn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>>

Zero-copy variant of get_blob. Returns a bytes::Bytes view of the blob’s content, which for FsStore reads is a slice into the pack file’s mmap when the entry is non-delta and uncompressed — no allocation, no memcpy.

Default impl wraps get_blob’s Vec<u8> in a Bytes (one Arc allocation, no body copy) so backends without a native fast path still satisfy the contract. The mount’s hot read path goes through this method instead of get_blob so the pack-mmap fast path lights up automatically.

Source

fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>>

Return the uncompressed byte length of the blob identified by hash, or Ok(None) when the blob is not in the store.

The contract is “size without paying for content”: backends are expected to honour this with a header read or index lookup rather than a full decompression. This is the hot path for directory listings (ls -l over a thread mount) where loading every blob just to learn its size would dominate.

The default implementation falls back to get_blob so backends without a cheap size accessor still satisfy the contract; native stores (FsStore, InMemoryStore) override this with a header- or hashmap-only path.

Source

fn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf>

Filesystem path of the loose blob whose on-disk bytes are byte-identical to the blob’s uncompressed content, suitable for hard_link/clonefile materialization without going through get_blob.

Returns None when the blob is missing, is only available via a packfile, is stored compressed (the on-disk bytes wouldn’t match what a worktree consumer needs to read), or the backend doesn’t expose stable filesystem paths (e.g. InMemoryStore, S3Store). The default impl returns None so non-FsStore backends silently fall through to the bytes path.

Source

fn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool>

Ensure the blob identified by hash is materialized as an uncompressed loose file at the canonical loose path so that loose_blob_path returns Some(path) on a subsequent call.

This is the “warm canonical store” path that lets the hardlink-first materializer keep its 5–10× wall-clock and storage-allocation wins after pack_objects + prune_loose_objects has moved everything into a packfile. Without this, the lazy hardlink path silently degrades to fs::write(decompressed) on every materialize, because loose_blob_path returns None for pack-only and compressed-loose blobs.

Cost-amortization: the first promotion of a blob pays decompress + atomic write. Every subsequent materialize of the same blob — into the same worktree on goto, or into a sibling worktree on delegate — is a single link(2). Net win for any N > 1 materializations; break-even at N == 1.

Pack invariants are preserved: this method does not remove the pack-resident copy. The blob lives in both pack and loose- uncompressed until the next prune_loose_objects cycle, at which point the loose mirror is discarded and a future materialize re-promotes on demand.

Idempotent: a blob that’s already loose-and-uncompressed is a no-op fast path. A blob that’s loose-but-compressed is rewritten in place (atomically) with the uncompressed bytes. A blob that’s pack-resident is decompressed out of the pack and written loose without touching the pack.

Returns Ok(true) when the call did real work (a write happened), Ok(false) when it was a no-op (blob was already loose+uncompressed), and Err when the blob isn’t in the store at all. The default impl returns Ok(false) for backends that don’t expose loose paths (InMemoryStore, S3Store), since the hardlink path is fundamentally inapplicable there.

Source

fn clear_recent_caches(&self)

Drop any in-memory caches of decompressed blobs / trees / states. The next access to any object pays full I/O + decompression cost. No-op for stores that don’t cache (InMemoryStore is already the source of truth).

Exposed primarily for benchmarks that want to measure the true cold-cache path without rebuilding the store from scratch. Production callers don’t need to invoke this.

Source

fn put_blob_with_hash( &self, blob: &Blob, hash: ContentHash, ) -> Result<ContentHash>

Source

fn put_blob_bytes_with_hash( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash>

Source

fn put_tree_serialized( &self, data: &[u8], hash: ContentHash, ) -> Result<ContentHash>

Source

fn put_state_serialized(&self, data: &[u8], id: ChangeId) -> Result<()>

Source

fn put_action_serialized(&self, data: &[u8], id: ActionId) -> Result<()>

Source

fn get_pack_object( &self, id: &PackObjectId, ) -> Result<Option<(ObjectType, Vec<u8>)>>

Source

fn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()>

Bulk-write a batch of blobs as a single durable unit. The default implementation falls back to per-blob writes; backends that support packfiles (i.e. FsStore) override this to install one packfile + index — two fsyncs total instead of N. Used by the snapshot hot path so writing 1000 small files takes ~one fsync, not 1000.

Blobs already present in the store are skipped on the way in (the caller would otherwise duplicate them in the pack).

Source

fn install_pack( &self, pack_data: &[u8], index_data: &[u8], ) -> Result<Vec<PackObjectId>>

Source

fn install_pack_streaming( &self, pack_path: &Path, index_path: &Path, ) -> Result<Vec<PackObjectId>>

Install a pack and its index from on-disk files (typically produced by StreamingPackBuilder). The default impl reads both files fully and delegates to install_pack, so any backend that doesn’t override this still works (at the cost of giving back the bounded-memory promise). Real fs- backed stores override this to rename(2) both files into the pack directory without ever loading them.

On success, the source files at pack_path/index_path may have been moved or removed depending on the backend; callers shouldn’t continue to rely on them.

Returns the ids of the installed objects — the same set install_pack reports for the equivalent byte-buffer install, so callers (e.g. native sync) read the installed ids off the install result instead of tracking them out-of-band.

Source

fn pack_objects(&self, aggressive: bool) -> Result<(u64, u64)>

Source

fn prune_loose_objects(&self) -> Result<(u64, u64)>

Source

fn begin_snapshot_write_batch(&self) -> Result<()>

Source

fn flush_snapshot_write_batch(&self) -> Result<()>

Source

fn abort_snapshot_write_batch(&self)

Source

fn has_redactions_for_blob(&self, _blob: &ContentHash) -> Result<bool>

Whether the store holds any redaction record for the given blob.

Redactions live in a sidecar (<heddle_dir>/redactions/) that is structurally outside the content-addressed object graph so GC can’t reach them. The wire layer needs a cheap probe to decide whether to ship a redaction for a blob in the closure, so this is a separate method rather than a get_* + null check.

Default impl returns Ok(false) — stores that don’t model redactions silently report “no redactions,” which is the correct behaviour for purely in-memory or remote-shim stores.

Source

fn get_redactions_bytes_for_blob( &self, _blob: &ContentHash, ) -> Result<Option<Vec<u8>>>

Return the raw rmp-encoded RedactionsBlob bytes for the given blob, or Ok(None) if no redaction record exists. The bytes are byte-identical to what was written by put_redactions_bytes_for_blob (or by Repository::put_redaction); this is the wire-transfer payload, not a re-serialized view.

Default impl returns Ok(None).

Source

fn put_redactions_bytes_for_blob( &self, _blob: &ContentHash, _bytes: &[u8], ) -> Result<()>

Persist the rmp-encoded RedactionsBlob bytes for the given blob. Receiver-side replay calls this after signature verification so the bytes land in the same sidecar that the sender’s Repository::put_redaction writes to.

Default impl returns an “unsupported” error — stores that don’t model redactions (e.g. read-only shims) refuse rather than silently dropping the record.

Source

fn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>>

List every blob that has at least one redaction record. Used by the GC pin guard and by sync to enumerate redactions for the state closure. Order is unspecified; callers that need stable ordering should sort.

Default impl returns Ok(vec![]).

Source

fn has_state_visibility_for_state(&self, _state: &ChangeId) -> Result<bool>

Whether the store holds any state-visibility record for state.

Like redactions, state-visibility records live in a sidecar outside the content-addressed object graph and cannot ride native packs. Sync uses this probe while enumerating a state closure so a non-public state can advertise the sidecar that must travel out-of-pack.

Default impl returns Ok(false) for stores that do not model this sidecar.

Source

fn get_state_visibility_bytes_for_state( &self, _state: &ChangeId, ) -> Result<Option<Vec<u8>>>

Return the raw rmp-encoded StateVisibilityBlob bytes for state, or Ok(None) if no sidecar exists. The bytes are the wire-transfer payload for state visibility.

Default impl returns Ok(None).

Source

fn put_state_visibility_bytes_for_state( &self, _state: &ChangeId, _bytes: &[u8], ) -> Result<()>

Persist raw StateVisibilityBlob bytes for state.

Default impl returns an “unsupported” error so stores that do not model the sidecar refuse instead of dropping it.

Source

fn list_states_with_visibility(&self) -> Result<Vec<ChangeId>>

List every state with at least one state-visibility record.

Default impl returns Ok(vec![]).

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§