git_internal/internal/object/
tree.rs1use crate::errors::GitError;
18use crate::hash::SHA1;
19use crate::internal::object::ObjectTrait;
20use crate::internal::object::ObjectType;
21use bincode::{Decode, Encode};
22use colored::Colorize;
23use encoding_rs::GBK;
24use serde::Deserialize;
25use serde::Serialize;
26use std::fmt::Display;
27
28#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize, Hash, Encode, Decode)]
33pub enum TreeItemMode {
34 Blob,
35 BlobExecutable,
36 Tree,
37 Commit,
38 Link,
39}
40
41impl Display for TreeItemMode {
42 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43 let _print = match *self {
44 TreeItemMode::Blob => "blob",
45 TreeItemMode::BlobExecutable => "blob executable",
46 TreeItemMode::Tree => "tree",
47 TreeItemMode::Commit => "commit",
48 TreeItemMode::Link => "link",
49 };
50
51 write!(f, "{}", String::from(_print).blue())
52 }
53}
54
55impl TreeItemMode {
56 pub fn tree_item_type_from_bytes(mode: &[u8]) -> Result<TreeItemMode, GitError> {
84 Ok(match mode {
85 b"40000" => TreeItemMode::Tree,
86 b"100644" => TreeItemMode::Blob,
87 b"100755" => TreeItemMode::BlobExecutable,
88 b"120000" => TreeItemMode::Link,
89 b"160000" => TreeItemMode::Commit,
90 b"100664" => TreeItemMode::Blob,
91 b"100640" => TreeItemMode::Blob,
92 _ => {
93 return Err(GitError::InvalidTreeItem(
94 String::from_utf8(mode.to_vec()).unwrap(),
95 ));
96 }
97 })
98 }
99
100 pub fn to_bytes(self) -> &'static [u8] {
105 match self {
106 TreeItemMode::Blob => b"100644",
107 TreeItemMode::BlobExecutable => b"100755",
108 TreeItemMode::Link => b"120000",
109 TreeItemMode::Tree => b"40000",
110 TreeItemMode::Commit => b"160000",
111 }
112 }
113}
114
115#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash, Encode, Decode)]
134pub struct TreeItem {
135 pub mode: TreeItemMode,
136 pub id: SHA1,
137 pub name: String,
138}
139
140impl Display for TreeItem {
141 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142 write!(
143 f,
144 "{} {} {}",
145 self.mode,
146 self.name,
147 self.id.to_string().blue()
148 )
149 }
150}
151
152impl TreeItem {
153 pub fn new(mode: TreeItemMode, id: SHA1, name: String) -> Self {
155 TreeItem { mode, id, name }
156 }
157
158 pub fn from_bytes(bytes: &[u8]) -> Result<Self, GitError> {
165 let mut parts = bytes.splitn(2, |b| *b == b' ');
166 let mode = parts.next().unwrap();
167 let rest = parts.next().unwrap();
168 let mut parts = rest.splitn(2, |b| *b == b'\0');
169 let raw_name = parts.next().unwrap();
170 let id = parts.next().unwrap();
171
172 let name = if String::from_utf8(raw_name.to_vec()).is_ok() {
173 String::from_utf8(raw_name.to_vec()).unwrap()
174 } else {
175 let (decoded, _, had_errors) = GBK.decode(raw_name);
176 if had_errors {
177 return Err(GitError::InvalidTreeItem(format!(
178 "Unsupported raw format: {raw_name:?}"
179 )));
180 } else {
181 decoded.to_string()
182 }
183 };
184 Ok(TreeItem {
185 mode: TreeItemMode::tree_item_type_from_bytes(mode)?,
186 id: SHA1::from_bytes(id),
187 name,
188 })
189 }
190
191 pub fn to_data(&self) -> Vec<u8> {
206 let mut bytes = Vec::new();
207
208 bytes.extend_from_slice(self.mode.to_bytes());
209 bytes.push(b' ');
210 bytes.extend_from_slice(self.name.as_bytes());
211 bytes.push(b'\0');
212 bytes.extend_from_slice(&self.id.to_data());
213
214 bytes
215 }
216
217 pub fn is_tree(&self) -> bool {
218 self.mode == TreeItemMode::Tree
219 }
220
221 pub fn is_blob(&self) -> bool {
222 self.mode == TreeItemMode::Blob
223 }
224}
225
226#[derive(Eq, Debug, Clone, Serialize, Deserialize, Encode, Decode)]
229pub struct Tree {
230 pub id: SHA1,
231 pub tree_items: Vec<TreeItem>,
232}
233
234impl PartialEq for Tree {
235 fn eq(&self, other: &Self) -> bool {
236 self.id == other.id
237 }
238}
239
240impl Display for Tree {
241 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
242 writeln!(f, "Tree: {}", self.id.to_string().blue())?;
243 for item in &self.tree_items {
244 writeln!(f, "{item}")?;
245 }
246 Ok(())
247 }
248}
249
250impl Tree {
251 pub fn from_tree_items(tree_items: Vec<TreeItem>) -> Result<Self, GitError> {
252 if tree_items.is_empty() {
253 return Err(GitError::EmptyTreeItems(
254 "When export tree object to meta, the items is empty"
255 .parse()
256 .unwrap(),
257 ));
258 }
259 let mut data = Vec::new();
260 for item in &tree_items {
261 data.extend_from_slice(item.to_data().as_slice());
262 }
263
264 Ok(Tree {
265 id: SHA1::from_type_and_data(ObjectType::Tree, &data),
266 tree_items,
267 })
268 }
269
270 pub fn rehash(&mut self) {
272 let mut data = Vec::new();
273 for item in &self.tree_items {
274 data.extend_from_slice(item.to_data().as_slice());
275 }
276 self.id = SHA1::from_type_and_data(ObjectType::Tree, &data);
277 }
278}
279
280impl TryFrom<&[u8]> for Tree {
281 type Error = GitError;
282 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
283 let h = SHA1::from_type_and_data(ObjectType::Tree, data);
284 Tree::from_bytes(data, h)
285 }
286}
287impl ObjectTrait for Tree {
288 fn from_bytes(data: &[u8], hash: SHA1) -> Result<Self, GitError>
289 where
290 Self: Sized,
291 {
292 let mut tree_items = Vec::new();
293 let mut i = 0;
294 while i < data.len() {
295 if let Some(index) = memchr::memchr(0x00, &data[i..]) {
297 let next = i + index + 21;
299
300 let item_data = &data[i..next];
302 let tree_item = TreeItem::from_bytes(item_data)?;
303
304 tree_items.push(tree_item);
305
306 i = next;
307 } else {
308 return Err(GitError::InvalidTreeObject);
310 }
311 }
312
313 Ok(Tree {
314 id: hash,
315 tree_items,
316 })
317 }
318
319 fn get_type(&self) -> ObjectType {
320 ObjectType::Tree
321 }
322
323 fn get_size(&self) -> usize {
324 todo!()
325 }
326
327 fn to_data(&self) -> Result<Vec<u8>, GitError> {
328 let mut data: Vec<u8> = Vec::new();
329
330 for item in &self.tree_items {
331 data.extend_from_slice(item.to_data().as_slice());
332 }
334
335 Ok(data)
336 }
337}
338
339#[cfg(test)]
340mod tests {
341
342 use std::str::FromStr;
343
344 use crate::hash::SHA1;
345 use crate::internal::object::tree::{Tree, TreeItem, TreeItemMode};
346
347 #[test]
348 fn test_tree_item_new() {
349 let tree_item = TreeItem::new(
350 TreeItemMode::Blob,
351 SHA1::from_str("8ab686eafeb1f44702738c8b0f24f2567c36da6d").unwrap(),
352 "hello-world".to_string(),
353 );
354
355 assert_eq!(tree_item.mode, TreeItemMode::Blob);
356 assert_eq!(
357 tree_item.id.to_string(),
358 "8ab686eafeb1f44702738c8b0f24f2567c36da6d"
359 );
360 }
361
362 #[test]
363 fn test_tree_item_to_bytes() {
364 let tree_item = TreeItem::new(
365 TreeItemMode::Blob,
366 SHA1::from_str("8ab686eafeb1f44702738c8b0f24f2567c36da6d").unwrap(),
367 "hello-world".to_string(),
368 );
369
370 let bytes = tree_item.to_data();
371 assert_eq!(
372 bytes,
373 vec![
374 49, 48, 48, 54, 52, 52, 32, 104, 101, 108, 108, 111, 45, 119, 111, 114, 108, 100,
375 0, 138, 182, 134, 234, 254, 177, 244, 71, 2, 115, 140, 139, 15, 36, 242, 86, 124,
376 54, 218, 109
377 ]
378 );
379 }
380
381 #[test]
382 fn test_tree_item_from_bytes() {
383 let item = TreeItem::new(
384 TreeItemMode::Blob,
385 SHA1::from_str("8ab686eafeb1f44702738c8b0f24f2567c36da6d").unwrap(),
386 "hello-world".to_string(),
387 );
388
389 let bytes = item.to_data();
390 let tree_item = TreeItem::from_bytes(bytes.as_slice()).unwrap();
391
392 assert_eq!(tree_item.mode, TreeItemMode::Blob);
393 assert_eq!(tree_item.id.to_string(), item.id.to_string());
394 }
395
396 #[test]
397 fn test_from_tree_items() {
398 let item = TreeItem::new(
399 TreeItemMode::Blob,
400 SHA1::from_str("17288789afffb273c8c394bc65e87d899b92897b").unwrap(),
401 "hello-world".to_string(),
402 );
403 let tree = Tree::from_tree_items(vec![item]).unwrap();
404 println!("{}", tree.id);
405 assert_eq!(
406 "cf99336fa61439a2f074c7e6de1c1a05579550e2",
407 tree.id.to_string()
408 );
409 }
410}