veridian_kernel/devtools/git/
refs.rs1use alloc::{
6 collections::BTreeMap,
7 string::{String, ToString},
8 vec::Vec,
9};
10
11use super::objects::ObjectId;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub(crate) enum RefValue {
16 Direct(ObjectId),
18 Symbolic(String),
20}
21
22#[derive(Debug, Clone)]
24pub(crate) struct Ref {
25 pub(crate) name: String,
26 pub(crate) value: RefValue,
27}
28
29impl Ref {
30 pub(crate) fn direct(name: &str, id: ObjectId) -> Self {
31 Self {
32 name: name.to_string(),
33 value: RefValue::Direct(id),
34 }
35 }
36
37 pub(crate) fn symbolic(name: &str, target: &str) -> Self {
38 Self {
39 name: name.to_string(),
40 value: RefValue::Symbolic(target.to_string()),
41 }
42 }
43
44 pub(crate) fn resolve<'a>(&'a self, store: &'a RefStore) -> Option<&'a ObjectId> {
46 match &self.value {
47 RefValue::Direct(id) => Some(id),
48 RefValue::Symbolic(target) => store.resolve(target),
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub(crate) struct RefStore {
56 refs: BTreeMap<String, RefValue>,
57}
58
59impl Default for RefStore {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl RefStore {
66 pub(crate) fn new() -> Self {
67 let mut refs = BTreeMap::new();
68 refs.insert(
70 "HEAD".to_string(),
71 RefValue::Symbolic("refs/heads/main".to_string()),
72 );
73 Self { refs }
74 }
75
76 pub(crate) fn get(&self, name: &str) -> Option<&RefValue> {
78 self.refs.get(name)
79 }
80
81 pub(crate) fn set_direct(&mut self, name: &str, id: ObjectId) {
83 self.refs.insert(name.to_string(), RefValue::Direct(id));
84 }
85
86 pub(crate) fn set_symbolic(&mut self, name: &str, target: &str) {
88 self.refs
89 .insert(name.to_string(), RefValue::Symbolic(target.to_string()));
90 }
91
92 pub(crate) fn resolve(&self, name: &str) -> Option<&ObjectId> {
94 let mut current = name;
95 let mut depth = 0;
96 loop {
97 if depth > 10 {
98 return None; }
100 match self.refs.get(current)? {
101 RefValue::Direct(id) => return Some(id),
102 RefValue::Symbolic(target) => {
103 current = target;
104 depth += 1;
105 }
106 }
107 }
108 }
109
110 pub(crate) fn head(&self) -> Option<&ObjectId> {
112 self.resolve("HEAD")
113 }
114
115 pub(crate) fn current_branch(&self) -> Option<&str> {
117 match self.refs.get("HEAD")? {
118 RefValue::Symbolic(target) => target.strip_prefix("refs/heads/"),
119 RefValue::Direct(_) => None, }
121 }
122
123 pub(crate) fn branches(&self) -> Vec<String> {
125 self.refs
126 .keys()
127 .filter(|k| k.starts_with("refs/heads/"))
128 .map(|k| k.strip_prefix("refs/heads/").unwrap_or(k).to_string())
129 .collect()
130 }
131
132 pub(crate) fn tags(&self) -> Vec<String> {
134 self.refs
135 .keys()
136 .filter(|k| k.starts_with("refs/tags/"))
137 .map(|k| k.strip_prefix("refs/tags/").unwrap_or(k).to_string())
138 .collect()
139 }
140
141 pub(crate) fn delete(&mut self, name: &str) -> bool {
143 self.refs.remove(name).is_some()
144 }
145
146 pub(crate) fn create_branch(&mut self, name: &str, id: ObjectId) {
148 let ref_name = alloc::format!("refs/heads/{}", name);
149 self.set_direct(&ref_name, id);
150 }
151
152 pub(crate) fn create_tag(&mut self, name: &str, id: ObjectId) {
154 let ref_name = alloc::format!("refs/tags/{}", name);
155 self.set_direct(&ref_name, id);
156 }
157
158 pub(crate) fn checkout_branch(&mut self, name: &str) -> bool {
160 let ref_name = alloc::format!("refs/heads/{}", name);
161 if self.refs.contains_key(&ref_name) {
162 self.set_symbolic("HEAD", &ref_name);
163 true
164 } else {
165 false
166 }
167 }
168
169 pub(crate) fn count(&self) -> usize {
171 self.refs.len()
172 }
173}
174
175#[cfg(test)]
180mod tests {
181 use super::*;
182
183 fn test_oid() -> ObjectId {
184 ObjectId::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709").unwrap()
185 }
186
187 #[test]
188 fn test_ref_store_new() {
189 let store = RefStore::new();
190 assert_eq!(store.current_branch(), Some("main"));
191 }
192
193 #[test]
194 fn test_ref_store_set_get() {
195 let mut store = RefStore::new();
196 store.set_direct("refs/heads/main", test_oid());
197 assert_eq!(store.resolve("refs/heads/main"), Some(&test_oid()));
198 }
199
200 #[test]
201 fn test_ref_store_head() {
202 let mut store = RefStore::new();
203 store.set_direct("refs/heads/main", test_oid());
204 assert_eq!(store.head(), Some(&test_oid()));
205 }
206
207 #[test]
208 fn test_ref_store_branches() {
209 let mut store = RefStore::new();
210 store.create_branch("main", test_oid());
211 store.create_branch("dev", test_oid());
212 let branches = store.branches();
213 assert_eq!(branches.len(), 2);
214 assert!(branches.contains(&"main".to_string()));
215 assert!(branches.contains(&"dev".to_string()));
216 }
217
218 #[test]
219 fn test_ref_store_tags() {
220 let mut store = RefStore::new();
221 store.create_tag("v1.0", test_oid());
222 let tags = store.tags();
223 assert_eq!(tags.len(), 1);
224 assert_eq!(tags[0], "v1.0");
225 }
226
227 #[test]
228 fn test_ref_store_delete() {
229 let mut store = RefStore::new();
230 store.create_branch("temp", test_oid());
231 assert!(store.delete("refs/heads/temp"));
232 assert!(!store.delete("refs/heads/temp"));
233 }
234
235 #[test]
236 fn test_ref_store_checkout() {
237 let mut store = RefStore::new();
238 store.create_branch("main", test_oid());
239 store.create_branch("dev", test_oid());
240
241 assert!(store.checkout_branch("dev"));
242 assert_eq!(store.current_branch(), Some("dev"));
243
244 assert!(!store.checkout_branch("nonexistent"));
245 }
246
247 #[test]
248 fn test_symbolic_ref_chain() {
249 let mut store = RefStore::new();
250 store.set_direct("refs/heads/main", test_oid());
251 assert_eq!(store.resolve("HEAD"), Some(&test_oid()));
253 }
254
255 #[test]
256 fn test_detached_head() {
257 let mut store = RefStore::new();
258 store.set_direct("HEAD", test_oid());
259 assert_eq!(store.current_branch(), None);
260 assert_eq!(store.head(), Some(&test_oid()));
261 }
262
263 #[test]
264 fn test_ref_value_eq() {
265 let a = RefValue::Direct(test_oid());
266 let b = RefValue::Direct(test_oid());
267 assert_eq!(a, b);
268 }
269
270 #[test]
271 fn test_ref_direct() {
272 let r = Ref::direct("refs/heads/main", test_oid());
273 assert_eq!(r.name, "refs/heads/main");
274 match &r.value {
275 RefValue::Direct(id) => assert_eq!(*id, test_oid()),
276 _ => panic!("Expected direct ref"),
277 }
278 }
279
280 #[test]
281 fn test_ref_symbolic() {
282 let r = Ref::symbolic("HEAD", "refs/heads/main");
283 match &r.value {
284 RefValue::Symbolic(target) => assert_eq!(target, "refs/heads/main"),
285 _ => panic!("Expected symbolic ref"),
286 }
287 }
288
289 #[test]
290 fn test_ref_resolve() {
291 let mut store = RefStore::new();
292 store.set_direct("refs/heads/main", test_oid());
293
294 let r = Ref::symbolic("HEAD", "refs/heads/main");
295 assert_eq!(r.resolve(&store), Some(&test_oid()));
296 }
297
298 #[test]
299 fn test_ref_store_count() {
300 let mut store = RefStore::new();
301 let initial = store.count(); store.create_branch("dev", test_oid());
303 assert_eq!(store.count(), initial + 1);
304 }
305
306 #[test]
307 fn test_infinite_symbolic_loop() {
308 let mut store = RefStore::new();
309 store.set_symbolic("A", "B");
310 store.set_symbolic("B", "A");
311 assert!(store.resolve("A").is_none());
312 }
313}