git_internal/internal/object/
types.rs

1//! Object type enumeration plus conversions used across pack/object modules for printing,
2//! serialization, and delta bookkeeping.
3
4use std::fmt::Display;
5
6use serde::{Deserialize, Serialize};
7
8use crate::errors::GitError;
9
10/// In Git, each object type is assigned a unique integer value, which is used to identify the
11/// type of the object in Git repositories.
12///
13/// * `Blob` (1): A Git object that stores the content of a file.
14/// * `Tree` (2): A Git object that represents a directory or a folder in a Git repository.
15/// * `Commit` (3): A Git object that represents a commit in a Git repository, which contains
16///   information such as the author, committer, commit message, and parent commits.
17/// * `Tag` (4): A Git object that represents a tag in a Git repository, which is used to mark a
18///   specific point in the Git history.
19/// * `OffsetDelta` (6): A Git object that represents a delta between two objects, where the delta
20///   is stored as an offset to the base object.
21/// * `HashDelta` (7): A Git object that represents a delta between two objects, where the delta
22///   is stored as a hash of the base object.
23///
24/// By assigning unique integer values to each Git object type, Git can easily and efficiently
25/// identify the type of an object and perform the appropriate operations on it. when parsing a Git
26/// repository, Git can use the integer value of an object's type to determine how to parse
27/// the object's content.
28#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, Serialize, Deserialize)]
29pub enum ObjectType {
30    Commit = 1,
31    Tree,
32    Blob,
33    Tag,
34    OffsetZstdelta, // Private extension for Zstandard-compressed delta objects
35    OffsetDelta,
36    HashDelta,
37}
38
39const COMMIT_OBJECT_TYPE: &[u8] = b"commit";
40const TREE_OBJECT_TYPE: &[u8] = b"tree";
41const BLOB_OBJECT_TYPE: &[u8] = b"blob";
42const TAG_OBJECT_TYPE: &[u8] = b"tag";
43
44/// Display trait for Git objects type
45impl Display for ObjectType {
46    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
47        match self {
48            ObjectType::Blob => write!(f, "blob"),
49            ObjectType::Tree => write!(f, "tree"),
50            ObjectType::Commit => write!(f, "commit"),
51            ObjectType::Tag => write!(f, "tag"),
52            ObjectType::OffsetZstdelta => write!(f, "OffsetZstdelta"),
53            ObjectType::OffsetDelta => write!(f, "OffsetDelta"),
54            ObjectType::HashDelta => write!(f, "HashDelta"),
55        }
56    }
57}
58
59/// Display trait for Git objects type
60impl ObjectType {
61    pub fn to_bytes(&self) -> &[u8] {
62        match self {
63            ObjectType::Commit => COMMIT_OBJECT_TYPE,
64            ObjectType::Tree => TREE_OBJECT_TYPE,
65            ObjectType::Blob => BLOB_OBJECT_TYPE,
66            ObjectType::Tag => TAG_OBJECT_TYPE,
67            _ => panic!("can put compute the delta hash value"),
68        }
69    }
70
71    /// Parses a string representation of a Git object type and returns an ObjectType value
72    pub fn from_string(s: &str) -> Result<ObjectType, GitError> {
73        match s {
74            "blob" => Ok(ObjectType::Blob),
75            "tree" => Ok(ObjectType::Tree),
76            "commit" => Ok(ObjectType::Commit),
77            "tag" => Ok(ObjectType::Tag),
78            _ => Err(GitError::InvalidObjectType(s.to_string())),
79        }
80    }
81
82    /// Convert an object type to a byte array.
83    pub fn to_data(self) -> Result<Vec<u8>, GitError> {
84        match self {
85            ObjectType::Blob => Ok(vec![0x62, 0x6c, 0x6f, 0x62]),
86            ObjectType::Tree => Ok(vec![0x74, 0x72, 0x65, 0x65]),
87            ObjectType::Commit => Ok(vec![0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74]),
88            ObjectType::Tag => Ok(vec![0x74, 0x61, 0x67]),
89            _ => Err(GitError::InvalidObjectType(self.to_string())),
90        }
91    }
92
93    /// Convert an object type to a number.
94    pub fn to_u8(&self) -> u8 {
95        match self {
96            ObjectType::Commit => 1,
97            ObjectType::Tree => 2,
98            ObjectType::Blob => 3,
99            ObjectType::Tag => 4,
100            ObjectType::OffsetZstdelta => 5, // Type 5 is reserved in standard Git packs; we use it for Zstd delta objects.
101            ObjectType::OffsetDelta => 6,
102            ObjectType::HashDelta => 7,
103        }
104    }
105
106    /// Convert a number to an object type.
107    pub fn from_u8(number: u8) -> Result<ObjectType, GitError> {
108        match number {
109            1 => Ok(ObjectType::Commit),
110            2 => Ok(ObjectType::Tree),
111            3 => Ok(ObjectType::Blob),
112            4 => Ok(ObjectType::Tag),
113            5 => Ok(ObjectType::OffsetZstdelta),
114            6 => Ok(ObjectType::OffsetDelta),
115            7 => Ok(ObjectType::HashDelta),
116            _ => Err(GitError::InvalidObjectType(format!(
117                "Invalid object type number: {number}"
118            ))),
119        }
120    }
121
122    pub fn is_base(&self) -> bool {
123        match self {
124            ObjectType::Commit => true,
125            ObjectType::Tree => true,
126            ObjectType::Blob => true,
127            ObjectType::Tag => true,
128            ObjectType::HashDelta => false,
129            ObjectType::OffsetZstdelta => false,
130            ObjectType::OffsetDelta => false,
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::internal::object::types::ObjectType;
138
139    /// Verify ObjectType::Blob converts to its ASCII byte representation "blob".
140    #[test]
141    fn test_object_type_to_data() {
142        let blob = ObjectType::Blob;
143        let blob_bytes = blob.to_data().unwrap();
144        assert_eq!(blob_bytes, vec![0x62, 0x6c, 0x6f, 0x62]);
145    }
146
147    /// Verify parsing "tree" string returns ObjectType::Tree.
148    #[test]
149    fn test_object_type_from_string() {
150        let tree = ObjectType::from_string("tree").unwrap();
151        assert_eq!(tree, ObjectType::Tree);
152    }
153
154    /// Verify ObjectType::Commit converts to pack type number 1.
155    #[test]
156    fn test_object_type_to_u8() {
157        let commit = ObjectType::Commit;
158        let commit_number = commit.to_u8();
159        assert_eq!(commit_number, 1);
160    }
161
162    /// Verify pack type number 4 parses to ObjectType::Tag.
163    #[test]
164    fn test_object_type_from_u8() {
165        let tag_number = 4;
166        let tag = ObjectType::from_u8(tag_number).unwrap();
167        assert_eq!(tag, ObjectType::Tag);
168    }
169}