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::{
109 hash::{HashKind, set_hash_kind_for_test},
110 internal::object::ObjectTrait,
111 };
112
113 /// Test creating a Blob from content string
114 #[test]
115 fn test_blob_from_content() {
116 let _guard = set_hash_kind_for_test(HashKind::Sha1);
117 let content = "Hello, world!";
118 let blob = Blob::from_content(content);
119 assert_eq!(
120 blob.id.to_string(),
121 "5dd01c177f5d7d1be5346a5bc18a569a7410c2ef"
122 );
123 let hash_from_trait = blob.object_hash().unwrap();
124 assert_eq!(hash_from_trait.to_string(), blob.id.to_string());
125 }
126
127 /// Test creating a Blob from content string using SHA-256 hash algorithm.
128 #[test]
129 fn test_blob_from_content_sha256() {
130 let _guard = set_hash_kind_for_test(HashKind::Sha256);
131 let content = "Hello, world!";
132 let blob = Blob::from_content(content);
133 assert_eq!(
134 blob.id.to_string(),
135 "178b5fbed164aee269fee7323badf7269cca0eed0875717b0d2d4f9819164c3f"
136 );
137 }
138}