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