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::errors::GitError;
33use crate::hash::ObjectHash;
34use crate::internal::object::ObjectTrait;
35use crate::internal::object::types::ObjectType;
36
37/// **The Blob Object**
38#[derive(Eq, Debug, Clone)]
39pub struct Blob {
40    pub id: ObjectHash,
41    pub data: Vec<u8>,
42}
43
44impl PartialEq for Blob {
45    /// The Blob object is equal to another Blob object if their IDs are equal.
46    fn eq(&self, other: &Self) -> bool {
47        self.id == other.id
48    }
49}
50
51impl Display for Blob {
52    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
53        writeln!(f, "Type: Blob").unwrap();
54        writeln!(f, "Size: {}", self.data.len())
55    }
56}
57
58impl ObjectTrait for Blob {
59    /// Creates a new object from a byte slice.
60    fn from_bytes(data: &[u8], hash: ObjectHash) -> Result<Self, GitError>
61    where
62        Self: Sized,
63    {
64        Ok(Blob {
65            id: hash,
66            data: data.to_vec(),
67        })
68    }
69
70    /// Returns the Blob type
71    fn get_type(&self) -> ObjectType {
72        ObjectType::Blob
73    }
74
75    fn get_size(&self) -> usize {
76        self.data.len()
77    }
78
79    fn to_data(&self) -> Result<Vec<u8>, GitError> {
80        Ok(self.data.clone())
81    }
82}
83
84impl Blob {
85    /// Create a new Blob object from the given content string.
86    /// - This is a convenience method for creating a Blob from a string.
87    /// - It converts the string to bytes and then calls `from_content_bytes`.
88    pub fn from_content(content: &str) -> Self {
89        let content = content.as_bytes().to_vec();
90        Blob::from_content_bytes(content)
91    }
92
93    /// Create a new Blob object from the given content bytes.
94    /// - some file content can't be represented as a string (UTF-8), so we need to use bytes.
95    pub fn from_content_bytes(content: Vec<u8>) -> Self {
96        Blob {
97            // Calculate the hash from the type and content
98            id: ObjectHash::from_type_and_data(ObjectType::Blob, &content),
99            data: content,
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::hash::{HashKind, set_hash_kind_for_test};
108    #[test]
109    fn test_blob_from_content() {
110        let _guard = set_hash_kind_for_test(HashKind::Sha1);
111        let content = "Hello, world!";
112        let blob = Blob::from_content(content);
113        assert_eq!(
114            blob.id.to_string(),
115            "5dd01c177f5d7d1be5346a5bc18a569a7410c2ef"
116        );
117    }
118    #[test]
119    fn test_blob_from_content_sha256() {
120        let _guard = set_hash_kind_for_test(HashKind::Sha256);
121        let content = "Hello, world!";
122        let blob = Blob::from_content(content);
123        assert_eq!(
124            blob.id.to_string(),
125            "178b5fbed164aee269fee7323badf7269cca0eed0875717b0d2d4f9819164c3f"
126        );
127    }
128}