1use crate::{ObjectId, Result, StorageError};
4use parking_lot::RwLock;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub enum Reference {
10 Direct(ObjectId),
12 Symbolic(String),
14}
15
16impl Reference {
17 pub fn resolve(&self, store: &RefStore) -> Result<ObjectId> {
19 match self {
20 Self::Direct(id) => Ok(*id),
21 Self::Symbolic(target) => {
22 let target_ref = store.get(target)?;
23 target_ref.resolve(store)
24 }
25 }
26 }
27
28 pub fn as_direct(&self) -> Option<ObjectId> {
30 match self {
31 Self::Direct(id) => Some(*id),
32 Self::Symbolic(_) => None,
33 }
34 }
35}
36
37#[derive(Debug, Default)]
39pub struct RefStore {
40 refs: RwLock<HashMap<String, Reference>>,
41}
42
43impl RefStore {
44 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn get(&self, name: &str) -> Result<Reference> {
51 self.refs
52 .read()
53 .get(name)
54 .cloned()
55 .ok_or_else(|| StorageError::RefNotFound(name.to_string()))
56 }
57
58 pub fn set(&self, name: &str, target: ObjectId) {
60 self.refs
61 .write()
62 .insert(name.to_string(), Reference::Direct(target));
63 }
64
65 pub fn set_symbolic(&self, name: &str, target: &str) {
67 self.refs
68 .write()
69 .insert(name.to_string(), Reference::Symbolic(target.to_string()));
70 }
71
72 pub fn delete(&self, name: &str) -> Result<()> {
74 self.refs
75 .write()
76 .remove(name)
77 .map(|_| ())
78 .ok_or_else(|| StorageError::RefNotFound(name.to_string()))
79 }
80
81 pub fn list(&self, prefix: &str) -> Vec<(String, Reference)> {
83 self.refs
84 .read()
85 .iter()
86 .filter(|(name, _)| name.starts_with(prefix))
87 .map(|(name, refr)| (name.clone(), refr.clone()))
88 .collect()
89 }
90
91 pub fn list_all(&self) -> Vec<(String, Reference)> {
93 self.refs
94 .read()
95 .iter()
96 .map(|(name, refr)| (name.clone(), refr.clone()))
97 .collect()
98 }
99
100 pub fn resolve_head(&self) -> Result<ObjectId> {
102 let head = self.get("HEAD")?;
103 match head {
104 Reference::Direct(id) => Ok(id),
105 Reference::Symbolic(target) => {
106 let target_ref = self.get(&target)?;
107 match target_ref {
108 Reference::Direct(id) => Ok(id),
109 Reference::Symbolic(_) => Err(StorageError::InvalidRef(
110 "deeply nested symbolic refs not supported".to_string(),
111 )),
112 }
113 }
114 }
115 }
116
117 pub fn current_branch(&self) -> Option<String> {
119 match self.get("HEAD").ok()? {
120 Reference::Symbolic(target) => {
121 target.strip_prefix("refs/heads/").map(|s| s.to_string())
122 }
123 Reference::Direct(_) => None,
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_ref_store_basic() {
134 let store = RefStore::new();
135 let id = ObjectId::from_hex("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3").unwrap();
136
137 store.set("refs/heads/main", id);
138 store.set_symbolic("HEAD", "refs/heads/main");
139
140 assert_eq!(store.current_branch(), Some("main".to_string()));
141
142 let resolved = store.resolve_head().unwrap();
143 assert_eq!(resolved.to_hex(), id.to_hex());
144 }
145
146 #[test]
147 fn test_ref_listing() {
148 let store = RefStore::new();
149 let id = ObjectId::from_hex("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3").unwrap();
150
151 store.set("refs/heads/main", id);
152 store.set("refs/heads/feature", id);
153 store.set("refs/tags/v1.0", id);
154
155 let heads = store.list("refs/heads/");
156 assert_eq!(heads.len(), 2);
157
158 let tags = store.list("refs/tags/");
159 assert_eq!(tags.len(), 1);
160 }
161
162 #[test]
163 fn test_ref_store_get_not_found() {
164 let store = RefStore::new();
165 let result = store.get("refs/heads/nonexistent");
166 assert!(matches!(result, Err(StorageError::RefNotFound(_))));
167 }
168
169 #[test]
170 fn test_ref_store_delete() {
171 let store = RefStore::new();
172 let id = ObjectId::from_bytes([1u8; 20]);
173
174 store.set("refs/heads/feature", id);
175 assert!(store.get("refs/heads/feature").is_ok());
176
177 store.delete("refs/heads/feature").unwrap();
178 assert!(store.get("refs/heads/feature").is_err());
179 }
180
181 #[test]
182 fn test_ref_store_delete_not_found() {
183 let store = RefStore::new();
184 let result = store.delete("refs/heads/nonexistent");
185 assert!(matches!(result, Err(StorageError::RefNotFound(_))));
186 }
187
188 #[test]
189 fn test_ref_store_list_all() {
190 let store = RefStore::new();
191 let id = ObjectId::from_bytes([1u8; 20]);
192
193 store.set("refs/heads/main", id);
194 store.set("refs/heads/feature", id);
195 store.set("refs/tags/v1.0", id);
196 store.set_symbolic("HEAD", "refs/heads/main");
197
198 let all = store.list_all();
199 assert_eq!(all.len(), 4);
200 }
201
202 #[test]
203 fn test_reference_direct() {
204 let id = ObjectId::from_bytes([1u8; 20]);
205 let reference = Reference::Direct(id);
206
207 assert_eq!(reference.as_direct(), Some(id));
208 }
209
210 #[test]
211 fn test_reference_symbolic() {
212 let reference = Reference::Symbolic("refs/heads/main".to_string());
213
214 assert!(reference.as_direct().is_none());
215 }
216
217 #[test]
218 fn test_reference_resolve_direct() {
219 let store = RefStore::new();
220 let id = ObjectId::from_bytes([1u8; 20]);
221
222 let reference = Reference::Direct(id);
223 let resolved = reference.resolve(&store).unwrap();
224
225 assert_eq!(resolved, id);
226 }
227
228 #[test]
229 fn test_reference_resolve_symbolic() {
230 let store = RefStore::new();
231 let id = ObjectId::from_bytes([1u8; 20]);
232
233 store.set("refs/heads/main", id);
234
235 let reference = Reference::Symbolic("refs/heads/main".to_string());
236 let resolved = reference.resolve(&store).unwrap();
237
238 assert_eq!(resolved, id);
239 }
240
241 #[test]
242 fn test_resolve_head_direct() {
243 let store = RefStore::new();
244 let id = ObjectId::from_bytes([1u8; 20]);
245
246 store.set("HEAD", id);
247
248 let resolved = store.resolve_head().unwrap();
249 assert_eq!(resolved, id);
250 }
251
252 #[test]
253 fn test_resolve_head_symbolic() {
254 let store = RefStore::new();
255 let id = ObjectId::from_bytes([1u8; 20]);
256
257 store.set("refs/heads/main", id);
258 store.set_symbolic("HEAD", "refs/heads/main");
259
260 let resolved = store.resolve_head().unwrap();
261 assert_eq!(resolved, id);
262 }
263
264 #[test]
265 fn test_resolve_head_not_found() {
266 let store = RefStore::new();
267 let result = store.resolve_head();
268 assert!(result.is_err());
269 }
270
271 #[test]
272 fn test_resolve_head_dangling_symbolic() {
273 let store = RefStore::new();
274 store.set_symbolic("HEAD", "refs/heads/nonexistent");
275
276 let result = store.resolve_head();
277 assert!(result.is_err());
278 }
279
280 #[test]
281 fn test_current_branch_with_direct_head() {
282 let store = RefStore::new();
283 let id = ObjectId::from_bytes([1u8; 20]);
284
285 store.set("HEAD", id);
286
287 assert!(store.current_branch().is_none());
288 }
289
290 #[test]
291 fn test_current_branch_feature() {
292 let store = RefStore::new();
293 store.set_symbolic("HEAD", "refs/heads/feature-branch");
294
295 assert_eq!(store.current_branch(), Some("feature-branch".to_string()));
296 }
297
298 #[test]
299 fn test_ref_update() {
300 let store = RefStore::new();
301 let id1 = ObjectId::from_bytes([1u8; 20]);
302 let id2 = ObjectId::from_bytes([2u8; 20]);
303
304 store.set("refs/heads/main", id1);
305 assert_eq!(store.get("refs/heads/main").unwrap().as_direct(), Some(id1));
306
307 store.set("refs/heads/main", id2);
308 assert_eq!(store.get("refs/heads/main").unwrap().as_direct(), Some(id2));
309 }
310
311 #[test]
312 fn test_ref_list_empty_prefix() {
313 let store = RefStore::new();
314 let id = ObjectId::from_bytes([1u8; 20]);
315
316 store.set("refs/heads/main", id);
317 store.set("refs/tags/v1", id);
318
319 let all = store.list("");
320 assert_eq!(all.len(), 2);
321 }
322
323 #[test]
324 fn test_ref_list_no_matches() {
325 let store = RefStore::new();
326 let id = ObjectId::from_bytes([1u8; 20]);
327
328 store.set("refs/heads/main", id);
329
330 let remotes = store.list("refs/remotes/");
331 assert!(remotes.is_empty());
332 }
333
334 #[test]
335 fn test_reference_clone() {
336 let id = ObjectId::from_bytes([1u8; 20]);
337 let original = Reference::Direct(id);
338 let cloned = original.clone();
339
340 assert_eq!(original.as_direct(), cloned.as_direct());
341 }
342
343 #[test]
344 fn test_symbolic_reference_update() {
345 let store = RefStore::new();
346 let id = ObjectId::from_bytes([1u8; 20]);
347
348 store.set("refs/heads/main", id);
349 store.set("refs/heads/feature", id);
350 store.set_symbolic("HEAD", "refs/heads/main");
351
352 assert_eq!(store.current_branch(), Some("main".to_string()));
353
354 store.set_symbolic("HEAD", "refs/heads/feature");
355
356 assert_eq!(store.current_branch(), Some("feature".to_string()));
357 }
358
359 #[test]
360 fn test_ref_store_default() {
361 let store: RefStore = Default::default();
362 assert!(store.list_all().is_empty());
363 }
364
365 #[test]
366 fn test_current_branch_non_heads() {
367 let store = RefStore::new();
368 store.set_symbolic("HEAD", "refs/remotes/origin/main");
370
371 assert!(store.current_branch().is_none());
372 }
373}