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