Skip to main content

panproto_vcs/
store.rs

1//! Storage trait for the VCS object store and ref system.
2//!
3//! The [`Store`] trait abstracts over the backing storage, enabling both
4//! filesystem-backed repositories ([`FsStore`](crate::fs_store::FsStore))
5//! and in-memory stores ([`MemStore`](crate::mem_store::MemStore)) for
6//! testing and WASM.
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::VcsError;
11use crate::hash::ObjectId;
12use crate::object::Object;
13
14/// The state of HEAD.
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum HeadState {
17    /// HEAD points to a branch by name (e.g., `"main"`).
18    Branch(String),
19    /// HEAD is detached, pointing directly at a commit.
20    Detached(ObjectId),
21}
22
23/// A reflog entry recording a ref mutation.
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct ReflogEntry {
26    /// The previous value of the ref (`None` for newly created refs).
27    pub old_id: Option<ObjectId>,
28    /// The new value of the ref.
29    pub new_id: ObjectId,
30    /// Who made the change.
31    pub author: String,
32    /// When the change was made (Unix seconds).
33    pub timestamp: u64,
34    /// A description of the change (e.g., `"commit: add user field"`).
35    pub message: String,
36}
37
38/// Storage backend for the VCS.
39///
40/// Implementations provide content-addressed object storage, named
41/// references (branches and tags), and HEAD management. All mutable
42/// operations take `&mut self`.
43pub trait Store {
44    // -- Objects --
45
46    /// Check whether an object exists in the store.
47    fn has(&self, id: &ObjectId) -> bool;
48
49    /// Retrieve an object by its content-addressed ID.
50    ///
51    /// # Errors
52    ///
53    /// Returns [`VcsError::ObjectNotFound`] if the object does not exist.
54    fn get(&self, id: &ObjectId) -> Result<Object, VcsError>;
55
56    /// Store an object and return its content-addressed ID.
57    ///
58    /// If the object already exists (same hash), this is a no-op that
59    /// returns the existing ID.
60    ///
61    /// # Errors
62    ///
63    /// Returns an error if serialization or I/O fails.
64    fn put(&mut self, object: &Object) -> Result<ObjectId, VcsError>;
65
66    // -- Refs --
67
68    /// Read a named reference (branch or tag).
69    ///
70    /// Returns `None` if the ref does not exist.
71    ///
72    /// # Errors
73    ///
74    /// Returns an error on I/O failure.
75    fn get_ref(&self, name: &str) -> Result<Option<ObjectId>, VcsError>;
76
77    /// Create or update a named reference.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error on I/O failure.
82    fn set_ref(&mut self, name: &str, id: ObjectId) -> Result<(), VcsError>;
83
84    /// Delete a named reference.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`VcsError::RefNotFound`] if the ref does not exist.
89    fn delete_ref(&mut self, name: &str) -> Result<(), VcsError>;
90
91    /// List all references whose names start with `prefix`.
92    ///
93    /// # Errors
94    ///
95    /// Returns an error on I/O failure.
96    fn list_refs(&self, prefix: &str) -> Result<Vec<(String, ObjectId)>, VcsError>;
97
98    // -- HEAD --
99
100    /// Read the current HEAD state.
101    ///
102    /// # Errors
103    ///
104    /// Returns an error on I/O failure.
105    fn get_head(&self) -> Result<HeadState, VcsError>;
106
107    /// Update the HEAD state.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error on I/O failure.
112    fn set_head(&mut self, state: HeadState) -> Result<(), VcsError>;
113
114    // -- Enumeration --
115
116    /// List all object IDs in the store.
117    ///
118    /// Used by garbage collection to find unreachable objects.
119    ///
120    /// # Errors
121    ///
122    /// Returns an error on I/O failure.
123    fn list_objects(&self) -> Result<Vec<ObjectId>, VcsError>;
124
125    /// Delete an object from the store.
126    ///
127    /// # Errors
128    ///
129    /// Returns an error if the object does not exist or I/O fails.
130    fn delete_object(&mut self, id: &ObjectId) -> Result<(), VcsError>;
131
132    // -- Reflog --
133
134    /// Append an entry to a ref's reflog.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error on I/O failure.
139    fn append_reflog(&mut self, ref_name: &str, entry: ReflogEntry) -> Result<(), VcsError>;
140
141    /// Read reflog entries for a ref, newest first.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error on I/O failure.
146    fn read_reflog(
147        &self,
148        ref_name: &str,
149        limit: Option<usize>,
150    ) -> Result<Vec<ReflogEntry>, VcsError>;
151}
152
153/// Resolve HEAD to a commit `ObjectId`.
154///
155/// If HEAD points to a branch, follows the branch ref. Returns `None` if
156/// HEAD points to a branch that has no commits yet (empty repository).
157///
158/// # Errors
159///
160/// Returns an error on I/O failure.
161pub fn resolve_head(store: &dyn Store) -> Result<Option<ObjectId>, VcsError> {
162    match store.get_head()? {
163        HeadState::Branch(name) => {
164            let ref_name = format!("refs/heads/{name}");
165            store.get_ref(&ref_name)
166        }
167        HeadState::Detached(id) => Ok(Some(id)),
168    }
169}