gix_index/extension/
untracked_cache.rs1use bstr::BString;
2use gix_hash::ObjectId;
3
4use crate::{
5 entry,
6 extension::{Signature, UntrackedCache},
7 util::{read_u32, split_at_byte_exclusive, split_at_pos, var_int},
8};
9
10#[derive(Clone)]
12pub struct OidStat {
13 pub stat: entry::Stat,
15 pub id: ObjectId,
17}
18
19#[derive(Clone)]
21pub struct Directory {
22 pub name: BString,
24 pub untracked_entries: Vec<BString>,
26 pub sub_directories: Vec<usize>,
28
29 pub stat: Option<entry::Stat>,
31 pub exclude_file_oid: Option<ObjectId>,
33 pub check_only: bool,
35}
36
37pub const SIGNATURE: Signature = *b"UNTR";
39
40pub fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Option<UntrackedCache> {
43 if !data.last().is_some_and(|b| *b == 0) {
44 return None;
45 }
46 let (identifier_len, data) = var_int(data)?;
47 let (identifier, data) = split_at_pos(data, identifier_len.try_into().ok()?)?;
48
49 let hash_len = object_hash.len_in_bytes();
50 let (info_exclude, data) = decode_oid_stat(data, hash_len)?;
51 let (excludes_file, data) = decode_oid_stat(data, hash_len)?;
52 let (dir_flags, data) = read_u32(data)?;
53 let (exclude_filename_per_dir, data) = split_at_byte_exclusive(data, 0)?;
54
55 let (num_directory_blocks, data) = var_int(data)?;
56
57 let mut res = UntrackedCache {
58 identifier: identifier.into(),
59 info_exclude: (!info_exclude.id.is_null()).then_some(info_exclude),
60 excludes_file: (!excludes_file.id.is_null()).then_some(excludes_file),
61 exclude_filename_per_dir: exclude_filename_per_dir.into(),
62 dir_flags,
63 directories: Vec::new(),
64 };
65 if num_directory_blocks == 0 {
66 return data.is_empty().then_some(res);
67 }
68
69 let num_directory_blocks = num_directory_blocks.try_into().ok()?;
70 let directories = &mut res.directories;
71 directories.reserve(num_directory_blocks);
72
73 let data = decode_directory_block(data, directories)?;
74 if directories.len() != num_directory_blocks {
75 return None;
76 }
77 let (valid, data) = gix_bitmap::ewah::decode(data).ok()?;
78 let (check_only, data) = gix_bitmap::ewah::decode(data).ok()?;
79 let (hash_valid, mut data) = gix_bitmap::ewah::decode(data).ok()?;
80
81 if valid.num_bits() > num_directory_blocks
82 || check_only.num_bits() > num_directory_blocks
83 || hash_valid.num_bits() > num_directory_blocks
84 {
85 return None;
86 }
87
88 check_only.for_each_set_bit(|index| {
89 directories[index].check_only = true;
90 Some(())
91 })?;
92 valid.for_each_set_bit(|index| {
93 let (stat, rest) = crate::decode::stat(data)?;
94 directories[index].stat = stat.into();
95 data = rest;
96 Some(())
97 });
98 hash_valid.for_each_set_bit(|index| {
99 let (hash, rest) = split_at_pos(data, hash_len)?;
100 data = rest;
101 directories[index].exclude_file_oid = ObjectId::from_bytes_or_panic(hash).into();
102 Some(())
103 });
104
105 if data.len() != 1 {
107 return None;
108 }
109 res.into()
110}
111
112fn decode_directory_block<'a>(data: &'a [u8], directories: &mut Vec<Directory>) -> Option<&'a [u8]> {
113 let (num_untracked, data) = var_int(data)?;
114 let (num_dirs, data) = var_int(data)?;
115 let (name, mut data) = split_at_byte_exclusive(data, 0)?;
116 let mut untracked_entries = Vec::<BString>::with_capacity(num_untracked.try_into().ok()?);
117 for _ in 0..num_untracked {
118 let (name, rest) = split_at_byte_exclusive(data, 0)?;
119 data = rest;
120 untracked_entries.push(name.into());
121 }
122
123 let index = directories.len();
124 directories.push(Directory {
125 name: name.into(),
126 untracked_entries,
127 sub_directories: Vec::with_capacity(num_dirs.try_into().ok()?),
128 stat: None,
130 exclude_file_oid: None,
131 check_only: false,
132 });
133
134 for _ in 0..num_dirs {
135 let subdir_index = directories.len();
136 let rest = decode_directory_block(data, directories)?;
137 data = rest;
138 directories[index].sub_directories.push(subdir_index);
139 }
140
141 data.into()
142}
143
144fn decode_oid_stat(data: &[u8], hash_len: usize) -> Option<(OidStat, &[u8])> {
145 let (stat, data) = crate::decode::stat(data)?;
146 let (hash, data) = split_at_pos(data, hash_len)?;
147 Some((
148 OidStat {
149 stat,
150 id: ObjectId::from_bytes_or_panic(hash),
151 },
152 data,
153 ))
154}