zerogit/objects/
mod.rs

1//! Git object types (blob, tree, commit, tag).
2
3pub mod blob;
4pub mod commit;
5pub mod oid;
6pub mod store;
7pub mod tag_object;
8pub mod tree;
9
10pub use blob::Blob;
11pub use commit::{Commit, Signature};
12pub use oid::Oid;
13pub use store::{LooseObjectStore, ObjectType, RawObject};
14pub use tag_object::TagObject;
15pub use tree::{FileMode, Tree, TreeEntry};
16
17/// A unified enum representing any Git object type.
18///
19/// This enum allows handling different Git object types uniformly
20/// while still providing type-safe access to specific object data.
21#[derive(Debug, Clone)]
22pub enum Object {
23    /// A blob object containing file content.
24    Blob(Blob),
25    /// A tree object containing directory entries.
26    Tree(Tree),
27    /// A commit object containing commit metadata.
28    Commit(Commit),
29}
30
31impl Object {
32    /// Returns the type of this object.
33    pub fn kind(&self) -> ObjectType {
34        match self {
35            Object::Blob(_) => ObjectType::Blob,
36            Object::Tree(_) => ObjectType::Tree,
37            Object::Commit(_) => ObjectType::Commit,
38        }
39    }
40
41    /// Returns a reference to the inner Blob if this is a Blob object.
42    pub fn as_blob(&self) -> Option<&Blob> {
43        match self {
44            Object::Blob(blob) => Some(blob),
45            _ => None,
46        }
47    }
48
49    /// Returns a reference to the inner Tree if this is a Tree object.
50    pub fn as_tree(&self) -> Option<&Tree> {
51        match self {
52            Object::Tree(tree) => Some(tree),
53            _ => None,
54        }
55    }
56
57    /// Returns a reference to the inner Commit if this is a Commit object.
58    pub fn as_commit(&self) -> Option<&Commit> {
59        match self {
60            Object::Commit(commit) => Some(commit),
61            _ => None,
62        }
63    }
64
65    /// Consumes this Object and returns the inner Blob if this is a Blob object.
66    pub fn into_blob(self) -> Option<Blob> {
67        match self {
68            Object::Blob(blob) => Some(blob),
69            _ => None,
70        }
71    }
72
73    /// Consumes this Object and returns the inner Tree if this is a Tree object.
74    pub fn into_tree(self) -> Option<Tree> {
75        match self {
76            Object::Tree(tree) => Some(tree),
77            _ => None,
78        }
79    }
80
81    /// Consumes this Object and returns the inner Commit if this is a Commit object.
82    pub fn into_commit(self) -> Option<Commit> {
83        match self {
84            Object::Commit(commit) => Some(commit),
85            _ => None,
86        }
87    }
88}
89
90impl From<Blob> for Object {
91    fn from(blob: Blob) -> Self {
92        Object::Blob(blob)
93    }
94}
95
96impl From<Tree> for Object {
97    fn from(tree: Tree) -> Self {
98        Object::Tree(tree)
99    }
100}
101
102impl From<Commit> for Object {
103    fn from(commit: Commit) -> Self {
104        Object::Commit(commit)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use store::RawObject;
112
113    fn make_blob_raw(content: &[u8]) -> RawObject {
114        RawObject {
115            object_type: ObjectType::Blob,
116            content: content.to_vec(),
117        }
118    }
119
120    fn make_tree_raw() -> RawObject {
121        RawObject {
122            object_type: ObjectType::Tree,
123            content: vec![],
124        }
125    }
126
127    fn make_commit_raw() -> RawObject {
128        let content = "tree da39a3ee5e6b4b0d3255bfef95601890afd80709\n\
129                       author John Doe <john@example.com> 1234567890 +0000\n\
130                       committer John Doe <john@example.com> 1234567890 +0000\n\
131                       \n\
132                       Test commit";
133        RawObject {
134            object_type: ObjectType::Commit,
135            content: content.as_bytes().to_vec(),
136        }
137    }
138
139    fn dummy_oid() -> Oid {
140        Oid::from_hex("0123456789abcdef0123456789abcdef01234567").unwrap()
141    }
142
143    // O-001: Object::Blob can be created from Blob
144    #[test]
145    fn test_object_from_blob() {
146        let blob = Blob::parse(make_blob_raw(b"Hello")).unwrap();
147        let obj = Object::from(blob);
148        assert!(matches!(obj, Object::Blob(_)));
149    }
150
151    // O-002: Object::Tree can be created from Tree
152    #[test]
153    fn test_object_from_tree() {
154        let tree = Tree::parse(make_tree_raw()).unwrap();
155        let obj = Object::from(tree);
156        assert!(matches!(obj, Object::Tree(_)));
157    }
158
159    // O-003: Object::Commit can be created from Commit
160    #[test]
161    fn test_object_from_commit() {
162        let commit = Commit::parse(dummy_oid(), make_commit_raw()).unwrap();
163        let obj = Object::from(commit);
164        assert!(matches!(obj, Object::Commit(_)));
165    }
166
167    // O-004: kind() returns correct ObjectType
168    #[test]
169    fn test_kind() {
170        let blob = Object::from(Blob::parse(make_blob_raw(b"test")).unwrap());
171        assert_eq!(blob.kind(), ObjectType::Blob);
172
173        let tree = Object::from(Tree::parse(make_tree_raw()).unwrap());
174        assert_eq!(tree.kind(), ObjectType::Tree);
175
176        let commit = Object::from(Commit::parse(dummy_oid(), make_commit_raw()).unwrap());
177        assert_eq!(commit.kind(), ObjectType::Commit);
178    }
179
180    // O-005: as_blob() returns Some for Blob, None for others
181    #[test]
182    fn test_as_blob() {
183        let blob_obj = Object::from(Blob::parse(make_blob_raw(b"test")).unwrap());
184        assert!(blob_obj.as_blob().is_some());
185        assert!(blob_obj.as_tree().is_none());
186        assert!(blob_obj.as_commit().is_none());
187    }
188
189    // O-006: as_tree() returns Some for Tree, None for others
190    #[test]
191    fn test_as_tree() {
192        let tree_obj = Object::from(Tree::parse(make_tree_raw()).unwrap());
193        assert!(tree_obj.as_tree().is_some());
194        assert!(tree_obj.as_blob().is_none());
195        assert!(tree_obj.as_commit().is_none());
196    }
197
198    // O-007: as_commit() returns Some for Commit, None for others
199    #[test]
200    fn test_as_commit() {
201        let commit_obj = Object::from(Commit::parse(dummy_oid(), make_commit_raw()).unwrap());
202        assert!(commit_obj.as_commit().is_some());
203        assert!(commit_obj.as_blob().is_none());
204        assert!(commit_obj.as_tree().is_none());
205    }
206
207    // O-008: into_blob() returns Some for Blob, None for others
208    #[test]
209    fn test_into_blob() {
210        let blob_obj = Object::from(Blob::parse(make_blob_raw(b"test")).unwrap());
211        let blob = blob_obj.into_blob();
212        assert!(blob.is_some());
213        assert_eq!(blob.unwrap().content(), b"test");
214
215        let tree_obj = Object::from(Tree::parse(make_tree_raw()).unwrap());
216        assert!(tree_obj.into_blob().is_none());
217    }
218
219    // O-009: into_tree() returns Some for Tree, None for others
220    #[test]
221    fn test_into_tree() {
222        let tree_obj = Object::from(Tree::parse(make_tree_raw()).unwrap());
223        let tree = tree_obj.into_tree();
224        assert!(tree.is_some());
225        assert!(tree.unwrap().is_empty());
226
227        let blob_obj = Object::from(Blob::parse(make_blob_raw(b"test")).unwrap());
228        assert!(blob_obj.into_tree().is_none());
229    }
230
231    // O-010: into_commit() returns Some for Commit, None for others
232    #[test]
233    fn test_into_commit() {
234        let commit_obj = Object::from(Commit::parse(dummy_oid(), make_commit_raw()).unwrap());
235        let commit = commit_obj.into_commit();
236        assert!(commit.is_some());
237        assert_eq!(commit.unwrap().summary(), "Test commit");
238
239        let blob_obj = Object::from(Blob::parse(make_blob_raw(b"test")).unwrap());
240        assert!(blob_obj.into_commit().is_none());
241    }
242
243    // O-011: as_* methods return references to inner data
244    #[test]
245    fn test_as_methods_return_references() {
246        let blob_obj = Object::from(Blob::parse(make_blob_raw(b"content")).unwrap());
247        let blob_ref = blob_obj.as_blob().unwrap();
248        assert_eq!(blob_ref.content(), b"content");
249
250        // Object is still usable after as_* call
251        assert_eq!(blob_obj.kind(), ObjectType::Blob);
252    }
253}