1use std::{cell::RefCell, sync::atomic::AtomicBool};
2
3use gix_features::parallel;
4use gix_hash::ObjectId;
5
6use crate::data::output;
7
8pub(in crate::data::output::count::objects_impl) mod reduce;
9mod util;
10
11mod types;
12pub use types::{Error, ObjectExpansion, Options, Outcome};
13
14mod tree;
15
16pub fn objects<Find>(
33 db: Find,
34 objects_ids: Box<dyn Iterator<Item = Result<ObjectId, Box<dyn std::error::Error + Send + Sync + 'static>>> + Send>,
35 objects: &dyn gix_features::progress::Count,
36 should_interrupt: &AtomicBool,
37 Options {
38 thread_limit,
39 input_object_expansion,
40 chunk_size,
41 }: Options,
42) -> Result<(Vec<output::Count>, Outcome), Error>
43where
44 Find: crate::Find + Send + Clone,
45{
46 let lower_bound = objects_ids.size_hint().0;
47 let (chunk_size, thread_limit, _) = parallel::optimize_chunk_size_and_thread_limit(
48 chunk_size,
49 if lower_bound == 0 { None } else { Some(lower_bound) },
50 thread_limit,
51 None,
52 );
53 let chunks = gix_features::iter::Chunks {
54 inner: objects_ids,
55 size: chunk_size,
56 };
57 let seen_objs = gix_hashtable::sync::ObjectIdMap::default();
58 let objects = objects.counter();
59
60 parallel::in_parallel(
61 chunks,
62 thread_limit,
63 {
64 move |_| {
65 (
66 Vec::new(), Vec::new(), objects.clone(),
69 )
70 }
71 },
72 {
73 let seen_objs = &seen_objs;
74 move |oids: Vec<_>, (buf1, buf2, objects)| {
75 expand::this(
76 &db,
77 input_object_expansion,
78 seen_objs,
79 &mut oids.into_iter(),
80 buf1,
81 buf2,
82 objects,
83 should_interrupt,
84 true, )
86 }
87 },
88 reduce::Statistics::new(),
89 )
90}
91
92pub fn objects_unthreaded(
94 db: &dyn crate::Find,
95 object_ids: &mut dyn Iterator<Item = Result<ObjectId, Box<dyn std::error::Error + Send + Sync + 'static>>>,
96 objects: &dyn gix_features::progress::Count,
97 should_interrupt: &AtomicBool,
98 input_object_expansion: ObjectExpansion,
99) -> Result<(Vec<output::Count>, Outcome), Error> {
100 let seen_objs = RefCell::new(gix_hashtable::HashSet::default());
101
102 let (mut buf1, mut buf2) = (Vec::new(), Vec::new());
103 expand::this(
104 db,
105 input_object_expansion,
106 &seen_objs,
107 object_ids,
108 &mut buf1,
109 &mut buf2,
110 &objects.counter(),
111 should_interrupt,
112 false, )
114}
115
116mod expand {
117 use std::{
118 cell::RefCell,
119 sync::atomic::{AtomicBool, Ordering},
120 };
121
122 use gix_hash::{ObjectId, oid};
123 use gix_object::{CommitRefIter, Data, TagRefIter};
124
125 use super::{
126 tree,
127 types::{Error, ObjectExpansion, Outcome},
128 util,
129 };
130 use crate::{
131 FindExt,
132 data::{output, output::count::PackLocation},
133 };
134
135 #[expect(clippy::too_many_arguments)]
136 pub fn this(
137 db: &dyn crate::Find,
138 input_object_expansion: ObjectExpansion,
139 seen_objs: &impl util::InsertImmutable,
140 oids: &mut dyn Iterator<Item = Result<ObjectId, Box<dyn std::error::Error + Send + Sync + 'static>>>,
141 buf1: &mut Vec<u8>,
142 buf2: &mut Vec<u8>,
143 objects: &gix_features::progress::AtomicStep,
144 should_interrupt: &AtomicBool,
145 allow_pack_lookups: bool,
146 ) -> Result<(Vec<output::Count>, Outcome), Error> {
147 use ObjectExpansion::*;
148
149 let mut out = Vec::new();
150 let mut tree_traversal_state = gix_traverse::tree::breadthfirst::State::default();
151 let mut tree_diff_state = gix_diff::tree::State::default();
152 let mut parent_commit_ids = Vec::new();
153 let mut traverse_delegate = tree::traverse::AllUnseen::new(seen_objs);
154 let mut changes_delegate = tree::changes::AllNew::new(seen_objs);
155 let mut outcome = Outcome::default();
156
157 let stats = &mut outcome;
158 for id in oids {
159 if should_interrupt.load(Ordering::Relaxed) {
160 return Err(Error::Interrupted);
161 }
162
163 let id = id.map_err(Error::InputIteration)?;
164 let (obj, location) = db.find(&id, buf1)?;
165 stats.input_objects += 1;
166 match input_object_expansion {
167 TreeAdditionsComparedToAncestor => {
168 use gix_object::Kind::*;
169 let mut obj = obj;
170 let mut location = location;
171 let mut id = id.to_owned();
172
173 loop {
174 push_obj_count_unique(&mut out, seen_objs, &id, location, objects, stats, false);
175 match obj.kind {
176 Tree | Blob => break,
177 Tag => {
178 id = TagRefIter::from_bytes(obj.data, obj.object_hash)
179 .target_id()
180 .expect("every tag has a target");
181 let tmp = db.find(&id, buf1)?;
182
183 obj = tmp.0;
184 location = tmp.1;
185
186 stats.expanded_objects += 1;
187 continue;
188 }
189 Commit => {
190 let current_tree_iter = {
191 let mut commit_iter = CommitRefIter::from_bytes(obj.data, obj.object_hash);
192 let tree_id = commit_iter.tree_id().expect("every commit has a tree");
193 parent_commit_ids.clear();
194 for token in commit_iter {
195 match token {
196 Ok(gix_object::commit::ref_iter::Token::Parent { id }) => {
197 parent_commit_ids.push(id);
198 }
199 Ok(_) => break,
200 Err(err) => return Err(Error::CommitDecode(err)),
201 }
202 }
203 let (obj, location) = db.find(&tree_id, buf1)?;
204 push_obj_count_unique(
205 &mut out, seen_objs, &tree_id, location, objects, stats, true,
206 );
207 gix_object::TreeRefIter::from_bytes(obj.data, obj.object_hash)
208 };
209
210 let objects_ref = if parent_commit_ids.is_empty() {
211 traverse_delegate.clear();
212 let objects = ExpandedCountingObjects::new(db, out, objects);
213 gix_traverse::tree::breadthfirst(
214 current_tree_iter,
215 &mut tree_traversal_state,
216 &objects,
217 &mut traverse_delegate,
218 )
219 .map_err(Error::TreeTraverse)?;
220 out = objects.dissolve(stats);
221 &traverse_delegate.non_trees
222 } else {
223 changes_delegate.clear();
224 for commit_id in &parent_commit_ids {
225 let parent_tree_id = {
226 let (parent_commit_obj, location) = db.find(commit_id, buf2)?;
227
228 push_obj_count_unique(
229 &mut out, seen_objs, commit_id, location, objects, stats, true,
230 );
231 CommitRefIter::from_bytes(
232 parent_commit_obj.data,
233 parent_commit_obj.object_hash,
234 )
235 .tree_id()
236 .expect("every commit has a tree")
237 };
238 let parent_tree = {
239 let (parent_tree_obj, location) = db.find(&parent_tree_id, buf2)?;
240 push_obj_count_unique(
241 &mut out,
242 seen_objs,
243 &parent_tree_id,
244 location,
245 objects,
246 stats,
247 true,
248 );
249 gix_object::TreeRefIter::from_bytes(
250 parent_tree_obj.data,
251 parent_tree_obj.object_hash,
252 )
253 };
254
255 let objects = CountingObjects::new(db);
256 gix_diff::tree(
257 parent_tree,
258 current_tree_iter,
259 &mut tree_diff_state,
260 &objects,
261 &mut changes_delegate,
262 )
263 .map_err(Error::TreeChanges)?;
264 stats.decoded_objects += objects.into_count();
265 }
266 &changes_delegate.objects
267 };
268 for id in objects_ref.iter() {
269 out.push(id_to_count(db, buf2, id, objects, stats, allow_pack_lookups));
270 }
271 break;
272 }
273 }
274 }
275 }
276 TreeContents => {
277 use gix_object::Kind::*;
278 let mut id = id;
279 let mut obj = (obj, location);
280 loop {
281 push_obj_count_unique(&mut out, seen_objs, &id, obj.1.clone(), objects, stats, false);
282 match obj.0.kind {
283 Tree => {
284 traverse_delegate.clear();
285 {
286 let objects = ExpandedCountingObjects::new(db, out, objects);
287 gix_traverse::tree::breadthfirst(
288 gix_object::TreeRefIter::from_bytes(obj.0.data, obj.0.object_hash),
289 &mut tree_traversal_state,
290 &objects,
291 &mut traverse_delegate,
292 )
293 .map_err(Error::TreeTraverse)?;
294 out = objects.dissolve(stats);
295 }
296 for id in &traverse_delegate.non_trees {
297 out.push(id_to_count(db, buf1, id, objects, stats, allow_pack_lookups));
298 }
299 break;
300 }
301 Commit => {
302 id = CommitRefIter::from_bytes(obj.0.data, obj.0.object_hash)
303 .tree_id()
304 .expect("every commit has a tree");
305 stats.expanded_objects += 1;
306 obj = db.find(&id, buf1)?;
307 continue;
308 }
309 Blob => break,
310 Tag => {
311 id = TagRefIter::from_bytes(obj.0.data, obj.0.object_hash)
312 .target_id()
313 .expect("every tag has a target");
314 stats.expanded_objects += 1;
315 obj = db.find(&id, buf1)?;
316 continue;
317 }
318 }
319 }
320 }
321 AsIs => push_obj_count_unique(&mut out, seen_objs, &id, location, objects, stats, false),
322 }
323 }
324 outcome.total_objects = out.len();
325 Ok((out, outcome))
326 }
327
328 #[inline]
329 fn push_obj_count_unique(
330 out: &mut Vec<output::Count>,
331 all_seen: &impl util::InsertImmutable,
332 id: &oid,
333 location: Option<crate::data::entry::Location>,
334 objects: &gix_features::progress::AtomicStep,
335 statistics: &mut Outcome,
336 count_expanded: bool,
337 ) {
338 let inserted = all_seen.insert(id.to_owned());
339 if inserted {
340 objects.fetch_add(1, Ordering::Relaxed);
341 statistics.decoded_objects += 1;
342 if count_expanded {
343 statistics.expanded_objects += 1;
344 }
345 out.push(output::Count::from_data(id, location));
346 }
347 }
348
349 #[inline]
350 fn id_to_count(
351 db: &dyn crate::Find,
352 buf: &mut Vec<u8>,
353 id: &oid,
354 objects: &gix_features::progress::AtomicStep,
355 statistics: &mut Outcome,
356 allow_pack_lookups: bool,
357 ) -> output::Count {
358 objects.fetch_add(1, Ordering::Relaxed);
359 statistics.expanded_objects += 1;
360 output::Count {
361 id: id.to_owned(),
362 entry_pack_location: if allow_pack_lookups {
363 PackLocation::LookedUp(db.location_by_oid(id, buf))
364 } else {
365 PackLocation::NotLookedUp
366 },
367 }
368 }
369
370 struct CountingObjects<'a> {
371 decoded_objects: std::cell::RefCell<usize>,
372 objects: &'a dyn crate::Find,
373 }
374
375 impl<'a> CountingObjects<'a> {
376 fn new(objects: &'a dyn crate::Find) -> Self {
377 Self {
378 decoded_objects: Default::default(),
379 objects,
380 }
381 }
382
383 fn into_count(self) -> usize {
384 self.decoded_objects.into_inner()
385 }
386 }
387
388 impl gix_object::Find for CountingObjects<'_> {
389 fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, gix_object::find::Error> {
390 let res = Ok(self.objects.try_find(id, buffer)?.map(|t| t.0));
391 *self.decoded_objects.borrow_mut() += 1;
392 res
393 }
394 }
395
396 struct ExpandedCountingObjects<'a> {
397 decoded_objects: std::cell::RefCell<usize>,
398 expanded_objects: std::cell::RefCell<usize>,
399 out: std::cell::RefCell<Vec<output::Count>>,
400 objects_count: &'a gix_features::progress::AtomicStep,
401 objects: &'a dyn crate::Find,
402 }
403
404 impl<'a> ExpandedCountingObjects<'a> {
405 fn new(
406 objects: &'a dyn crate::Find,
407 out: Vec<output::Count>,
408 objects_count: &'a gix_features::progress::AtomicStep,
409 ) -> Self {
410 Self {
411 decoded_objects: Default::default(),
412 expanded_objects: Default::default(),
413 out: RefCell::new(out),
414 objects_count,
415 objects,
416 }
417 }
418
419 fn dissolve(self, stats: &mut Outcome) -> Vec<output::Count> {
420 stats.decoded_objects += self.decoded_objects.into_inner();
421 stats.expanded_objects += self.expanded_objects.into_inner();
422 self.out.into_inner()
423 }
424 }
425
426 impl gix_object::Find for ExpandedCountingObjects<'_> {
427 fn try_find<'a>(&self, id: &oid, buffer: &'a mut Vec<u8>) -> Result<Option<Data<'a>>, gix_object::find::Error> {
428 let maybe_obj = self.objects.try_find(id, buffer)?;
429 *self.decoded_objects.borrow_mut() += 1;
430 match maybe_obj {
431 None => Ok(None),
432 Some((obj, location)) => {
433 self.objects_count.fetch_add(1, Ordering::Relaxed);
434 *self.expanded_objects.borrow_mut() += 1;
435 self.out.borrow_mut().push(output::Count::from_data(id, location));
436 Ok(Some(obj))
437 }
438 }
439 }
440 }
441}