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