This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-git / src / bitmap / mod.rs
4.9 kB 170 lines
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}