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