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§
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§
Sourcefn get_blob_bytes(&self, hash: &ContentHash) -> Result<Option<Bytes>>
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.
Sourcefn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>>
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.
Sourcefn loose_blob_path(&self, _hash: &ContentHash) -> Option<PathBuf>
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.
Sourcefn promote_to_loose_uncompressed(&self, _hash: &ContentHash) -> Result<bool>
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.
Sourcefn clear_recent_caches(&self)
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.
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>)>>
Sourcefn put_blobs_packed(&self, blobs: Vec<(ContentHash, Vec<u8>)>) -> Result<()>
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).
fn install_pack( &self, pack_data: &[u8], index_data: &[u8], ) -> Result<Vec<PackObjectId>>
Sourcefn install_pack_streaming(
&self,
pack_path: &Path,
index_path: &Path,
) -> Result<Vec<PackObjectId>>
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.
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)
Sourcefn has_redactions_for_blob(&self, _blob: &ContentHash) -> Result<bool>
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.
Sourcefn get_redactions_bytes_for_blob(
&self,
_blob: &ContentHash,
) -> Result<Option<Vec<u8>>>
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).
Sourcefn put_redactions_bytes_for_blob(
&self,
_blob: &ContentHash,
_bytes: &[u8],
) -> Result<()>
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.
Sourcefn list_blobs_with_redactions(&self) -> Result<Vec<ContentHash>>
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![]).
Sourcefn has_state_visibility_for_state(&self, _state: &ChangeId) -> Result<bool>
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.
Sourcefn get_state_visibility_bytes_for_state(
&self,
_state: &ChangeId,
) -> Result<Option<Vec<u8>>>
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).
Sourcefn put_state_visibility_bytes_for_state(
&self,
_state: &ChangeId,
_bytes: &[u8],
) -> Result<()>
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.
Sourcefn list_states_with_visibility(&self) -> Result<Vec<ChangeId>>
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".