1pub(crate) mod function {
2 use std::{cmp::Ordering, sync::Arc};
3
4 use gix_features::{
5 parallel,
6 parallel::SequenceId,
7 progress::{
8 Progress,
9 prodash::{Count, DynNestedProgress},
10 },
11 };
12
13 use super::{Error, Mode, Options, Outcome, ProgressId, reduce, util};
14 use crate::data::output;
15
16 pub fn iter_from_counts<Find>(
46 mut counts: Vec<output::Count>,
47 db: Find,
48 mut progress: Box<dyn DynNestedProgress + 'static>,
49 Options {
50 version,
51 mode,
52 allow_thin_pack,
53 thread_limit,
54 chunk_size,
55 compression,
56 }: Options,
57 ) -> impl Iterator<Item = Result<(SequenceId, Vec<output::Entry>), Error>>
58 + parallel::reduce::Finalize<Reduce = reduce::Statistics<Error>>
59 where
60 Find: crate::Find + Send + Clone + 'static,
61 {
62 assert!(
63 matches!(version, crate::data::Version::V2),
64 "currently we can only write version 2"
65 );
66 let (chunk_size, thread_limit, _) =
67 parallel::optimize_chunk_size_and_thread_limit(chunk_size, Some(counts.len()), thread_limit, None);
68 {
69 let progress = Arc::new(parking_lot::Mutex::new(
70 progress.add_child_with_id("resolving".into(), ProgressId::ResolveCounts.into()),
71 ));
72 progress.lock().init(None, gix_features::progress::count("counts"));
73 let enough_counts_present = counts.len() > 4_000;
74 let start = std::time::Instant::now();
75 parallel::in_parallel_if(
76 || enough_counts_present,
77 counts.chunks_mut(chunk_size),
78 thread_limit,
79 |_n| Vec::<u8>::new(),
80 {
81 let progress = Arc::clone(&progress);
82 let db = db.clone();
83 move |chunk, buf| {
84 let chunk_size = chunk.len();
85 for count in chunk {
86 use crate::data::output::count::PackLocation::*;
87 match count.entry_pack_location {
88 LookedUp(_) => continue,
89 NotLookedUp => count.entry_pack_location = LookedUp(db.location_by_oid(&count.id, buf)),
90 }
91 }
92 progress.lock().inc_by(chunk_size);
93 Ok::<_, ()>(())
94 }
95 },
96 parallel::reduce::IdentityWithResult::<(), ()>::default(),
97 )
98 .expect("infallible - we ignore none-existing objects");
99 progress.lock().show_throughput(start);
100 }
101 let counts_range_by_pack_id = match mode {
102 Mode::PackCopyAndBaseObjects => {
103 let mut progress = progress.add_child_with_id("sorting".into(), ProgressId::SortEntries.into());
104 progress.init(Some(counts.len()), gix_features::progress::count("counts"));
105 let start = std::time::Instant::now();
106
107 use crate::data::output::count::PackLocation::*;
108 counts.sort_by(|lhs, rhs| match (&lhs.entry_pack_location, &rhs.entry_pack_location) {
109 (LookedUp(None), LookedUp(None)) => Ordering::Equal,
110 (LookedUp(Some(_)), LookedUp(None)) => Ordering::Greater,
111 (LookedUp(None), LookedUp(Some(_))) => Ordering::Less,
112 (LookedUp(Some(lhs)), LookedUp(Some(rhs))) => lhs
113 .pack_id
114 .cmp(&rhs.pack_id)
115 .then(lhs.pack_offset.cmp(&rhs.pack_offset)),
116 (_, _) => unreachable!("counts were resolved beforehand"),
117 });
118
119 let mut index: Vec<(u32, std::ops::Range<usize>)> = Vec::new();
120 let mut chunks_pack_start = counts.partition_point(|e| e.entry_pack_location.is_none());
121 let mut slice = &counts[chunks_pack_start..];
122 while !slice.is_empty() {
123 let current_pack_id = slice[0].entry_pack_location.as_ref().expect("packed object").pack_id;
124 let pack_end = slice.partition_point(|e| {
125 e.entry_pack_location.as_ref().expect("packed object").pack_id == current_pack_id
126 });
127 index.push((current_pack_id, chunks_pack_start..chunks_pack_start + pack_end));
128 slice = &slice[pack_end..];
129 chunks_pack_start += pack_end;
130 }
131
132 progress.set(counts.len());
133 progress.show_throughput(start);
134
135 index
136 }
137 };
138
139 let counts = Arc::new(counts);
140 let progress = Arc::new(parking_lot::Mutex::new(progress));
141 let chunks = util::ChunkRanges::new(chunk_size, counts.len());
142
143 parallel::reduce::Stepwise::new(
144 chunks.enumerate(),
145 thread_limit,
146 {
147 let progress = Arc::clone(&progress);
148 move |n| {
149 (
150 Vec::new(), progress
152 .lock()
153 .add_child_with_id(format!("thread {n}"), gix_features::progress::UNKNOWN),
154 )
155 }
156 },
157 {
158 let counts = Arc::clone(&counts);
159 move |(chunk_id, chunk_range): (SequenceId, std::ops::Range<usize>), (buf, progress)| {
160 let mut out = Vec::new();
161 let chunk = &counts[chunk_range];
162 let mut stats = Outcome::default();
163 let mut pack_offsets_to_id = None;
164 progress.init(Some(chunk.len()), gix_features::progress::count("objects"));
165
166 for count in chunk.iter() {
167 out.push(match count
168 .entry_pack_location
169 .as_ref()
170 .and_then(|l| db.entry_by_location(l).map(|pe| (l, pe)))
171 {
172 Some((location, pack_entry)) => {
173 if let Some((cached_pack_id, _)) = &pack_offsets_to_id {
174 if *cached_pack_id != location.pack_id {
175 pack_offsets_to_id = None;
176 }
177 }
178 let pack_range = counts_range_by_pack_id[counts_range_by_pack_id
179 .binary_search_by_key(&location.pack_id, |e| e.0)
180 .expect("pack-id always present")]
181 .1
182 .clone();
183 let base_index_offset = pack_range.start;
184 let counts_in_pack = &counts[pack_range];
185 let entry = output::Entry::from_pack_entry(
186 pack_entry,
187 count,
188 counts_in_pack,
189 base_index_offset,
190 allow_thin_pack.then_some({
191 |pack_id, base_offset| {
192 let (cached_pack_id, cache) = pack_offsets_to_id.get_or_insert_with(|| {
193 db.pack_offsets_and_oid(pack_id)
194 .map(|mut v| {
195 v.sort_by_key(|e| e.0);
196 (pack_id, v)
197 })
198 .expect("pack used for counts is still available")
199 });
200 debug_assert_eq!(*cached_pack_id, pack_id);
201 stats.ref_delta_objects += 1;
202 cache
203 .binary_search_by_key(&base_offset, |e| e.0)
204 .ok()
205 .map(|idx| cache[idx].1)
206 }
207 }),
208 version,
209 );
210 match entry {
211 Some(entry) => {
212 stats.objects_copied_from_pack += 1;
213 entry
214 }
215 None => match db.try_find(&count.id, buf).map_err(Error::Find)? {
216 Some((obj, _location)) => {
217 stats.decoded_and_recompressed_objects += 1;
218 output::Entry::from_data(count, &obj, compression)
219 }
220 None => {
221 stats.missing_objects += 1;
222 Ok(output::Entry::invalid())
223 }
224 },
225 }
226 }
227 None => match db.try_find(&count.id, buf).map_err(Error::Find)? {
228 Some((obj, _location)) => {
229 stats.decoded_and_recompressed_objects += 1;
230 output::Entry::from_data(count, &obj, compression)
231 }
232 None => {
233 stats.missing_objects += 1;
234 Ok(output::Entry::invalid())
235 }
236 },
237 }?);
238 progress.inc();
239 }
240 Ok((chunk_id, out, stats))
241 }
242 },
243 reduce::Statistics::default(),
244 )
245 }
246}
247
248mod util {
249 #[derive(Clone)]
250 pub struct ChunkRanges {
251 cursor: usize,
252 size: usize,
253 len: usize,
254 }
255
256 impl ChunkRanges {
257 pub fn new(size: usize, total: usize) -> Self {
258 ChunkRanges {
259 cursor: 0,
260 size,
261 len: total,
262 }
263 }
264 }
265
266 impl Iterator for ChunkRanges {
267 type Item = std::ops::Range<usize>;
268
269 fn next(&mut self) -> Option<Self::Item> {
270 if self.cursor >= self.len {
271 None
272 } else {
273 let upper = (self.cursor + self.size).min(self.len);
274 let range = self.cursor..upper;
275 self.cursor = upper;
276 Some(range)
277 }
278 }
279 }
280}
281
282mod reduce {
283 use std::marker::PhantomData;
284
285 use gix_features::{parallel, parallel::SequenceId};
286
287 use super::Outcome;
288 use crate::data::output;
289
290 pub struct Statistics<E> {
291 total: Outcome,
292 _err: PhantomData<E>,
293 }
294
295 impl<E> Default for Statistics<E> {
296 fn default() -> Self {
297 Statistics {
298 total: Default::default(),
299 _err: PhantomData,
300 }
301 }
302 }
303
304 impl<Error> parallel::Reduce for Statistics<Error> {
305 type Input = Result<(SequenceId, Vec<output::Entry>, Outcome), Error>;
306 type FeedProduce = (SequenceId, Vec<output::Entry>);
307 type Output = Outcome;
308 type Error = Error;
309
310 fn feed(&mut self, item: Self::Input) -> Result<Self::FeedProduce, Self::Error> {
311 item.map(|(cid, entries, stats)| {
312 self.total.aggregate(stats);
313 (cid, entries)
314 })
315 }
316
317 fn finalize(self) -> Result<Self::Output, Self::Error> {
318 Ok(self.total)
319 }
320 }
321}
322
323mod types {
324 use crate::data::output::entry;
325
326 #[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
328 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329 pub struct Outcome {
330 pub decoded_and_recompressed_objects: usize,
332 pub missing_objects: usize,
334 pub objects_copied_from_pack: usize,
337 pub ref_delta_objects: usize,
339 }
340
341 impl Outcome {
342 pub(in crate::data::output::entry) fn aggregate(
343 &mut self,
344 Outcome {
345 decoded_and_recompressed_objects: decoded_objects,
346 missing_objects,
347 objects_copied_from_pack,
348 ref_delta_objects,
349 }: Self,
350 ) {
351 self.decoded_and_recompressed_objects += decoded_objects;
352 self.missing_objects += missing_objects;
353 self.objects_copied_from_pack += objects_copied_from_pack;
354 self.ref_delta_objects += ref_delta_objects;
355 }
356 }
357
358 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
360 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
361 pub enum Mode {
362 PackCopyAndBaseObjects,
367 }
368
369 #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
371 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
372 pub struct Options {
373 pub thread_limit: Option<usize>,
375 pub mode: Mode,
377 pub allow_thin_pack: bool,
384 pub chunk_size: usize,
387 pub version: crate::data::Version,
389 pub compression: gix_zlib::Compression,
396 }
397
398 impl Default for Options {
399 fn default() -> Self {
400 Options {
401 thread_limit: None,
402 mode: Mode::PackCopyAndBaseObjects,
403 allow_thin_pack: false,
404 chunk_size: 10,
405 version: Default::default(),
406 compression: gix_zlib::Compression::DEFAULT,
407 }
408 }
409 }
410
411 #[derive(Debug, thiserror::Error)]
413 #[expect(missing_docs)]
414 pub enum Error {
415 #[error(transparent)]
416 Find(gix_object::find::Error),
417 #[error(transparent)]
418 NewEntry(#[from] entry::Error),
419 }
420
421 #[derive(Debug, Copy, Clone)]
425 pub enum ProgressId {
426 ResolveCounts,
428 SortEntries,
430 }
431
432 impl From<ProgressId> for gix_features::progress::Id {
433 fn from(v: ProgressId) -> Self {
434 match v {
435 ProgressId::ResolveCounts => *b"ECRC",
436 ProgressId::SortEntries => *b"ECSE",
437 }
438 }
439 }
440}
441pub use types::{Error, Mode, Options, Outcome, ProgressId};