pub trait GitOps: Send + Sync {
// Required methods
fn put(&self, pack: &[u8], refs: &[RefUpdate]) -> Result<TxId, Error>;
fn put_pack(&self, bytes: &[u8]) -> Result<TxId, Error>;
fn put_refs(&self, updates: &[RefUpdate]) -> Result<TxId, Error>;
fn get(&self, oid: &[u8]) -> Result<Option<Stored>, Error>;
fn has(&self, oid: &[u8]) -> Result<bool, Error>;
fn size(&self, oid: &[u8]) -> Result<Option<u64>, Error>;
fn extents(&self, oids: &[&[u8]]) -> Result<Vec<Option<(u64, u64)>>, Error>;
fn refs(&self) -> Result<Vec<RefRow>, Error>;
fn update_ref(
&self,
name: &str,
old: Option<&[u8]>,
new: Option<&[u8]>,
) -> Result<TxId, Error>;
fn put_refs_cas(&self, edits: &[RefCas<'_>]) -> Result<TxId, Error>;
fn reachable(
&self,
want: &[&[u8]],
have: &[&[u8]],
) -> Result<Vec<Vec<u8>>, Error>;
fn gc(&self) -> Result<GcReport, Error>;
}Expand description
The eleven. Typed, backend-neutral, and the only entry point a git server needs into storage.
Implemented by znippy’s GitStore (Arrow-IPC) and by storage-git-gix
(gix). No method invents storage, an index, a durability contract or a
concurrency mechanism; if one looks like it does, that is the bug.
Send + Sync is a supertrait because every consumer shares one store across
threads and cannot do otherwise. gunnar’s RepoStore is Send + Sync — the
server holds one per repository and serves many connections from it at once —
so a RepoStore holding an Arc<dyn GitOps> does not compile without this
(E0277 at every implementor). The traits GitOps replaced, ObjectStore
and RefStore, both carried it; dropping it here was an oversight of the
extraction, not a decision.
It costs the implementers nothing: both already satisfy it. The Arrow arm is
built around a per-account indexer thread and declares ArchiveWrite: Send + Sync and ObjectAbsorb: Send + Sync itself; the gix arm shares a pooled odb
handle across workers. The alternative — spelling dyn GitOps + Send + Sync
at every use site — is viral, and a consumer who forgets it gets a different
type rather than an error at the definition.
Required Methods§
Sourcefn put(&self, pack: &[u8], refs: &[RefUpdate]) -> Result<TxId, Error>
fn put(&self, pack: &[u8], refs: &[RefUpdate]) -> Result<TxId, Error>
One push: pack bytes and ref updates, durable before this returns.
The order inside it is the contract: the pack’s bytes durable first, then the refs that point into them. A crash between the two leaves objects nobody points at (a GC reclaims them); the reverse order leaves a ref pointing at objects that are not there, which no later pass repairs.
Sourcefn put_refs(&self, updates: &[RefUpdate]) -> Result<TxId, Error>
fn put_refs(&self, updates: &[RefUpdate]) -> Result<TxId, Error>
The ref half of put, on its own.
Sourcefn has(&self, oid: &[u8]) -> Result<bool, Error>
fn has(&self, oid: &[u8]) -> Result<bool, Error>
Is this object here? The negotiation call, made a thousand at a time.
Sourcefn extents(&self, oids: &[&[u8]]) -> Result<Vec<Option<(u64, u64)>>, Error>
fn extents(&self, oids: &[&[u8]]) -> Result<Vec<Option<(u64, u64)>>, Error>
Byte extents, in bulk — the wire path.
Sourcefn update_ref(
&self,
name: &str,
old: Option<&[u8]>,
new: Option<&[u8]>,
) -> Result<TxId, Error>
fn update_ref( &self, name: &str, old: Option<&[u8]>, new: Option<&[u8]>, ) -> Result<TxId, Error>
Compare-and-swap one ref.
Sourcefn put_refs_cas(&self, edits: &[RefCas<'_>]) -> Result<TxId, Error>
fn put_refs_cas(&self, edits: &[RefCas<'_>]) -> Result<TxId, Error>
Apply every edit or none. The git push --atomic primitive.
The third shape, not a replacement: put_refs (batch,
no CAS) and update_ref (CAS, one ref) both stay,
and both have callers that want exactly what they are.
Returns Err naming the first edit whose old did not match, and
applies nothing. That error carries a RefRejection, recoverable
with RefRejection::of — receive-pack must be able to tell a client
which ref lost the race and what was there instead, and it must never do
that by reading a message.
§Every expectation is checked BEFORE anything is applied — S-023
Not a stylistic preference; it is the one implementation note this method
carries, and it is a real defect found in a real backend. gix’s file ref
store short-circuits an edit whose new value equals the value the
reference already holds: it rewrites the expectation to
MustExistAndMatch(current) and never evaluates the one the caller
wrote. For an old: None — must not exist — that turns a create that
must fail into a silent success, so “exactly one creator wins”, the
property receive-pack arbitrates two racing pushes with, was not true on
the only backend that survives a restart.
So an implementation compares against one snapshot of the namespace, taken once, before the batch reaches the backend’s own writer.
§There is no third state
An empty batch is a no-op that succeeds, not an error: a deletions-free push with nothing to apply calls this, and refusing it would make the caller special-case the empty case at every site.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".