git-ents.gitmain
⌘K
foforge
lib.rs83 lines · 3.5 KB · rusthistorycomment on this file
1//! The pluggable ref store: reads plus atomic multi-ref compare-and-swap,
2//! and a loose-ref implementation over gitoxide.
3//!
4//! This crate is the one place `git-ents` defines a trait gitoxide itself
5//! is silent about (`arch.no-object-store-trait` names the ref store as
6//! one of the seams that qualifies). It owns two things: the `RefStore`
7//! trait, split into a read half ([`RefStoreRead`]) and a write half
8//! ([`RefStore`]) per `arch.refstore-read-cas-split`, and
9//! [`LooseRefStore`], the local default backend, which writes through
10//! gitoxide's own in-process ref transaction rather than shelling out to
11//! `git update-ref` (`arch.loose-cas-discipline`).
12//!
13//! The split exists for the gate (`gate.adoc`): verification is a pure
14//! function over ref-store reads and must be statically incapable of
15//! performing a write, so it is written against `RefStoreRead` alone.
16//!
17//! `LooseRefStore` delegates the mechanics of a write (the loose-file
18//! format, reflog, packed-refs interaction) to gitoxide, but layers its
19//! own serialization lock around every `transaction()` call — see the
20//! `loose` module's doc comment for why: the pinned gitoxide version's
21//! file-transaction precondition check reads a ref's value *before*
22//! acquiring that ref's own lock, which is safe only when every writer
23//! already funnels through one in-process handle. Two independent
24//! `gix::Repository` handles racing the same ref (two `git-ents`
25//! processes, most concretely) can otherwise both observe the same stale
26//! precondition and both "win" a `MustNotExist`/`MustExistAndMatch` check.
27//! `arch.loose-cas-discipline` asks for this store's *own* CAS discipline
28//! for exactly this reason; `LooseRefStore` earns that literally rather
29//! than trusting gitoxide's internal ordering to be enough on its own.
30//!
31//! # Spec coverage
32//!
33//! This crate implements, from `docs/spec/overview.sdoc`:
34//!
35//! - `arch.refstore-read-cas-split` — the `RefStoreRead`/`RefStore` split.
36//! - `arch.loose-cas-discipline` — [`LooseRefStore`]'s use of gitoxide's
37//! own transaction machinery instead of a `git update-ref` subprocess.
38//! - `arch.no-object-store-trait` — this crate defines exactly one new
39//! trait (the ref store), and touches object access only through
40//! gitoxide's own types.
41//!
42//! # Examples
43//!
44//! ```
45//! use gix_hash::ObjectId;
46//! use gix_ref_store::{Expected, LooseRefStore, RefEdit, RefStore, RefStoreRead, TxOutcome};
47//!
48//! # fn main() -> gix_ref_store::Result<()> {
49//! let dir = tempfile::tempdir().expect("tempdir");
50//! gix::init(dir.path()).expect("init");
51//! let store = LooseRefStore::open(dir.path())?;
52//!
53//! let name: gix::refs::FullName = "refs/meta/config".try_into().expect("valid refname");
54//! let oid = ObjectId::null(gix_hash::Kind::Sha1);
55//!
56//! // The read half alone is enough to observe the ref not existing yet —
57//! // exactly what the gate is handed.
58//! let read: &dyn RefStoreRead = &store;
59//! assert_eq!(read.get(name.as_ref())?, None);
60//!
61//! // Only the write half can change it, and only via CAS.
62//! let outcome = store.transaction(&[RefEdit {
63//! name: name.clone(),
64//! expected: Expected::MustNotExist,
65//! new: Some(oid),
66//! }])?;
67//! assert_eq!(outcome, TxOutcome::Applied);
68//! assert_eq!(store.get(name.as_ref())?, Some(oid));
69//! # Ok(())
70//! # }
71//! ```
72
73mod edit;
74mod error;
75mod loose;
76mod read;
77mod store;
78
79pub use edit::{Expected, RefEdit, RefIter, TxOutcome};
80pub use error::{Error, Result};
81pub use loose::LooseRefStore;
82pub use read::RefStoreRead;
83pub use store::RefStore;