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}