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