Skip to main content

grit_lib/refs/store/
mod.rs

1//! Pluggable reference storage: typed transactions and backends.
2//!
3//! The [`RefStore`] trait models Git's ref backend vtable (prepare/commit with
4//! locking, raw reads, iteration, and reflog helpers). [`MemoryRefStore`] is an
5//! embeddable in-memory implementation used by tests and callers that keep refs
6//! outside a repository directory.
7//!
8//! To write your own backend, build `prepare` and `commit` on the [`backend`] helpers,
9//! implement [`RefStore::rewrite_reflog`] with the same writer exclusion as `prepare`/`commit`,
10//! and check it with the `grit_test_support::refstore_conformance` suite.
11
12mod apply;
13pub mod backend;
14mod checked;
15mod error;
16mod files;
17pub(crate) mod gate;
18mod linked_worktree;
19mod memory;
20mod open;
21pub mod paths;
22pub(crate) mod reference_transaction;
23mod reftable;
24mod routing;
25mod semantics;
26mod transaction;
27mod types;
28mod validation;
29
30pub(crate) use checked::{open_checked_ref_store, ObjectCheckedRefStore};
31pub use error::{BackendError, RefStoreError};
32pub use files::{FilesRefStore, FilesRefStoreConfig};
33pub use gate::{SingleWriterGate, SingleWriterTicket};
34pub(crate) use linked_worktree::LinkedWorktreeRefStore;
35pub use memory::MemoryRefStore;
36pub use open::open_ref_store;
37pub(crate) use open::{initialize_head, open_files_ref_store, open_ref_store_uncached};
38pub use reftable::ReftableRefStore;
39pub(crate) use semantics::format_reflog_identity;
40pub use transaction::RefTransaction;
41pub use types::{
42    Expected, RawRef, RefCaseOnlyRename, RefEntry, RefName, RefReflogRename, RefStorageFormat,
43    RefUpdate, RefUpdateFlags, ReflogUpdate,
44};
45pub(crate) use validation::{
46    validate_reflog_identity_string, validate_reflog_message, validate_reflog_update,
47};
48
49use std::fmt::Debug;
50use std::ops::ControlFlow;
51
52use crate::error::Result;
53use crate::lockfile::LockWait;
54use crate::objects::ObjectId;
55use crate::reflog::ReflogEntry;
56use crate::refs::SYMREF_MAXDEPTH;
57
58type StoreResult<T> = std::result::Result<T, RefStoreError>;
59
60/// Committable ref update batch returned from [`RefStore::prepare`].
61///
62/// The value owns the locks `prepare` took. Exactly one of three things releases them:
63/// [`Self::commit`], [`Self::abort`], or dropping the value. Implementations must release
64/// their locks in `Drop` when neither method ran (callers bail out with `?` between prepare
65/// and commit), leaving stored refs unchanged, so the next `prepare` does not fail with
66/// [`RefStoreError::LockHeld`].
67pub trait PreparedRefTransaction: Send {
68    /// Apply queued updates and release locks.
69    ///
70    /// Every update becomes visible to readers, or (on error) none does where the backend
71    /// can guarantee it. On the files backend, a compare-and-swap failure writes neither the
72    /// ref nor its reflog entry.
73    ///
74    /// # Errors
75    ///
76    /// Returns [`RefStoreError`] when the batch can no longer be applied.
77    fn commit(self: Box<Self>) -> StoreResult<()>;
78
79    /// Updates [`RefTransaction::allowing_failures`] skipped during [`Self::commit`]'s prepare.
80    ///
81    /// Empty when the transaction was not prepared with [`RefTransaction::allowing_failures`].
82    fn rejected_updates(&self) -> &[(String, RefStoreError)] {
83        &[]
84    }
85
86    /// Release locks without changing stored refs.
87    ///
88    /// # Errors
89    ///
90    /// Propagates backend errors while releasing resources.
91    fn abort(self: Box<Self>) -> StoreResult<()>;
92}
93
94/// Reference storage backend (loose/packed files, reftable, or in-memory).
95///
96/// # Re-entrant callbacks
97///
98/// The callbacks passed to [`Self::for_each_ref`], [`Self::for_each_reflog_entry`] and
99/// [`Self::for_each_reflog_ref`] may call back into the same store, both to read (resolve a
100/// symref, read each listed ref's reflog) and to write. The set of names to visit is fixed when
101/// the walk starts, so a write made from the callback may not be seen by the walk in progress.
102/// Implementations must not hold internal locks while a callback runs.
103///
104/// # Implementing a backend
105///
106/// Refs are full names (`HEAD`, `refs/heads/main`, …) mapped to a [`RawRef`]. Reads never
107/// resolve symbolic refs except in [`Self::resolve`] and [`RefEntry::peeled`]. Writes only go
108/// through [`Self::prepare`]; see its documentation for the checks it must make. The
109/// [`backend`] module implements those checks and the commit semantics, and the
110/// `grit_test_support::refstore_conformance` suite verifies a store agrees with the built-in
111/// backends. You must implement [`Self::rewrite_reflog`] with the same writer exclusion as
112/// `prepare`/`commit` for that ref; the suite includes a case that fails unsafe read-transform-write
113/// paths. Report failures of your own storage as [`RefStoreError::Backend`] (or
114/// [`crate::error::Error::RefStore`] wrapping it from the `Result` methods).
115pub trait RefStore: Send + Sync + Debug {
116    /// Which on-disk or logical format this store uses.
117    ///
118    /// Custom backends return [`RefStorageFormat::Custom`].
119    fn format(&self) -> RefStorageFormat;
120
121    /// Read the stored value for `name` without resolving symbolic targets.
122    ///
123    /// A missing ref returns `Ok(None)` without allocating a new name string.
124    fn read_raw(&self, name: &str) -> Result<Option<RawRef>>;
125
126    /// Resolve `name` to an object id, following symbolic refs.
127    ///
128    /// Default implementation uses [`Self::read_raw`] and stops after
129    /// [`SYMREF_MAXDEPTH`] hops.
130    ///
131    /// # Errors
132    ///
133    /// Returns [`RefStoreError::SymrefLoop`] when the chain is too deep and
134    /// [`RefStoreError::NotFound`] when `name` or a symbolic target is missing.
135    fn resolve(&self, name: &str) -> Result<ObjectId> {
136        resolve_store(self, name)
137    }
138
139    /// Invoke `f` for each ref whose name starts with `prefix`, in sorted name order.
140    ///
141    /// A non-empty `prefix` is a literal name prefix, so `"H"` or `"HEAD"` lists `HEAD` and
142    /// `"ORIG"` lists `ORIG_HEAD`. An empty `prefix` lists only the refs under `refs/`, like
143    /// `git for-each-ref` with no pattern: root refs such as `HEAD` and `ORIG_HEAD` are left
144    /// out and stay reachable through [`Self::read_raw`] or a non-empty prefix they match.
145    ///
146    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
147    fn for_each_ref(
148        &self,
149        prefix: &str,
150        f: &mut dyn FnMut(&RefEntry) -> ControlFlow<()>,
151    ) -> Result<()>;
152
153    /// Walk refs stored in this handle's physical reftable stack (`reftable/` under the
154    /// store git directory). On the files backend this matches [`Self::for_each_ref`].
155    fn for_each_physical_stack_ref(
156        &self,
157        prefix: &str,
158        f: &mut dyn FnMut(&RefEntry) -> ControlFlow<()>,
159    ) -> Result<()> {
160        self.for_each_ref(prefix, f)
161    }
162
163    /// Validate `txn`, take locks, and return a committable batch.
164    ///
165    /// Every check runs here, before anything is written, and a failure leaves the store
166    /// unchanged and unlocked. A store must:
167    ///
168    /// 1. Before taking locks, reject names Git refuses to store (an update name or either
169    ///    side of a reflog or case-only rename) with [`RefStoreError::InvalidRefName`], a
170    ///    symbolic value Git refuses with [`RefStoreError::InvalidSymrefTarget`], an update
171    ///    other than a reflog-only one that would store the null object id with
172    ///    [`RefStoreError::NullOid`], and malformed reflog identity or message fields with
173    ///    [`RefStoreError::InvalidReflog`].
174    /// 2. While another writer holds a lock this transaction needs, fail promptly with
175    ///    [`RefStoreError::LockHeld`] naming this transaction's first ref when the caller
176    ///    uses [`Self::prepare`] or [`Self::prepare_for`] with [`LockWait::Immediately`].
177    ///    Cross-process contention (another process holding a ref lock file) should fail fast
178    ///    so grit-lib can retry with jittered backoff for `core.filesRefLockTimeout`. The
179    ///    files backend takes one lock per ref (plus packed-refs when needed), so only the
180    ///    same ref contends in that case. A store with one in-process writer slot for
181    ///    everything must wait inside [`Self::prepare_for`] using [`SingleWriterGate`] (or
182    ///    equivalent FIFO queueing) for the supplied [`LockWait`]; exponential backoff between
183    ///    immediate [`RefStoreError::LockHeld`] responses is not fair and will starve waiters
184    ///    under load. Only return [`RefStoreError::LockHeld`] from `prepare_for` after the
185    ///    wait budget elapses or for immediate attempts while the slot is busy.
186    /// 3. Reject a created ref whose name is a directory prefix of a stored ref or of
187    ///    another ref in the batch, or the other way round (`a` vs `a/b`), with
188    ///    [`RefStoreError::NameUnavailable`]. Deletes in the same batch do not free a name.
189    /// 4. Check each [`RefUpdate::expected`] against the state left by the earlier updates
190    ///    in the batch ([`RefStoreError::ExpectedMismatch`]). For a direct-value update
191    ///    through a symbolic ref, an [`Expected::Oid`] is compared with the resolved id.
192    /// 5. Reject two updates that write the same stored ref, including through symbolic
193    ///    refs, with [`RefStoreError::DuplicateUpdate`].
194    ///
195    /// `backend::check_transaction` performs steps 1 and 3–5 and
196    /// `backend::apply_transaction` defines what [`PreparedRefTransaction::commit`] stores,
197    /// including appended reflog lines and removing a deleted ref's reflog.
198    ///
199    /// A backend does not look at objects. The store
200    /// [`Repository::refs`](crate::repo::Repository::refs) returns (whatever backend it
201    /// wraps, including one injected with
202    /// [`RepositoryBuilder::ref_store`](crate::repo::RepositoryBuilder::ref_store)) adds one
203    /// more check before calling the backend. Each direct value must name an object the
204    /// repository stores ([`RefStoreError::MissingObject`]). A branch (`refs/heads/…`) or
205    /// `HEAD` must name a commit ([`RefStoreError::NotACommit`]). An object that cannot be
206    /// read fails with [`RefStoreError::ObjectLookup`]. Deletes and symbolic values are not
207    /// looked up. Reflog-only updates and explicit reflog object ids are checked like direct
208    /// values. Updates flagged [`RefUpdateFlags::skip_oid_verification`] are not looked up.
209    fn prepare(
210        &self,
211        txn: RefTransaction,
212    ) -> StoreResult<Box<dyn PreparedRefTransaction + Send + '_>>;
213
214    /// Like [`Self::prepare`], waiting up to `wait` for an in-process writer slot.
215    ///
216    /// Backends with one writer slot should block here (typically via [`SingleWriterGate`])
217    /// until the slot is free or `wait` expires. Backends that already wait on lock files
218    /// inside `prepare` may ignore `wait` and delegate to [`Self::prepare`]; grit-lib passes
219    /// a shrinking remaining budget when it retries cross-process [`RefStoreError::LockHeld`].
220    fn prepare_for(
221        &self,
222        txn: RefTransaction,
223        wait: LockWait,
224    ) -> StoreResult<Box<dyn PreparedRefTransaction + Send + '_>> {
225        let _ = wait;
226        self.prepare(txn)
227    }
228
229    /// Check the objects `updates` would point refs at, without taking any lock or writing.
230    ///
231    /// This is the object check [`Self::prepare`] makes on the store
232    /// [`Repository::refs`](crate::repo::Repository::refs) returns. Writers that update a ref
233    /// without a transaction (the files-backend fast path behind
234    /// [`update_branch_for_commit_on_store`](crate::refs::update_branch_for_commit_on_store))
235    /// call it before locking. Backends do not look at objects, so the default accepts
236    /// everything; there is no need to override it.
237    ///
238    /// # Errors
239    ///
240    /// On the repository's store: [`RefStoreError::MissingObject`],
241    /// [`RefStoreError::NotACommit`] or [`RefStoreError::ObjectLookup`] for the first
242    /// offending update.
243    fn verify_update_targets(&self, updates: &[RefUpdate]) -> StoreResult<()> {
244        let _ = updates;
245        Ok(())
246    }
247
248    /// Hook context when this store runs `reference-transaction` hooks.
249    #[doc(hidden)]
250    fn ref_hook_context(&self) -> Option<&crate::hooks::RefHookContext> {
251        let _ = self;
252        None
253    }
254
255    /// Null object id width for `reference-transaction` stdin on this store.
256    #[doc(hidden)]
257    fn reference_transaction_null_oid(&self) -> ObjectId {
258        crate::diff::zero_oid()
259    }
260
261    /// Whether a reflog is present for `name`.
262    fn reflog_exists(&self, name: &str) -> Result<bool>;
263
264    /// Iterate reflog entries for `name` (oldest first unless `reverse`).
265    ///
266    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
267    fn for_each_reflog_entry(
268        &self,
269        name: &str,
270        reverse: bool,
271        f: &mut dyn FnMut(&ReflogEntry) -> ControlFlow<()>,
272    ) -> Result<()>;
273
274    /// Create an empty reflog file/record for `name`.
275    fn create_reflog(&self, name: &str) -> Result<()>;
276
277    /// Remove the reflog for `name`.
278    fn delete_reflog(&self, name: &str) -> Result<()>;
279
280    /// Replace the entire reflog (used by expire/delete-entries).
281    fn replace_reflog(&self, name: &str, entries: Vec<ReflogEntry>) -> Result<()>;
282
283    /// Read a reflog under the backend writer lock, transform it, and write it back atomically.
284    ///
285    /// Custom backends must implement this method; there is no default. Hold the same writer
286    /// exclusion as [`Self::prepare`] / [`PreparedRefTransaction::commit`] for the whole
287    /// read-transform-write on `name`, pass the locked re-read of entries into `transform`, and
288    /// do not call back into this store from `transform` (that deadlocks backends that hold a
289    /// mutex across the call).
290    ///
291    /// # Errors
292    ///
293    /// Propagates lock, I/O, and transform failures. [`RefStoreError::ReflogRewriteStale`] is
294    /// returned when a caller compares against a snapshot and the locked re-read no longer
295    /// matches (retry the outer operation).
296    fn rewrite_reflog(
297        &self,
298        name: &str,
299        transform: &mut dyn FnMut(Vec<ReflogEntry>) -> Result<Vec<ReflogEntry>>,
300    ) -> Result<()>;
301
302    /// Invoke `f` for each ref name that has a reflog, in sorted order.
303    ///
304    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
305    fn for_each_reflog_ref(&self, f: &mut dyn FnMut(&str) -> ControlFlow<()>) -> Result<()>;
306
307    /// The files backend behind this store, when it is one.
308    ///
309    /// Lets callers reuse state the files backend keeps between calls (such as its
310    /// `packed-refs` snapshot) instead of reading the same files again. Other backends,
311    /// including injected ones, return `None`.
312    fn as_files(&self) -> Option<&FilesRefStore> {
313        None
314    }
315
316    /// How long grit-lib retries [`RefStoreError::LockHeld`] from [`Self::prepare`].
317    ///
318    /// The files backend reads `core.filesRefLockTimeout` from its git directory. Injected
319    /// backends return Git's 100 ms default unless a wrapper (such as the store returned by
320    /// [`crate::repo::Repository::refs`]) overrides this from the repository git directory.
321    fn ref_lock_wait(&self) -> LockWait {
322        self.as_files()
323            .map(|files| LockWait::files_ref_lock_for_git_dir(files.git_dir()))
324            .unwrap_or_else(|| LockWait::files_ref_lock(None))
325    }
326}
327
328/// Resolve `name` through `store`, following symbolic refs.
329pub fn resolve_store<S: RefStore + ?Sized>(store: &S, name: &str) -> Result<ObjectId> {
330    resolve_via_raw(store, name, 0).map_err(Into::into)
331}
332
333fn resolve_via_raw<S: RefStore + ?Sized>(
334    store: &S,
335    name: &str,
336    depth: usize,
337) -> StoreResult<ObjectId> {
338    if depth >= SYMREF_MAXDEPTH {
339        return Err(RefStoreError::SymrefLoop);
340    }
341    match store.read_raw(name).map_err(|err| match err {
342        crate::error::Error::RefStore(err) => err,
343        other => RefStoreError::Corrupt(other.to_string()),
344    })? {
345        None => Err(RefStoreError::not_found(name)),
346        Some(RawRef::Direct(oid)) => Ok(oid),
347        Some(RawRef::Symbolic(target)) => resolve_via_raw(store, &target, depth + 1),
348    }
349}