1use std::collections::{HashSet, VecDeque};
7
8use crate::error::Error;
9use crate::ewah_bitmap::Bitmap;
10use crate::objects::{
11 empty_oid_set, oid_hash_set, parse_tag, ObjectId, ObjectKind, OidHashSet, TreeRefIter,
12};
13use crate::pack::read_object_info_from_packs;
14use crate::pack_bitmap::{BitmapError, BitmapIndex};
15use crate::repo::Repository;
16use crate::rev_list::{shallow_boundary_oids, FilterObjectKind, MissingAction, ObjectFilter};
17
18#[derive(Debug, Clone, Copy)]
20pub struct ReachabilityQuery<'a> {
21 pub wants: &'a [ObjectId],
23 pub haves: &'a [ObjectId],
25 pub filter: Option<&'a ObjectFilter>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
31#[error("bitmap reachability walk is not supported for this repository or query")]
32pub struct BitmapWalkUnsupported;
33
34#[derive(Debug, thiserror::Error)]
36pub enum BitmapWalkError {
37 #[error(transparent)]
39 Unsupported(#[from] BitmapWalkUnsupported),
40 #[error(transparent)]
42 Bitmap(#[from] BitmapError),
43 #[error(transparent)]
45 Repository(#[from] Error),
46}
47
48#[derive(Clone)]
50pub struct ReachableSet {
51 index: std::sync::Arc<BitmapIndex>,
52 bits: Bitmap,
53 extended: Vec<(ObjectId, ObjectKind)>,
54 extended_set: OidHashSet,
55}
56
57impl ReachableSet {
58 #[must_use]
60 pub fn count(&self) -> usize {
61 self.bits.count_ones() + self.extended.len()
62 }
63
64 #[must_use]
66 pub fn contains(&self, oid: &ObjectId) -> bool {
67 if self.extended_set.contains(oid) {
68 return true;
69 }
70 self.index
71 .position_of(oid)
72 .is_some_and(|pos| self.bits.get(pos as usize))
73 }
74
75 pub fn count_by_kind(&self, kind: ObjectKind) -> std::result::Result<usize, BitmapWalkError> {
81 self.count_by_kind_popcount(kind)
82 }
83
84 pub fn count_by_kind_popcount(
90 &self,
91 kind: ObjectKind,
92 ) -> std::result::Result<usize, BitmapWalkError> {
93 let mut type_bits = Bitmap::new();
94 self.index
95 .type_bitmap(kind)
96 .expand_into_bitmap(&mut type_bits)?;
97 type_bits.and_assign(&self.bits);
98 Ok(type_bits.count_ones() + self.extended.iter().filter(|(_, k)| *k == kind).count())
99 }
100
101 pub fn iter_by_kind(&self, kind: ObjectKind) -> impl Iterator<Item = ObjectId> + '_ {
103 let positions = self.type_filtered_positions(kind);
104 positions
105 .into_iter()
106 .filter_map(|pos| self.index.oid_at(pos).ok())
107 .chain(
108 self.extended
109 .iter()
110 .filter_map(move |(oid, k)| (*k == kind).then_some(*oid)),
111 )
112 }
113
114 #[must_use]
116 pub fn object_ids(&self) -> HashSet<ObjectId> {
117 let mut out = oid_hash_set();
118 for pos in self.bits.set_bits() {
119 if let Ok(oid) = self.index.oid_at(u32::try_from(pos).unwrap_or(u32::MAX)) {
120 out.insert(oid);
121 }
122 }
123 for (oid, _) in &self.extended {
124 out.insert(*oid);
125 }
126 out.into_iter().collect()
127 }
128
129 #[must_use]
131 pub(crate) fn reuse_send_bits(&self) -> &Bitmap {
132 &self.bits
133 }
134
135 #[must_use]
137 pub fn bitmap_index(&self) -> &std::sync::Arc<BitmapIndex> {
138 &self.index
139 }
140
141 #[must_use]
143 pub fn extended_objects(&self) -> &[(ObjectId, ObjectKind)] {
144 &self.extended
145 }
146
147 pub fn iter_grouped_by_kind(&self) -> impl Iterator<Item = ObjectId> + '_ {
149 [
150 ObjectKind::Commit,
151 ObjectKind::Tree,
152 ObjectKind::Blob,
153 ObjectKind::Tag,
154 ]
155 .into_iter()
156 .flat_map(|kind| self.iter_by_kind(kind))
157 }
158
159 pub(crate) fn reachable_commit_bitmap_positions(
161 &self,
162 ) -> std::result::Result<Vec<u32>, BitmapWalkError> {
163 let Ok(mut type_bits) = (|| {
164 let mut tb = Bitmap::new();
165 self.index
166 .type_bitmap(ObjectKind::Commit)
167 .expand_into_bitmap(&mut tb)?;
168 Ok::<_, BitmapError>(tb)
169 })() else {
170 return Ok(Vec::new());
171 };
172 type_bits.and_assign(&self.bits);
173 Ok(type_bits
174 .set_bits()
175 .map(|p| u32::try_from(p).unwrap_or(u32::MAX))
176 .collect())
177 }
178
179 fn type_filtered_positions(&self, kind: ObjectKind) -> Vec<u32> {
180 let Ok(mut type_bits) = (|| {
181 let mut tb = Bitmap::new();
182 self.index.type_bitmap(kind).expand_into_bitmap(&mut tb)?;
183 Ok::<_, BitmapError>(tb)
184 })() else {
185 return Vec::new();
186 };
187 type_bits.and_assign(&self.bits);
188 type_bits
189 .set_bits()
190 .map(|p| u32::try_from(p).unwrap_or(u32::MAX))
191 .collect()
192 }
193
194 fn subtract(&mut self, other: &Self) {
195 self.bits.and_not_assign(&other.bits);
196 self.extended.retain(|(oid, _)| !other.contains(oid));
197 self.extended_set
198 .retain(|oid| !other.extended_set.contains(oid));
199 }
200}
201
202#[must_use]
204pub fn bitmap_filter_supported(filter: Option<&ObjectFilter>) -> bool {
205 match filter {
206 None => true,
207 Some(ObjectFilter::SparseOid(_)) => false,
208 Some(ObjectFilter::TreeDepth(depth)) if *depth > 0 => false,
209 Some(ObjectFilter::Combine(parts)) => {
210 parts.iter().all(|p| bitmap_filter_supported(Some(p)))
211 }
212 Some(ObjectFilter::BlobNone)
213 | Some(ObjectFilter::BlobLimit(_))
214 | Some(ObjectFilter::TreeDepth(0))
215 | Some(ObjectFilter::ObjectType(_)) => true,
216 Some(ObjectFilter::TreeDepth(_)) => false,
217 }
218}
219
220pub(crate) struct PeeledTips {
222 pub commits: Vec<ObjectId>,
223 pub tags: Vec<ObjectId>,
224 pub extra_objects: Vec<ObjectId>,
225}
226
227fn peel_tips(
228 repo: &Repository,
229 tips: &[ObjectId],
230 missing: MissingAction,
231) -> std::result::Result<PeeledTips, BitmapWalkError> {
232 let mut out = PeeledTips {
233 commits: Vec::new(),
234 tags: Vec::new(),
235 extra_objects: Vec::new(),
236 };
237 for &tip in tips {
238 peel_one(repo, tip, missing, &mut out)?;
239 }
240 Ok(out)
241}
242
243fn peel_one(
244 repo: &Repository,
245 mut oid: ObjectId,
246 missing: MissingAction,
247 out: &mut PeeledTips,
248) -> std::result::Result<(), BitmapWalkError> {
249 loop {
250 let object = match repo.odb().read(&oid) {
251 Ok(obj) => obj,
252 Err(Error::ObjectNotFound(_)) if missing != MissingAction::Error => return Ok(()),
253 Err(err) => return Err(err.into()),
254 };
255 match object.kind {
256 ObjectKind::Commit => {
257 out.commits.push(oid);
258 return Ok(());
259 }
260 ObjectKind::Tag => {
261 out.tags.push(oid);
262 let tag = parse_tag(&object.data)?;
263 oid = tag.object;
264 }
265 ObjectKind::Tree | ObjectKind::Blob => {
266 out.extra_objects.push(oid);
267 return Ok(());
268 }
269 }
270 }
271}
272
273impl BitmapIndex {
274 pub fn reachability(
280 self: &std::sync::Arc<Self>,
281 repo: &Repository,
282 query: ReachabilityQuery<'_>,
283 missing: MissingAction,
284 ) -> std::result::Result<ReachableSet, BitmapWalkError> {
285 if !shallow_boundary_oids(repo.git_dir()).is_empty()
286 || crate::shallow::shallow_file_path(repo.git_dir()).is_file()
287 {
288 return Err(BitmapWalkUnsupported.into());
289 }
290 if !bitmap_filter_supported(query.filter) {
291 return Err(BitmapWalkUnsupported.into());
292 }
293
294 let haves = find_objects_union(repo, self, query.haves, missing, false)?;
295 let mut wants = find_objects_union(repo, self, query.wants, missing, false)?;
296 wants.subtract(&haves);
297 apply_filter(repo, self, &mut wants, query.filter, query.wants)?;
298 Ok(wants)
299 }
300
301 pub fn verify_commit(
310 self: &std::sync::Arc<Self>,
311 repo: &Repository,
312 commit: &ObjectId,
313 missing: MissingAction,
314 ) -> std::result::Result<bool, BitmapWalkError> {
315 let Some(stored) = self.commit_bitmap(commit) else {
316 return Ok(false);
317 };
318 let walked = find_objects_union(repo, self, &[*commit], missing, true)?;
319 let stored_set: HashSet<u32> = stored.positions().collect();
320 let walked_set: HashSet<u32> = walked
321 .bits
322 .set_bits()
323 .map(|p| u32::try_from(p).unwrap_or(u32::MAX))
324 .collect();
325 Ok(stored_set == walked_set && walked.extended.is_empty())
326 }
327
328 pub(crate) fn reachability_with_peeled_wants(
330 self: &std::sync::Arc<Self>,
331 repo: &Repository,
332 wants: PeeledTips,
333 haves: &[ObjectId],
334 filter: Option<&ObjectFilter>,
335 missing: MissingAction,
336 filter_want_tips: &[ObjectId],
337 ) -> std::result::Result<ReachableSet, BitmapWalkError> {
338 if !shallow_boundary_oids(repo.git_dir()).is_empty()
339 || crate::shallow::shallow_file_path(repo.git_dir()).is_file()
340 {
341 return Err(BitmapWalkUnsupported.into());
342 }
343 if !bitmap_filter_supported(filter) {
344 return Err(BitmapWalkUnsupported.into());
345 }
346
347 let haves = find_objects_union(repo, self, haves, missing, false)?;
348 let mut want_set = find_objects_peeled(repo, self, wants, missing, false)?;
349 want_set.subtract(&haves);
350 apply_filter(repo, self, &mut want_set, filter, filter_want_tips)?;
351 Ok(want_set)
352 }
353}
354
355fn find_objects_union(
356 repo: &Repository,
357 index: &std::sync::Arc<BitmapIndex>,
358 tips: &[ObjectId],
359 missing: MissingAction,
360 ignore_stored_commit_bitmaps: bool,
361) -> std::result::Result<ReachableSet, BitmapWalkError> {
362 if tips.is_empty() {
363 return Ok(ReachableSet {
364 index: std::sync::Arc::clone(index),
365 bits: Bitmap::new(),
366 extended: Vec::new(),
367 extended_set: oid_hash_set(),
368 });
369 }
370 find_objects(repo, index, tips, missing, ignore_stored_commit_bitmaps)
371}
372
373fn find_objects(
374 repo: &Repository,
375 index: &std::sync::Arc<BitmapIndex>,
376 tips: &[ObjectId],
377 missing: MissingAction,
378 ignore_stored_commit_bitmaps: bool,
379) -> std::result::Result<ReachableSet, BitmapWalkError> {
380 let peeled = peel_tips(repo, tips, missing)?;
381 find_objects_peeled(repo, index, peeled, missing, ignore_stored_commit_bitmaps)
382}
383
384fn find_objects_peeled(
385 repo: &Repository,
386 index: &std::sync::Arc<BitmapIndex>,
387 peeled: PeeledTips,
388 missing: MissingAction,
389 ignore_stored_commit_bitmaps: bool,
390) -> std::result::Result<ReachableSet, BitmapWalkError> {
391 let mut bits = Bitmap::new();
392 let mut extended = Vec::new();
393 let mut extended_set = oid_hash_set();
394 let mut pending_commits = VecDeque::new();
395
396 for commit in peeled.commits {
397 if try_or_commit_bitmap(index, &mut bits, &commit, ignore_stored_commit_bitmaps)? {
398 continue;
399 }
400 if already_marked(index, &bits, &extended_set, &commit) {
401 continue;
402 }
403 pending_commits.push_back(commit);
404 }
405
406 for tag in peeled.tags {
407 mark_object(
408 repo,
409 index,
410 &mut bits,
411 &mut extended,
412 &mut extended_set,
413 tag,
414 ObjectKind::Tag,
415 missing,
416 )?;
417 }
418 for oid in peeled.extra_objects {
419 let kind = repo
420 .odb()
421 .read_info(&oid)
422 .map_err(BitmapWalkError::from)?
423 .kind;
424 mark_object(
425 repo,
426 index,
427 &mut bits,
428 &mut extended,
429 &mut extended_set,
430 oid,
431 kind,
432 missing,
433 )?;
434 }
435
436 let mut seen_commits = oid_hash_set();
437 while let Some(commit) = pending_commits.pop_front() {
438 if !seen_commits.insert(commit) {
439 continue;
440 }
441 if try_or_commit_bitmap(index, &mut bits, &commit, ignore_stored_commit_bitmaps)? {
442 if let Ok(parents) = repo.odb().read_commit(&commit).map(|c| c.parents) {
443 for parent in parents {
444 seen_commits.insert(parent);
445 }
446 }
447 continue;
448 }
449 mark_index_or_extended(
450 index,
451 &mut bits,
452 &mut extended,
453 &mut extended_set,
454 commit,
455 ObjectKind::Commit,
456 );
457 let commit_data = match repo.odb().read_commit(&commit) {
458 Ok(c) => c,
459 Err(Error::ObjectNotFound(_)) => continue,
460 Err(err) => return Err(err.into()),
461 };
462 walk_tree(
463 repo,
464 index,
465 &mut bits,
466 &mut extended,
467 &mut extended_set,
468 commit_data.tree,
469 missing,
470 )?;
471 for parent in commit_data.parents {
472 if !seen_commits.contains(&parent) {
473 pending_commits.push_back(parent);
474 }
475 }
476 }
477
478 Ok(ReachableSet {
479 index: std::sync::Arc::clone(index),
480 bits,
481 extended,
482 extended_set,
483 })
484}
485
486fn try_or_commit_bitmap(
487 index: &BitmapIndex,
488 bits: &mut Bitmap,
489 commit: &ObjectId,
490 ignore_stored: bool,
491) -> std::result::Result<bool, BitmapWalkError> {
492 if ignore_stored {
493 return Ok(false);
494 }
495 let Some(bm) = index.commit_bitmap(commit) else {
496 return Ok(false);
497 };
498 bm.or_into(bits);
499 Ok(true)
500}
501
502fn already_marked(
503 index: &BitmapIndex,
504 bits: &Bitmap,
505 extended: &OidHashSet,
506 oid: &ObjectId,
507) -> bool {
508 if extended.contains(oid) {
509 return true;
510 }
511 index
512 .position_of(oid)
513 .is_some_and(|pos| bits.get(pos as usize))
514}
515
516fn mark_index_or_extended(
517 index: &BitmapIndex,
518 bits: &mut Bitmap,
519 extended: &mut Vec<(ObjectId, ObjectKind)>,
520 extended_set: &mut OidHashSet,
521 oid: ObjectId,
522 kind: ObjectKind,
523) {
524 if let Some(pos) = index.position_of(&oid) {
525 bits.set(pos as usize);
526 } else if extended_set.insert(oid) {
527 extended.push((oid, kind));
528 }
529}
530
531#[allow(clippy::too_many_arguments)]
532fn mark_object(
533 repo: &Repository,
534 index: &BitmapIndex,
535 bits: &mut Bitmap,
536 extended: &mut Vec<(ObjectId, ObjectKind)>,
537 extended_set: &mut OidHashSet,
538 oid: ObjectId,
539 kind: ObjectKind,
540 missing: MissingAction,
541) -> std::result::Result<(), BitmapWalkError> {
542 if already_marked(index, bits, extended_set, &oid) {
543 return Ok(());
544 }
545 match kind {
546 ObjectKind::Commit | ObjectKind::Tag | ObjectKind::Blob => {
547 mark_index_or_extended(index, bits, extended, extended_set, oid, kind);
548 }
549 ObjectKind::Tree => {
550 walk_tree(repo, index, bits, extended, extended_set, oid, missing)?;
551 }
552 }
553 Ok(())
554}
555
556fn walk_tree(
557 repo: &Repository,
558 index: &BitmapIndex,
559 bits: &mut Bitmap,
560 extended: &mut Vec<(ObjectId, ObjectKind)>,
561 extended_set: &mut OidHashSet,
562 tree: ObjectId,
563 missing: MissingAction,
564) -> std::result::Result<(), BitmapWalkError> {
565 if already_marked(index, bits, extended_set, &tree) {
566 return Ok(());
567 }
568 let object = match repo.odb().read(&tree) {
569 Ok(obj) => obj,
570 Err(Error::ObjectNotFound(_)) if missing != MissingAction::Error => return Ok(()),
571 Err(err) => return Err(err.into()),
572 };
573 if object.kind != ObjectKind::Tree {
574 return Ok(());
575 }
576 mark_index_or_extended(index, bits, extended, extended_set, tree, ObjectKind::Tree);
577 let oid_len = repo.odb().hash_algo().len();
578 for entry in TreeRefIter::with_oid_len(&object.data, oid_len) {
579 let entry = entry?;
580 if entry.mode == 0o160000 {
582 continue;
583 }
584 if entry.mode == 0o040000 {
585 walk_tree(
586 repo,
587 index,
588 bits,
589 extended,
590 extended_set,
591 entry.oid,
592 missing,
593 )?;
594 } else {
595 mark_index_or_extended(
596 index,
597 bits,
598 extended,
599 extended_set,
600 entry.oid,
601 ObjectKind::Blob,
602 );
603 }
604 }
605 Ok(())
606}
607
608fn apply_filter(
609 repo: &Repository,
610 index: &BitmapIndex,
611 set: &mut ReachableSet,
612 filter: Option<&ObjectFilter>,
613 want_tips: &[ObjectId],
614) -> std::result::Result<(), BitmapWalkError> {
615 let Some(filter) = filter else {
616 return Ok(());
617 };
618 apply_filter_one(repo, index, set, filter, want_tips)?;
619 Ok(())
620}
621
622fn apply_filter_one(
623 repo: &Repository,
624 index: &BitmapIndex,
625 set: &mut ReachableSet,
626 filter: &ObjectFilter,
627 want_tips: &[ObjectId],
628) -> std::result::Result<(), BitmapWalkError> {
629 match filter {
630 ObjectFilter::Combine(parts) => {
631 for part in parts {
632 apply_filter_one(repo, index, set, part, want_tips)?;
633 }
634 }
635 ObjectFilter::BlobNone => {
636 let keep = explicit_filter_roots(repo, index, want_tips)?.blobs;
637 clear_type_except(index, &mut set.bits, ObjectKind::Blob, &keep);
638 set.extended
639 .retain(|(oid, kind)| *kind != ObjectKind::Blob || keep.contains(oid));
640 rebuild_extended_set(set);
641 }
642 ObjectFilter::BlobLimit(limit) => {
643 let tips = tip_blob_oids(repo, index, want_tips)?;
644 filter_blob_limit(repo, index, &mut set.bits, &tips, *limit)?;
645 set.extended.retain(|(oid, kind)| {
646 if tips.contains(oid) {
647 return true;
648 }
649 *kind != ObjectKind::Blob || blob_under_limit(repo, oid, *limit)
650 });
651 rebuild_extended_set(set);
652 }
653 ObjectFilter::TreeDepth(0) => {
654 let roots = explicit_filter_roots(repo, index, want_tips)?;
655 clear_type_except(index, &mut set.bits, ObjectKind::Tree, &roots.trees);
656 clear_type_except(index, &mut set.bits, ObjectKind::Blob, &roots.blobs);
657 set.extended.retain(|(oid, kind)| {
658 matches!(kind, ObjectKind::Commit | ObjectKind::Tag)
659 || roots.trees.contains(oid)
660 || roots.blobs.contains(oid)
661 });
662 rebuild_extended_set(set);
663 }
664 ObjectFilter::ObjectType(kind) => {
665 keep_only_object_type(set, *kind);
666 }
667 ObjectFilter::SparseOid(_) | ObjectFilter::TreeDepth(_) => {
668 return Err(BitmapWalkUnsupported.into());
669 }
670 }
671 Ok(())
672}
673
674fn rebuild_extended_set(set: &mut ReachableSet) {
675 set.extended_set = set.extended.iter().map(|(oid, _)| *oid).collect();
676}
677
678fn blob_under_limit(repo: &Repository, oid: &ObjectId, limit: u64) -> bool {
679 match read_object_info_from_packs(repo.odb().objects_dir(), oid) {
680 Ok(info) => info.size < limit,
681 Err(_) => repo
682 .odb()
683 .read_info(oid)
684 .map(|info| info.size < limit)
685 .unwrap_or(false),
686 }
687}
688
689fn clear_type(index: &BitmapIndex, bits: &mut Bitmap, kind: ObjectKind) {
690 clear_type_except(index, bits, kind, empty_oid_set());
691}
692
693fn clear_type_except(index: &BitmapIndex, bits: &mut Bitmap, kind: ObjectKind, keep: &OidHashSet) {
694 if let Ok(type_bits) = expand_type(index, kind) {
695 for pos in type_bits.set_bits().collect::<Vec<_>>() {
696 let pos_u32 = u32::try_from(pos).unwrap_or(u32::MAX);
697 if let Ok(oid) = index.oid_at(pos_u32) {
698 if keep.contains(&oid) {
699 continue;
700 }
701 }
702 bits.clear(pos);
703 }
704 }
705}
706
707struct ExplicitFilterRoots {
708 trees: OidHashSet,
709 blobs: OidHashSet,
710}
711
712fn explicit_filter_roots(
713 repo: &Repository,
714 index: &BitmapIndex,
715 tips: &[ObjectId],
716) -> std::result::Result<ExplicitFilterRoots, BitmapWalkError> {
717 let peeled = peel_tips(repo, tips, MissingAction::Error)?;
718 let mut trees = oid_hash_set();
719 let mut blobs = oid_hash_set();
720 for oid in peeled.extra_objects {
721 match repo
722 .odb()
723 .read_info(&oid)
724 .map_err(BitmapWalkError::from)?
725 .kind
726 {
727 ObjectKind::Tree => {
728 trees.insert(oid);
729 }
730 ObjectKind::Blob => {
731 blobs.insert(oid);
732 }
733 ObjectKind::Commit | ObjectKind::Tag => {}
734 }
735 }
736 for oid in tips {
737 if let Some(pos) = index.position_of(oid) {
738 if index
739 .type_bitmap(ObjectKind::Tree)
740 .contains(pos)
741 .unwrap_or(false)
742 {
743 trees.insert(*oid);
744 }
745 if index
746 .type_bitmap(ObjectKind::Blob)
747 .contains(pos)
748 .unwrap_or(false)
749 {
750 blobs.insert(*oid);
751 }
752 }
753 }
754 Ok(ExplicitFilterRoots { trees, blobs })
755}
756
757fn keep_only_object_type(set: &mut ReachableSet, kind: FilterObjectKind) {
758 let object_kind = match kind {
759 FilterObjectKind::Blob => ObjectKind::Blob,
760 FilterObjectKind::Tree => ObjectKind::Tree,
761 FilterObjectKind::Commit => ObjectKind::Commit,
762 FilterObjectKind::Tag => ObjectKind::Tag,
763 };
764 for k in [
765 ObjectKind::Commit,
766 ObjectKind::Tree,
767 ObjectKind::Blob,
768 ObjectKind::Tag,
769 ] {
770 if k != object_kind {
771 clear_type(&set.index, &mut set.bits, k);
772 }
773 }
774 set.extended.retain(|(_, k)| *k == object_kind);
775 rebuild_extended_set(set);
776}
777
778fn expand_type(
779 index: &BitmapIndex,
780 kind: ObjectKind,
781) -> std::result::Result<Bitmap, BitmapWalkError> {
782 let mut out = Bitmap::new();
783 index
784 .type_bitmap(kind)
785 .expand_into_bitmap(&mut out)
786 .map_err(BitmapWalkError::from)?;
787 Ok(out)
788}
789
790fn tip_blob_oids(
791 repo: &Repository,
792 index: &BitmapIndex,
793 tips: &[ObjectId],
794) -> std::result::Result<OidHashSet, BitmapWalkError> {
795 Ok(explicit_filter_roots(repo, index, tips)?.blobs)
796}
797
798fn filter_blob_limit(
799 repo: &Repository,
800 index: &BitmapIndex,
801 bits: &mut Bitmap,
802 tip_blobs: &OidHashSet,
803 limit: u64,
804) -> std::result::Result<(), BitmapWalkError> {
805 let mut blob_type = expand_type(index, ObjectKind::Blob)?;
806 blob_type.and_assign(bits);
807 let objects_dir = repo.odb().objects_dir();
808 for pos in blob_type.set_bits().collect::<Vec<_>>() {
809 let pos_u32 = u32::try_from(pos).unwrap_or(u32::MAX);
810 let Ok(oid) = index.oid_at(pos_u32) else {
811 continue;
812 };
813 if tip_blobs.contains(&oid) {
814 continue;
815 }
816 let size = read_object_info_from_packs(objects_dir, &oid)
817 .map_err(BitmapWalkError::from)?
818 .size;
819 if size >= limit {
820 bits.clear(pos);
821 }
822 }
823 Ok(())
824}