This repository has no description
1use std::path::{Path, PathBuf};
2
3use knot_types::{ObjectCount, Oid};
4
5use crate::error::GitError;
6use crate::objects::{Haves, Wants};
7use crate::repo::Repo;
8
9mod bitset;
10mod midx;
11mod reader;
12mod revindex;
13mod writer;
14
15use bitset::Bitset;
16use revindex::{Order, OrderTable};
17
18knot_types::scalar_newtype! {
19 pub(crate) struct BitPosition(u32);
20 pub(crate) struct IndexPosition(u32) => ordered;
21 pub(crate) struct BitmapEntryOffset(u64);
22}
23
24pub fn write_bitmap(repo: &Repo, pack_idx: &Path) -> Result<bool, GitError> {
25 writer::write(repo, pack_idx)
26}
27
28pub fn write_midx_bitmap(repo: &Repo) -> Result<bool, GitError> {
29 midx::write(repo)
30}
31
32pub fn reachable_via_bitmap(
33 repo: &Repo,
34 wants: Wants<'_>,
35 haves: Haves<'_>,
36) -> Result<Option<Vec<Oid>>, GitError> {
37 if let Some((_, found)) = single_pack(repo, wants, haves)? {
38 return Ok(Some(found));
39 }
40 midx::reachable(repo, wants, haves)
41}
42
43fn single_pack(
44 repo: &Repo,
45 wants: Wants<'_>,
46 haves: Haves<'_>,
47) -> Result<Option<(ObjectCount, Vec<Oid>)>, GitError> {
48 let kind = repo.object_format().kind();
49 let Some(pack_idx) = bitmapped_pack(&repo.objects_dir()) else {
50 return Ok(None);
51 };
52 let Ok(bytes) = std::fs::read(pack_idx.with_extension("bitmap")) else {
53 return Ok(None);
54 };
55 let index = gix_pack::index::File::at(&pack_idx, kind)
56 .map_err(|error| GitError::Backend(format!("open pack index: {error}")))?;
57 let rev = OrderTable::from_index(&index);
58 let count = rev.len();
59 let maps = reader::parse(&bytes, kind, count)?;
60 Ok(resolve(repo, &rev, &maps, wants, haves)?
61 .map(|reachable| (ObjectCount::new(count), reachable)))
62}
63
64pub(super) fn resolve(
65 repo: &Repo,
66 rev: &impl Order,
67 maps: &reader::Bitmaps<'_>,
68 wants: Wants<'_>,
69 haves: Haves<'_>,
70) -> Result<Option<Vec<Oid>>, GitError> {
71 let (Some(want), Some(have)) = (
72 accumulate(repo, rev, maps, wants.as_slice())?,
73 accumulate(repo, rev, maps, haves.as_slice())?,
74 ) else {
75 return Ok(None);
76 };
77 let reachable = want
78 .difference_indices(&have)
79 .map(|bit| rev.oid_at_bit(bit))
80 .collect();
81 Ok(Some(reachable))
82}
83
84pub fn verbatim_clone_pack(
85 repo: &Repo,
86 wants: Wants<'_>,
87) -> Result<Option<std::fs::File>, GitError> {
88 let Some(pack_idx) = bitmapped_pack(&repo.objects_dir()) else {
89 return Ok(None);
90 };
91 let Some((num_objects, reachable)) = single_pack(repo, wants, Haves::new(&[]))? else {
92 return Ok(None);
93 };
94 if ObjectCount::new(reachable.len()) != num_objects {
95 return Ok(None);
96 }
97 Ok(std::fs::File::open(pack_idx.with_extension("pack")).ok())
98}
99
100fn bitmapped_pack(objects_dir: &Path) -> Option<PathBuf> {
101 let pack_dir = objects_dir.join("pack");
102 let mut idxs: Vec<PathBuf> = std::fs::read_dir(&pack_dir)
103 .ok()?
104 .filter_map(Result::ok)
105 .map(|entry| entry.path())
106 .filter(|path| path.extension().is_some_and(|ext| ext == "idx"))
107 .collect();
108 idxs.sort();
109 idxs.into_iter()
110 .find(|idx| idx.with_extension("bitmap").exists())
111}
112
113fn accumulate(
114 repo: &Repo,
115 rev: &impl Order,
116 maps: &reader::Bitmaps<'_>,
117 oids: &[Oid],
118) -> Result<Option<Bitset>, GitError> {
119 oids.iter()
120 .try_fold(Some(Bitset::zeros(rev.len())), |acc, oid| {
121 let Some(mut acc) = acc else {
122 return Ok(None);
123 };
124 match contribution(repo, rev, maps, *oid)? {
125 Some(part) => {
126 acc.union_with(&part);
127 Ok(Some(acc))
128 }
129 None => Ok(None),
130 }
131 })
132}
133
134fn contribution(
135 repo: &Repo,
136 rev: &impl Order,
137 maps: &reader::Bitmaps<'_>,
138 oid: Oid,
139) -> Result<Option<Bitset>, GitError> {
140 let Some((commit, tags)) = peel_commit_chain(repo, oid) else {
141 return Ok(None);
142 };
143 let Some(position) = rev.index_of(commit) else {
144 return Ok(None);
145 };
146 let Some(base) = maps.bitmap(position)? else {
147 return Ok(None);
148 };
149 let bits = tags.iter().try_fold(base, |mut bits, tag| {
150 let position = rev.index_of(*tag)?;
151 bits.set(rev.bit_at_index(position));
152 Some(bits)
153 });
154 Ok(bits)
155}
156
157fn peel_commit_chain(repo: &Repo, oid: Oid) -> Option<(Oid, Vec<Oid>)> {
158 let object = repo.git().find_object(oid.object_id()).ok()?;
159 match object.kind {
160 gix::object::Kind::Commit => Some((oid, Vec::new())),
161 gix::object::Kind::Tag => {
162 let target = object.try_into_tag().ok()?.target_id().ok()?.detach();
163 peel_commit_chain(repo, Oid::from(target)).map(|(commit, mut tags)| {
164 tags.push(oid);
165 (commit, tags)
166 })
167 }
168 _ => None,
169 }
170}