git_policy_cli/index/
index_cache_tree.rs1const DEPTH_LIMIT: usize = 256;
15
16#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct CacheTree {
19 pub entry_count: i64,
21 pub id: Vec<u8>,
23 pub subtrees: Vec<(Vec<u8>, CacheTree)>,
25}
26
27fn parse_number(bytes: &[u8]) -> Option<(i64, &[u8])> {
29 let signs: usize = bytes.iter().take_while(|byte| { return **byte == b'-'; }).count();
30 let digits: usize = bytes[signs..].iter().take_while(|byte| { return byte.is_ascii_digit(); }).count();
31 let text: &str = std::str::from_utf8(&bytes[signs..signs + digits]).ok()?;
32 let magnitude: i64 = if text.is_empty() { 0 } else { text.parse::<i64>().ok()? };
33 let value: i64 = if signs % 2 == 1 { -magnitude } else { magnitude };
34 return Some((value, &bytes[signs + digits..]));
35}
36
37fn read_one<'a>(bytes: &'a [u8], hash_length: usize, depth: usize) -> Option<(Vec<u8>, CacheTree, &'a [u8])> {
39 if depth > DEPTH_LIMIT {
40 return None;
41 }
42 let nul: usize = bytes.iter().position(|byte| { return *byte == 0; })?;
43 let name: Vec<u8> = bytes[..nul].to_vec();
44 let (entry_count, after_count) = parse_number(&bytes[nul + 1..])?;
45 let after_space: &[u8] = after_count.strip_prefix(b" ")?;
46 let (subtree_count, after_subtrees) = parse_number(after_space)?;
47 let mut rest: &[u8] = after_subtrees.strip_prefix(b"\n")?;
48 let mut id: Vec<u8> = Vec::new();
49 if entry_count >= 0 {
50 id = rest.get(..hash_length)?.to_vec();
51 rest = &rest[hash_length..];
52 }
53 let mut subtrees: Vec<(Vec<u8>, CacheTree)> = Vec::new();
54 for _ in 0..usize::try_from(subtree_count).ok()? {
55 let (child_name, child, after) = read_one(rest, hash_length, depth + 1)?;
56 if subtrees.last().is_some_and(|(previous, _)| { return (previous.len(), previous.as_slice()) >= (child_name.len(), child_name.as_slice()); }) {
59 return None;
60 }
61 subtrees.push((child_name, child));
62 rest = after;
63 }
64 return Some((name, CacheTree { entry_count, id, subtrees }, rest));
65}
66
67fn write_one(out: &mut Vec<u8>, name: &[u8], tree: &CacheTree) {
69 out.extend_from_slice(name);
70 out.extend_from_slice(format!("\0{} {}\n", tree.entry_count, tree.subtrees.len()).as_bytes());
71 if tree.entry_count >= 0 {
72 out.extend_from_slice(tree.id.as_slice());
73 }
74 for (child_name, child) in &tree.subtrees {
75 write_one(out, child_name.as_slice(), child);
76 }
77}
78
79impl CacheTree {
80 pub fn read(bytes: &[u8], hash_length: usize) -> Option<CacheTree> {
82 let (_, tree, rest) = read_one(bytes, hash_length, 0)?;
83 if !rest.is_empty() {
84 return None;
85 }
86 return Some(tree);
87 }
88
89 pub fn write(&self) -> Vec<u8> {
91 let mut out: Vec<u8> = Vec::new();
92 write_one(&mut out, b"", self);
93 return out;
94 }
95
96 pub fn invalidate(&mut self, path: &[u8]) {
98 self.entry_count = -1;
99 self.id.clear();
100 let Some(slash) = path.iter().position(|byte| { return *byte == b'/'; }) else {
101 self.subtrees.retain(|(name, _)| { return name.as_slice() != path; });
102 return;
103 };
104 if let Some((_, child)) = self.subtrees.iter_mut().find(|(name, _)| { return name.as_slice() == &path[..slash]; }) {
105 child.invalidate(&path[slash + 1..]);
106 }
107 }
108}
109
110#[cfg(test)]
111#[path = "index_cache_tree_tests.rs"]
112mod tests;