1use std::sync::atomic::AtomicBool;
2
3use gix_features::{parallel, progress::Progress};
4
5use crate::index;
6
7mod reduce;
8pub mod with_index;
10pub mod with_lookup;
12use reduce::Reducer;
13
14mod error;
15pub use error::Error;
16use gix_features::progress::DynNestedProgress;
17
18mod types;
19pub use types::{Algorithm, ProgressId, SafetyCheck, Statistics};
20
21#[derive(Debug, Clone)]
23pub struct Options<F> {
24 pub traversal: Algorithm,
26 pub thread_limit: Option<usize>,
29 pub check: SafetyCheck,
31 pub alloc_limit_bytes: Option<usize>,
34 pub make_pack_lookup_cache: F,
36}
37
38impl Default for Options<fn() -> crate::cache::Never> {
39 fn default() -> Self {
40 Options {
41 check: Default::default(),
42 traversal: Default::default(),
43 thread_limit: None,
44 alloc_limit_bytes: None,
45 make_pack_lookup_cache: || crate::cache::Never,
46 }
47 }
48}
49
50pub struct Outcome {
52 pub actual_index_checksum: gix_hash::ObjectId,
54 pub statistics: Statistics,
56}
57
58impl<T> index::File<T>
60where
61 T: crate::FileData + Sync,
62{
63 pub fn traverse<C, Processor, E, F, D>(
84 &self,
85 pack: &crate::data::File<D>,
86 progress: &mut dyn DynNestedProgress,
87 should_interrupt: &AtomicBool,
88 processor: Processor,
89 Options {
90 traversal,
91 thread_limit,
92 check,
93 alloc_limit_bytes,
94 make_pack_lookup_cache,
95 }: Options<F>,
96 ) -> Result<Outcome, Error<E>>
97 where
98 C: crate::cache::DecodeEntry,
99 E: std::error::Error + Send + Sync + 'static,
100 Processor: FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn Progress) -> Result<(), E> + Send + Clone,
101 F: Fn() -> C + Send + Clone,
102 D: crate::FileData + Send + Sync,
103 {
104 match traversal {
105 Algorithm::Lookup => self.traverse_with_lookup(
106 processor,
107 pack,
108 progress,
109 should_interrupt,
110 with_lookup::Options {
111 thread_limit,
112 check,
113 make_pack_lookup_cache,
114 },
115 ),
116 Algorithm::DeltaTreeLookup => self.traverse_with_index(
117 pack,
118 processor,
119 progress,
120 should_interrupt,
121 with_index::Options {
122 check,
123 thread_limit,
124 alloc_limit_bytes,
125 },
126 ),
127 }
128 }
129
130 fn possibly_verify<E, D>(
131 &self,
132 pack: &crate::data::File<D>,
133 check: SafetyCheck,
134 pack_progress: &mut dyn Progress,
135 index_progress: &mut dyn Progress,
136 should_interrupt: &AtomicBool,
137 ) -> Result<gix_hash::ObjectId, Error<E>>
138 where
139 E: std::error::Error + Send + Sync + 'static,
140 D: crate::FileData + Send + Sync,
141 {
142 Ok(if check.file_checksum() {
143 pack.checksum()
144 .verify(&self.pack_checksum())
145 .map_err(Error::PackMismatch)?;
146 let (pack_res, id) = parallel::join(
147 move || pack.verify_checksum(pack_progress, should_interrupt),
148 move || self.verify_checksum(index_progress, should_interrupt),
149 );
150 pack_res.map_err(Error::PackVerify)?;
151 id.map_err(Error::IndexVerify)?
152 } else {
153 self.index_checksum()
154 })
155 }
156
157 #[expect(clippy::too_many_arguments)]
158 fn decode_and_process_entry<C, E, D>(
159 &self,
160 check: SafetyCheck,
161 pack: &crate::data::File<D>,
162 cache: &mut C,
163 buf: &mut Vec<u8>,
164 inflate: &mut gix_zlib::Inflate,
165 progress: &mut dyn Progress,
166 index_entry: &index::Entry,
167 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn Progress) -> Result<(), E>,
168 ) -> Result<crate::data::decode::entry::Outcome, Error<E>>
169 where
170 C: crate::cache::DecodeEntry,
171 E: std::error::Error + Send + Sync + 'static,
172 D: crate::FileData + Send + Sync,
173 {
174 let pack_entry = pack.entry(index_entry.pack_offset)?;
175 let pack_entry_data_offset = pack_entry.data_offset;
176 let entry_stats = pack
177 .decode_entry(
178 pack_entry,
179 buf,
180 inflate,
181 &|id, _| {
182 let index = self.lookup(id)?;
183 pack.entry(self.pack_offset_at_index(index))
184 .ok()
185 .map(crate::data::decode::entry::ResolvedBase::InPack)
186 },
187 cache,
188 )
189 .map_err(|e| Error::PackDecode {
190 source: e,
191 id: index_entry.oid,
192 offset: index_entry.pack_offset,
193 })?;
194 let object_kind = entry_stats.kind;
195 let header_size = (pack_entry_data_offset - index_entry.pack_offset) as usize;
196 let entry_len = header_size + entry_stats.compressed_size;
197
198 process_entry(
199 check,
200 object_kind,
201 buf,
202 index_entry,
203 || pack.entry_crc32(index_entry.pack_offset, entry_len),
204 progress,
205 processor,
206 )?;
207 Ok(entry_stats)
208 }
209}
210
211fn process_entry<E>(
212 check: SafetyCheck,
213 object_kind: gix_object::Kind,
214 decompressed: &[u8],
215 index_entry: &index::Entry,
216 pack_entry_crc32: impl FnOnce() -> u32,
217 progress: &dyn Progress,
218 processor: &mut impl FnMut(gix_object::Kind, &[u8], &index::Entry, &dyn Progress) -> Result<(), E>,
219) -> Result<(), Error<E>>
220where
221 E: std::error::Error + Send + Sync + 'static,
222{
223 if check.object_checksum() {
224 gix_object::Data::new(decompressed, object_kind, index_entry.oid.kind())
225 .verify_checksum(&index_entry.oid)
226 .map_err(|source| Error::PackObjectVerify {
227 offset: index_entry.pack_offset,
228 source,
229 })?;
230 if let Some(desired_crc32) = index_entry.crc32 {
231 let actual_crc32 = pack_entry_crc32();
232 if actual_crc32 != desired_crc32 {
233 return Err(Error::Crc32Mismatch {
234 actual: actual_crc32,
235 expected: desired_crc32,
236 offset: index_entry.pack_offset,
237 kind: object_kind,
238 });
239 }
240 }
241 }
242 processor(object_kind, decompressed, index_entry, progress).map_err(Error::Processor)
243}