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 and
9//! check it with the `grit_test_support::refstore_conformance` suite.
10
11mod apply;
12pub mod backend;
13mod error;
14mod files;
15mod linked_worktree;
16mod memory;
17mod open;
18pub mod paths;
19mod reftable;
20mod routing;
21mod semantics;
22mod transaction;
23mod types;
24mod validation;
25
26pub use error::{BackendError, RefStoreError};
27pub use files::{FilesRefStore, FilesRefStoreConfig};
28pub(crate) use linked_worktree::LinkedWorktreeRefStore;
29pub use memory::MemoryRefStore;
30pub use open::open_ref_store;
31pub(crate) use open::{initialize_head, open_files_ref_store, open_ref_store_uncached};
32pub use reftable::ReftableRefStore;
33pub(crate) use semantics::format_reflog_identity;
34pub use transaction::RefTransaction;
35pub use types::{
36    Expected, RawRef, RefCaseOnlyRename, RefEntry, RefName, RefReflogRename, RefStorageFormat,
37    RefUpdate, RefUpdateFlags, ReflogUpdate,
38};
39pub(crate) use validation::{
40    validate_reflog_identity_string, validate_reflog_message, validate_reflog_update,
41};
42
43use std::fmt::Debug;
44use std::ops::ControlFlow;
45
46use crate::error::Result;
47use crate::objects::ObjectId;
48use crate::reflog::ReflogEntry;
49use crate::refs::SYMREF_MAXDEPTH;
50
51type StoreResult<T> = std::result::Result<T, RefStoreError>;
52
53/// Committable ref update batch returned from [`RefStore::prepare`].
54///
55/// The value owns the locks `prepare` took. Exactly one of three things releases them:
56/// [`Self::commit`], [`Self::abort`], or dropping the value. Implementations must release
57/// their locks in `Drop` when neither method ran (callers bail out with `?` between prepare
58/// and commit), leaving stored refs unchanged, so the next `prepare` does not fail with
59/// [`RefStoreError::LockHeld`].
60pub trait PreparedRefTransaction: Send {
61    /// Apply queued updates and release locks.
62    ///
63    /// Every update becomes visible to readers, or (on error) none does where the backend
64    /// can guarantee it.
65    ///
66    /// # Errors
67    ///
68    /// Returns [`RefStoreError`] when the batch can no longer be applied.
69    fn commit(self: Box<Self>) -> StoreResult<()>;
70
71    /// Release locks without changing stored refs.
72    ///
73    /// # Errors
74    ///
75    /// Propagates backend errors while releasing resources.
76    fn abort(self: Box<Self>) -> StoreResult<()>;
77}
78
79/// Reference storage backend (loose/packed files, reftable, or in-memory).
80///
81/// # Re-entrant callbacks
82///
83/// The callbacks passed to [`Self::for_each_ref`], [`Self::for_each_reflog_entry`] and
84/// [`Self::for_each_reflog_ref`] may call back into the same store, both to read (resolve a
85/// symref, read each listed ref's reflog) and to write. The set of names to visit is fixed when
86/// the walk starts, so a write made from the callback may not be seen by the walk in progress.
87/// Implementations must not hold internal locks while a callback runs.
88///
89/// # Implementing a backend
90///
91/// Refs are full names (`HEAD`, `refs/heads/main`, …) mapped to a [`RawRef`]. Reads never
92/// resolve symbolic refs except in [`Self::resolve`] and [`RefEntry::peeled`]. Writes only go
93/// through [`Self::prepare`]; see its documentation for the checks it must make. The
94/// [`backend`] module implements those checks and the commit semantics, and the
95/// `grit_test_support::refstore_conformance` suite verifies a store agrees with the built-in
96/// backends. Report failures of your own storage as [`RefStoreError::Backend`] (or
97/// [`crate::error::Error::RefStore`] wrapping it from the `Result` methods).
98pub trait RefStore: Send + Sync + Debug {
99    /// Which on-disk or logical format this store uses.
100    ///
101    /// Custom backends return [`RefStorageFormat::Custom`].
102    fn format(&self) -> RefStorageFormat;
103
104    /// Read the stored value for `name` without resolving symbolic targets.
105    ///
106    /// A missing ref returns `Ok(None)` without allocating a new name string.
107    fn read_raw(&self, name: &str) -> Result<Option<RawRef>>;
108
109    /// Resolve `name` to an object id, following symbolic refs.
110    ///
111    /// Default implementation uses [`Self::read_raw`] and stops after
112    /// [`SYMREF_MAXDEPTH`] hops.
113    ///
114    /// # Errors
115    ///
116    /// Returns [`RefStoreError::SymrefLoop`] when the chain is too deep and
117    /// [`RefStoreError::NotFound`] when `name` or a symbolic target is missing.
118    fn resolve(&self, name: &str) -> Result<ObjectId> {
119        resolve_store(self, name)
120    }
121
122    /// Invoke `f` for each ref whose name starts with `prefix`, in sorted name order.
123    ///
124    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
125    fn for_each_ref(
126        &self,
127        prefix: &str,
128        f: &mut dyn FnMut(&RefEntry) -> ControlFlow<()>,
129    ) -> Result<()>;
130
131    /// Validate `txn`, take locks, and return a committable batch.
132    ///
133    /// Every check runs here, before anything is written, and a failure leaves the store
134    /// unchanged and unlocked. A store must:
135    ///
136    /// 1. Before taking locks, reject names Git refuses to store (an update name or either
137    ///    side of a reflog or case-only rename) with [`RefStoreError::InvalidRefName`], an
138    ///    update other than a reflog-only one that would store the null object id with
139    ///    [`RefStoreError::NullOid`], and malformed reflog identity or message fields with
140    ///    [`RefStoreError::InvalidReflog`].
141    /// 2. While another prepared transaction holds the lock, fail with
142    ///    [`RefStoreError::LockHeld`] (do not wait indefinitely).
143    /// 3. Reject a created ref whose name is a directory prefix of a stored ref or of
144    ///    another ref in the batch, or the other way round (`a` vs `a/b`), with
145    ///    [`RefStoreError::NameUnavailable`]. Deletes in the same batch do not free a name.
146    /// 4. Check each [`RefUpdate::expected`] against the state left by the earlier updates
147    ///    in the batch ([`RefStoreError::ExpectedMismatch`]). For a direct-value update
148    ///    through a symbolic ref, an [`Expected::Oid`] is compared with the resolved id.
149    /// 5. Reject two updates that write the same stored ref, including through symbolic
150    ///    refs, with [`RefStoreError::DuplicateUpdate`].
151    ///
152    /// [`backend::check_transaction`] performs steps 1 and 3–5 and
153    /// [`backend::apply_transaction`] defines what [`PreparedRefTransaction::commit`] stores,
154    /// including reflog lines.
155    fn prepare(
156        &self,
157        txn: RefTransaction,
158    ) -> StoreResult<Box<dyn PreparedRefTransaction + Send + '_>>;
159
160    /// Whether a reflog is present for `name`.
161    fn reflog_exists(&self, name: &str) -> Result<bool>;
162
163    /// Iterate reflog entries for `name` (oldest first unless `reverse`).
164    ///
165    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
166    fn for_each_reflog_entry(
167        &self,
168        name: &str,
169        reverse: bool,
170        f: &mut dyn FnMut(&ReflogEntry) -> ControlFlow<()>,
171    ) -> Result<()>;
172
173    /// Create an empty reflog file/record for `name`.
174    fn create_reflog(&self, name: &str) -> Result<()>;
175
176    /// Remove the reflog for `name`.
177    fn delete_reflog(&self, name: &str) -> Result<()>;
178
179    /// Replace the entire reflog (used by expire/delete-entries).
180    fn replace_reflog(&self, name: &str, entries: Vec<ReflogEntry>) -> Result<()>;
181
182    /// Invoke `f` for each ref name that has a reflog, in sorted order.
183    ///
184    /// `f` may call back into the store; see [re-entrant callbacks](Self#re-entrant-callbacks).
185    fn for_each_reflog_ref(&self, f: &mut dyn FnMut(&str) -> ControlFlow<()>) -> Result<()>;
186
187    /// The files backend behind this store, when it is one.
188    ///
189    /// Lets callers reuse state the files backend keeps between calls (such as its
190    /// `packed-refs` snapshot) instead of reading the same files again. Other backends,
191    /// including injected ones, return `None`.
192    fn as_files(&self) -> Option<&FilesRefStore> {
193        None
194    }
195}
196
197/// Resolve `name` through `store`, following symbolic refs.
198pub fn resolve_store<S: RefStore + ?Sized>(store: &S, name: &str) -> Result<ObjectId> {
199    resolve_via_raw(store, name, 0).map_err(Into::into)
200}
201
202fn resolve_via_raw<S: RefStore + ?Sized>(
203    store: &S,
204    name: &str,
205    depth: usize,
206) -> StoreResult<ObjectId> {
207    if depth >= SYMREF_MAXDEPTH {
208        return Err(RefStoreError::SymrefLoop);
209    }
210    match store.read_raw(name).map_err(|err| match err {
211        crate::error::Error::RefStore(err) => err,
212        other => RefStoreError::Corrupt(other.to_string()),
213    })? {
214        None => Err(RefStoreError::not_found(name)),
215        Some(RawRef::Direct(oid)) => Ok(oid),
216        Some(RawRef::Symbolic(target)) => resolve_via_raw(store, &target, depth + 1),
217    }
218}