1use crate::error::{GitError, Result};
2use crate::hash::GitHash;
3
4#[derive(Debug, Clone)]
5pub struct TreeEntry {
6 pub mode: u32,
8 pub name: String,
9 pub hash: GitHash,
10}
11
12impl TreeEntry {
13 pub fn is_directory(&self) -> bool {
14 self.mode == 0o40000
15 }
16
17 pub fn is_executable(&self) -> bool {
18 self.mode == 0o100_755
19 }
20
21 pub fn is_symlink(&self) -> bool {
22 self.mode == 0o120_000
23 }
24}
25
26#[derive(Debug, Clone)]
27pub struct TreeObject {
28 pub hash: GitHash,
29 pub entries: Vec<TreeEntry>,
30}
31
32impl TreeObject {
33 pub fn parse(hash: GitHash, data: &[u8]) -> Result<Self> {
38 let mut entries = Vec::new();
39 let mut pos = 0;
40
41 while pos < data.len() {
42 let nul = data[pos..]
43 .iter()
44 .position(|&b| b == 0)
45 .ok_or_else(|| GitError::InvalidObject("tree entry missing NUL".into()))?;
46
47 let header = std::str::from_utf8(&data[pos..pos + nul]).map_err(|e| {
48 GitError::InvalidObject(format!("tree entry header not UTF-8: {e}"))
49 })?;
50
51 let (mode_str, name) = header
52 .split_once(' ')
53 .ok_or_else(|| GitError::InvalidObject("tree entry header missing space".into()))?;
54
55 let mode = u32::from_str_radix(mode_str, 8)
56 .map_err(|_| GitError::InvalidObject(format!("invalid mode: {mode_str:?}")))?;
57
58 pos += nul + 1;
59
60 if pos + 20 > data.len() {
61 return Err(GitError::InvalidObject(
62 "tree entry truncated: no hash bytes".into(),
63 ));
64 }
65 let entry_hash = GitHash::from_bytes(&data[pos..pos + 20])?;
66 pos += 20;
67
68 entries.push(TreeEntry {
69 mode,
70 name: name.to_string(),
71 hash: entry_hash,
72 });
73 }
74
75 Ok(Self { hash, entries })
76 }
77}