⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/devtools/git/
refs.rs

1//! Git Reference Management
2//!
3//! Manages HEAD, branches, tags, and symbolic references.
4
5use alloc::{
6    collections::BTreeMap,
7    string::{String, ToString},
8    vec::Vec,
9};
10
11use super::objects::ObjectId;
12
13/// Reference types
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub(crate) enum RefValue {
16    /// Direct reference to an object ID
17    Direct(ObjectId),
18    /// Symbolic reference (e.g., HEAD -> refs/heads/main)
19    Symbolic(String),
20}
21
22/// Reference entry
23#[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    /// Resolve a reference to a direct object ID
45    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/// Reference store (in-memory)
54#[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        // Default HEAD -> refs/heads/main
69        refs.insert(
70            "HEAD".to_string(),
71            RefValue::Symbolic("refs/heads/main".to_string()),
72        );
73        Self { refs }
74    }
75
76    /// Get a reference value
77    pub(crate) fn get(&self, name: &str) -> Option<&RefValue> {
78        self.refs.get(name)
79    }
80
81    /// Set a direct reference
82    pub(crate) fn set_direct(&mut self, name: &str, id: ObjectId) {
83        self.refs.insert(name.to_string(), RefValue::Direct(id));
84    }
85
86    /// Set a symbolic reference
87    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    /// Resolve a reference name to an object ID, following symbolic refs
93    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; // Prevent infinite loops
99            }
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    /// Get HEAD object ID
111    pub(crate) fn head(&self) -> Option<&ObjectId> {
112        self.resolve("HEAD")
113    }
114
115    /// Get current branch name (if HEAD is symbolic)
116    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, // Detached HEAD
120        }
121    }
122
123    /// List all branches
124    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    /// List all tags
133    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    /// Delete a reference
142    pub(crate) fn delete(&mut self, name: &str) -> bool {
143        self.refs.remove(name).is_some()
144    }
145
146    /// Create a branch pointing to a commit
147    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    /// Create a lightweight tag
153    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    /// Switch HEAD to a branch
159    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    /// Ref count
170    pub(crate) fn count(&self) -> usize {
171        self.refs.len()
172    }
173}
174
175// ---------------------------------------------------------------------------
176// Tests
177// ---------------------------------------------------------------------------
178
179#[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        // HEAD -> refs/heads/main -> ObjectId
252        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(); // HEAD
302        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}