Skip to main content

GitOps

Trait GitOps 

Source
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§

Source

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.

Source

fn put_pack(&self, bytes: &[u8]) -> Result<TxId, Error>

The pack half of put, on its own.

Source

fn put_refs(&self, updates: &[RefUpdate]) -> Result<TxId, Error>

The ref half of put, on its own.

Source

fn get(&self, oid: &[u8]) -> Result<Option<Stored>, Error>

The stored bytes of one object.

Source

fn has(&self, oid: &[u8]) -> Result<bool, Error>

Is this object here? The negotiation call, made a thousand at a time.

Source

fn size(&self, oid: &[u8]) -> Result<Option<u64>, Error>

Post-resolution size.

Source

fn extents(&self, oids: &[&[u8]]) -> Result<Vec<Option<(u64, u64)>>, Error>

Byte extents, in bulk — the wire path.

Source

fn refs(&self) -> Result<Vec<RefRow>, Error>

The current ref namespace.

Source

fn update_ref( &self, name: &str, old: Option<&[u8]>, new: Option<&[u8]>, ) -> Result<TxId, Error>

Compare-and-swap one ref.

Source

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: Nonemust 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.

Source

fn reachable( &self, want: &[&[u8]], have: &[&[u8]], ) -> Result<Vec<Vec<u8>>, Error>

want minus have, the object closure a fetch must send.

Source

fn gc(&self) -> Result<GcReport, Error>

Reachability, drop the dead index rows, then compact.

Dyn Compatibility§

This trait is dyn compatible.

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

Implementors§