git_internal/internal/object/
blob.rs

1//! In Git, a blob (binary large object) is a type of Git object that stores the contents of a file.
2//! A blob object contains the binary data of a file, but does not contain any metadata such as
3//! the file name or permissions. The structure of a Git blob object is as follows:
4//!
5//! ```bash
6//! blob <content-length>\0<content>
7//! ```
8//!
9//! - `blob` is the object type, indicating that this is a blob object.
10//! - `<content-length>` is the length of the content in bytes, encoded as a string of decimal digits.
11//! - `\0` is a null byte, which separates the header from the content.
12//! - `<content>` is the binary data of the file, represented as a sequence of bytes.
13//!
14//! We can create a Git blob object for this file by running the following command:
15//!
16//! ```bash
17//! $ echo "Hello, world!" | git hash-object -w --stdin
18//! ```
19//!
20//! This will output an SHA-1/ SHA-256 hash, which is the ID of the newly created blob object.
21//! The contents of the blob object would look something like this:
22//!
23//! ```bash
24//! blob 13\0Hello, world!
25//! ```
26//! Git uses blobs to store the contents of files in a repository. Each version of a file is
27//! represented by a separate blob object, which can be linked together using Git's commit and tree
28//! objects to form a version history of the repository.
29//!
30use std::fmt::Display;
31
32use crate::{
33    errors::GitError,
34    hash::ObjectHash,
35    internal::object::{ObjectTrait, types::ObjectType},
36};
37
38/// **The Blob Object**
39#[derive(Eq, Debug, Clone)]
40pub struct Blob {
41    pub id: ObjectHash,
42    pub data: Vec<u8>,
43}
44
45impl PartialEq for Blob {
46    /// The Blob object is equal to another Blob object if their IDs are equal.
47    fn eq(&self, other: &Self) -> bool {
48        self.id == other.id
49    }
50}
51
52impl Display for Blob {
53    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54        writeln!(f, "Type: Blob").unwrap();
55        writeln!(f, "Size: {}", self.data.len())
56    }
57}
58
59impl ObjectTrait for Blob {
60    /// Creates a new object from a byte slice.
61    fn from_bytes(data: &[u8], hash: ObjectHash) -> Result<Self, GitError>
62    where
63        Self: Sized,
64    {
65        Ok(Blob {
66            id: hash,
67            data: data.to_vec(),
68        })
69    }
70
71    /// Returns the Blob type
72    fn get_type(&self) -> ObjectType {
73        ObjectType::Blob
74    }
75
76    fn get_size(&self) -> usize {
77        self.data.len()
78    }
79
80    fn to_data(&self) -> Result<Vec<u8>, GitError> {
81        Ok(self.data.clone())
82    }
83}
84
85impl Blob {
86    /// Create a new Blob object from the given content string.
87    /// - This is a convenience method for creating a Blob from a string.
88    /// - It converts the string to bytes and then calls `from_content_bytes`.
89    pub fn from_content(content: &str) -> Self {
90        let content = content.as_bytes().to_vec();
91        Blob::from_content_bytes(content)
92    }
93
94    /// Create a new Blob object from the given content bytes.
95    /// - some file content can't be represented as a string (UTF-8), so we need to use bytes.
96    pub fn from_content_bytes(content: Vec<u8>) -> Self {
97        Blob {
98            // Calculate the hash from the type and content
99            id: ObjectHash::from_type_and_data(ObjectType::Blob, &content),
100            data: content,
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::hash::{HashKind, set_hash_kind_for_test};
109
110    /// Test creating a Blob from content string
111    #[test]
112    fn test_blob_from_content() {
113        let _guard = set_hash_kind_for_test(HashKind::Sha1);
114        let content = "Hello, world!";
115        let blob = Blob::from_content(content);
116        assert_eq!(
117            blob.id.to_string(),
118            "5dd01c177f5d7d1be5346a5bc18a569a7410c2ef"
119        );
120    }
121
122    /// Test creating a Blob from content string using SHA-256 hash algorithm.
123    #[test]
124    fn test_blob_from_content_sha256() {
125        let _guard = set_hash_kind_for_test(HashKind::Sha256);
126        let content = "Hello, world!";
127        let blob = Blob::from_content(content);
128        assert_eq!(
129            blob.id.to_string(),
130            "178b5fbed164aee269fee7323badf7269cca0eed0875717b0d2d4f9819164c3f"
131        );
132    }
133}