1pub use error::Error;
2
3mod error;
4
5pub(crate) struct TreeEntry {
6 pub id: gix_hash::ObjectId,
7 pub crc32: u32,
8}
9
10#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Outcome {
14 pub index_version: crate::index::Version,
16 pub index_hash: gix_hash::ObjectId,
18
19 pub data_hash: gix_hash::ObjectId,
21 pub num_objects: u32,
23}
24
25#[derive(Debug, Copy, Clone)]
29pub enum ProgressId {
30 IndexObjects,
32 DecompressedBytes,
36 ResolveObjects,
40 DecodedBytes,
42 IndexBytesWritten,
44}
45
46impl From<ProgressId> for gix_features::progress::Id {
47 fn from(v: ProgressId) -> Self {
48 match v {
49 ProgressId::IndexObjects => *b"IWIO",
50 ProgressId::DecompressedBytes => *b"IWDB",
51 ProgressId::ResolveObjects => *b"IWRO",
52 ProgressId::DecodedBytes => *b"IWDB",
53 ProgressId::IndexBytesWritten => *b"IWBW",
54 }
55 }
56}
57
58pub(super) mod function {
59 use std::{io, sync::atomic::AtomicBool};
60
61 use gix_features::progress::{self, Count, Progress, prodash::DynNestedProgress};
62
63 use crate::cache::delta::{Tree, traverse};
64
65 use super::{Error, Outcome, ProgressId, TreeEntry, modify_base};
66
67 #[expect(clippy::too_many_arguments)]
99 pub fn write_data_iter_to_stream<F, F2, R>(
100 version: crate::index::Version,
101 make_resolver: F,
102 entries: &mut dyn Iterator<Item = Result<crate::data::input::Entry, crate::data::input::Error>>,
103 thread_limit: Option<usize>,
104 root_progress: &mut dyn DynNestedProgress,
105 out: &mut dyn io::Write,
106 should_interrupt: &AtomicBool,
107 object_hash: gix_hash::Kind,
108 alloc_limit_bytes: Option<usize>,
109 pack_version: crate::data::Version,
110 ) -> Result<Outcome, Error>
111 where
112 F: FnOnce() -> io::Result<(F2, R)>,
113 R: Send + Sync,
114 F2: for<'r> Fn(crate::data::EntryRange, &'r R) -> Option<&'r [u8]> + Send + Clone,
115 {
116 if version != crate::index::Version::default() {
117 return Err(Error::Unsupported(version));
118 }
119 let mut num_objects: usize = 0;
120 let mut last_seen_trailer = None;
121 let (anticipated_num_objects, upper_bound) = entries.size_hint();
122 let worst_case_num_objects_after_thin_pack_resolution = upper_bound.unwrap_or(anticipated_num_objects);
123 let mut tree = Tree::with_capacity(worst_case_num_objects_after_thin_pack_resolution)?;
124 let indexing_start = std::time::Instant::now();
125
126 root_progress.init(Some(4), progress::steps());
127 let mut objects_progress = root_progress.add_child_with_id("indexing".into(), ProgressId::IndexObjects.into());
128 objects_progress.init(Some(anticipated_num_objects), progress::count("objects"));
129 let mut decompressed_progress =
130 root_progress.add_child_with_id("decompressing".into(), ProgressId::DecompressedBytes.into());
131 decompressed_progress.init(None, progress::bytes());
132 let mut pack_entries_end: u64 = 0;
133
134 for entry in entries {
135 let crate::data::input::Entry {
136 header,
137 pack_offset,
138 crc32,
139 header_size,
140 compressed: _,
141 compressed_size,
142 decompressed_size,
143 trailer,
144 } = entry?;
145
146 decompressed_progress.inc_by(decompressed_size as usize);
147
148 let entry_len = u64::from(header_size) + compressed_size;
149 pack_entries_end = pack_offset + entry_len;
150
151 let crc32 = crc32.expect("crc32 to be computed by the iterator. Caller assures correct configuration.");
152
153 use crate::data::entry::Header::*;
154 match header {
155 Tree | Blob | Commit | Tag => {
156 tree.add_root(
157 pack_offset,
158 TreeEntry {
159 id: object_hash.null(),
160 crc32,
161 },
162 )?;
163 }
164 RefDelta { base_id } => {
165 tree.add_child_by_id(
166 base_id,
167 pack_offset,
168 TreeEntry {
169 id: object_hash.null(),
170 crc32,
171 },
172 )?;
173 }
174 OfsDelta { base_distance } => {
175 let base_pack_offset =
176 crate::data::entry::Header::verified_base_pack_offset(pack_offset, base_distance).ok_or(
177 Error::IteratorInvariantBaseOffset {
178 pack_offset,
179 distance: base_distance,
180 },
181 )?;
182 tree.add_child(
183 base_pack_offset,
184 pack_offset,
185 TreeEntry {
186 id: object_hash.null(),
187 crc32,
188 },
189 )?;
190 }
191 }
192 last_seen_trailer = trailer;
193 num_objects += 1;
194 objects_progress.inc();
195 }
196 let num_objects: u32 = num_objects
197 .try_into()
198 .map_err(|_| Error::IteratorInvariantTooManyObjects(num_objects))?;
199
200 objects_progress.show_throughput(indexing_start);
201 decompressed_progress.show_throughput(indexing_start);
202 drop(objects_progress);
203 drop(decompressed_progress);
204
205 root_progress.inc();
206
207 let (resolver, pack) = make_resolver().map_err(gix_hash::io::Error::from)?;
208 let sorted_pack_offsets_by_oid = {
209 let traverse::Outcome { roots, children } = tree.traverse(
210 resolver,
211 &pack,
212 pack_entries_end,
213 |data,
214 _progress,
215 traverse::Context {
216 entry,
217 decompressed: bytes,
218 ..
219 }| { modify_base(data, entry, bytes, object_hash) },
220 traverse::Options {
221 object_progress: Box::new(
222 root_progress.add_child_with_id("Resolving".into(), ProgressId::ResolveObjects.into()),
223 ),
224 size_progress: &mut root_progress
225 .add_child_with_id("Decoding".into(), ProgressId::DecodedBytes.into()),
226 thread_limit,
227 should_interrupt,
228 object_hash,
229 alloc_limit_bytes,
230 },
231 )?;
232 root_progress.inc();
233
234 let mut items = roots;
235 items.extend(children);
236 {
237 let _progress =
238 root_progress.add_child_with_id("sorting by id".into(), gix_features::progress::UNKNOWN);
239 items.sort_by_key(|e| e.data.id);
240 }
241
242 root_progress.inc();
243 items
244 };
245
246 let pack_hash = match last_seen_trailer {
247 Some(ph) => ph,
248 None if num_objects == 0 => {
249 let header = crate::data::header::encode(pack_version, 0);
250 let mut hasher = gix_hash::hasher(object_hash);
251 hasher.update(&header);
252 hasher.try_finalize().map_err(gix_hash::io::Error::from)?
253 }
254 None => return Err(Error::IteratorInvariantTrailer),
255 };
256 let index_hash = crate::index::encode::write_to(
257 out,
258 sorted_pack_offsets_by_oid,
259 &pack_hash,
260 version,
261 object_hash,
262 &mut root_progress.add_child_with_id("writing index file".into(), ProgressId::IndexBytesWritten.into()),
263 )?;
264 root_progress.show_throughput_with(
265 indexing_start,
266 num_objects as usize,
267 progress::count("objects").expect("unit always set"),
268 progress::MessageLevel::Success,
269 );
270 Ok(Outcome {
271 index_version: version,
272 index_hash,
273 data_hash: pack_hash,
274 num_objects,
275 })
276 }
277}
278
279fn modify_base(
280 entry: &mut TreeEntry,
281 pack_entry: &crate::data::Entry,
282 decompressed: &[u8],
283 hash: gix_hash::Kind,
284) -> Result<(), gix_hash::hasher::Error> {
285 let object_kind = pack_entry.header.as_kind().expect("base object as source of iteration");
286 let id = gix_object::compute_hash(hash, object_kind, decompressed)?;
287 entry.id = id;
288 Ok(())
289}