This repository has no description
0

Configure Feed

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

core / knot2 / third_party / gix-pack / src / index / access.rs
12 kB 312 lines
1use std::{mem::size_of, ops::Range}; 2 3use crate::{ 4 data, 5 index::{self, EntryIndex, FAN_LEN, PrefixLookupResult}, 6}; 7 8const N32_SIZE: usize = size_of::<u32>(); 9const N64_SIZE: usize = size_of::<u64>(); 10const V1_HEADER_SIZE: usize = FAN_LEN * N32_SIZE; 11const V2_HEADER_SIZE: usize = N32_SIZE * 2 + FAN_LEN * N32_SIZE; 12const N32_HIGH_BIT: u32 = 1 << 31; 13 14/// Represents an entry within a pack index file, effectively mapping object [`IDs`][gix_hash::ObjectId] to pack data file locations. 15#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] 16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 17pub struct Entry { 18 /// The ID of the object 19 pub oid: gix_hash::ObjectId, 20 /// The offset to the object's header in the pack data file 21 pub pack_offset: data::Offset, 22 /// The CRC32 hash over all bytes of the pack data entry. 23 /// 24 /// This can be useful for direct copies of pack data entries from one pack to another with insurance there was no bit rot. 25 /// _Note_: Only available in index version 2 or newer 26 pub crc32: Option<u32>, 27} 28 29/// Iteration and access 30impl<T> index::File<T> 31where 32 T: crate::FileData, 33{ 34 fn iter_v1(&self) -> impl Iterator<Item = Entry> + '_ { 35 match self.version { 36 index::Version::V1 => self.data[V1_HEADER_SIZE..] 37 .chunks_exact(N32_SIZE + self.hash_len) 38 .take(self.num_objects as usize) 39 .map(|c| { 40 let (ofs, oid) = c.split_at(N32_SIZE); 41 Entry { 42 oid: gix_hash::ObjectId::from_bytes_or_panic(oid), 43 pack_offset: u64::from(crate::read_u32(ofs)), 44 crc32: None, 45 } 46 }), 47 _ => panic!("Cannot use iter_v1() on index of type {:?}", self.version), 48 } 49 } 50 51 fn iter_v2(&self) -> impl Iterator<Item = Entry> + '_ { 52 let pack64_offset = self.offset_pack_offset64_v2(); 53 let oids = self.data[V2_HEADER_SIZE..] 54 .chunks_exact(self.hash_len) 55 .take(self.num_objects as usize); 56 let crcs = self.data[self.offset_crc32_v2()..] 57 .chunks_exact(N32_SIZE) 58 .take(self.num_objects as usize); 59 let offsets = self.data[self.offset_pack_offset_v2()..] 60 .chunks_exact(N32_SIZE) 61 .take(self.num_objects as usize); 62 assert_eq!(oids.len(), crcs.len()); 63 assert_eq!(crcs.len(), offsets.len()); 64 match self.version { 65 index::Version::V2 => { 66 izip!(oids, crcs, offsets).map(move |(oid, crc32, ofs32)| Entry { 67 oid: gix_hash::ObjectId::from_bytes_or_panic(oid), 68 pack_offset: self.pack_offset_from_offset_v2(ofs32, pack64_offset), 69 crc32: Some(crate::read_u32(crc32)), 70 }) 71 } 72 _ => panic!("Cannot use iter_v2() on index of type {:?}", self.version), 73 } 74 } 75 76 /// Returns the object hash at the given index in our list of (sorted) sha1 hashes. 77 /// The index ranges from 0 to `self.num_objects()` 78 /// 79 /// # Panics 80 /// 81 /// If `index` is out of bounds. 82 pub fn oid_at_index(&self, index: EntryIndex) -> &gix_hash::oid { 83 let index = index as usize; 84 let start = match self.version { 85 index::Version::V2 => V2_HEADER_SIZE + index * self.hash_len, 86 index::Version::V1 => V1_HEADER_SIZE + index * (N32_SIZE + self.hash_len) + N32_SIZE, 87 }; 88 gix_hash::oid::from_bytes_unchecked(&self.data[start..][..self.hash_len]) 89 } 90 91 /// Returns the offset into our pack data file at which to start reading the object at `index`. 92 /// 93 /// # Panics 94 /// 95 /// If `index` is out of bounds. 96 pub fn pack_offset_at_index(&self, index: EntryIndex) -> data::Offset { 97 let index = index as usize; 98 match self.version { 99 index::Version::V2 => { 100 let start = self.offset_pack_offset_v2() + index * N32_SIZE; 101 self.pack_offset_from_offset_v2( 102 &self.data[start..][..N32_SIZE], 103 self.offset_pack_offset64_v2(), 104 ) 105 } 106 index::Version::V1 => { 107 let start = V1_HEADER_SIZE + index * (N32_SIZE + self.hash_len); 108 u64::from(crate::read_u32(&self.data[start..][..N32_SIZE])) 109 } 110 } 111 } 112 113 /// Returns the CRC32 of the object at the given `index`. 114 /// 115 /// _Note_: These are always present for index version 2 or higher. 116 /// # Panics 117 /// 118 /// If `index` is out of bounds. 119 pub fn crc32_at_index(&self, index: EntryIndex) -> Option<u32> { 120 let index = index as usize; 121 match self.version { 122 index::Version::V2 => { 123 let start = self.offset_crc32_v2() + index * N32_SIZE; 124 Some(crate::read_u32(&self.data[start..start + N32_SIZE])) 125 } 126 index::Version::V1 => None, 127 } 128 } 129 130 /// Returns the `index` of the given hash for use with the [`oid_at_index()`][index::File::oid_at_index()], 131 /// [`pack_offset_at_index()`][index::File::pack_offset_at_index()] or [`crc32_at_index()`][index::File::crc32_at_index()]. 132 // NOTE: pretty much the same things as in `multi_index::File::lookup`, change things there 133 // as well. 134 pub fn lookup(&self, id: impl AsRef<gix_hash::oid>) -> Option<EntryIndex> { 135 lookup(id.as_ref(), &self.fan, &|idx| self.oid_at_index(idx)) 136 } 137 138 /// Given a `prefix`, find an object that matches it uniquely within this index and return `Some(Ok(entry_index))`. 139 /// If there is more than one object matching the object `Some(Err(())` is returned. 140 /// 141 /// Finally, if no object matches the index, the return value is `None`. 142 /// 143 /// Pass `candidates` to obtain the set of entry-indices matching `prefix`, with the same return value as 144 /// one would have received if it remained `None`. It will be empty if no object matched the `prefix`. 145 /// 146 // NOTE: pretty much the same things as in `index::File::lookup`, change things there 147 // as well. 148 pub fn lookup_prefix( 149 &self, 150 prefix: gix_hash::Prefix, 151 candidates: Option<&mut Range<EntryIndex>>, 152 ) -> Option<PrefixLookupResult> { 153 lookup_prefix( 154 prefix, 155 candidates, 156 &self.fan, 157 &|idx| self.oid_at_index(idx), 158 self.num_objects, 159 ) 160 } 161 162 /// An iterator over all [`Entries`][Entry] of this index file. 163 pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Entry> + 'a> { 164 match self.version { 165 index::Version::V2 => Box::new(self.iter_v2()), 166 index::Version::V1 => Box::new(self.iter_v1()), 167 } 168 } 169 170 /// Return a vector of ascending offsets into our respective pack data file. 171 /// 172 /// Useful to control an iteration over all pack entries in a cache-friendly way. 173 pub fn sorted_offsets(&self) -> Vec<data::Offset> { 174 let mut ofs: Vec<_> = match self.version { 175 index::Version::V1 => self.iter().map(|e| e.pack_offset).collect(), 176 index::Version::V2 => { 177 let offset32_start = &self.data[self.offset_pack_offset_v2()..]; 178 let offsets32 = offset32_start 179 .chunks_exact(N32_SIZE) 180 .take(self.num_objects as usize); 181 assert_eq!(self.num_objects as usize, offsets32.len()); 182 let pack_offset_64_start = self.offset_pack_offset64_v2(); 183 offsets32 184 .map(|offset| self.pack_offset_from_offset_v2(offset, pack_offset_64_start)) 185 .collect() 186 } 187 }; 188 ofs.sort_unstable(); 189 ofs 190 } 191 192 #[inline] 193 fn offset_crc32_v2(&self) -> usize { 194 V2_HEADER_SIZE + self.num_objects as usize * self.hash_len 195 } 196 197 #[inline] 198 fn offset_pack_offset_v2(&self) -> usize { 199 self.offset_crc32_v2() + self.num_objects as usize * N32_SIZE 200 } 201 202 #[inline] 203 fn offset_pack_offset64_v2(&self) -> usize { 204 self.offset_pack_offset_v2() + self.num_objects as usize * N32_SIZE 205 } 206 207 #[inline] 208 fn pack_offset_from_offset_v2(&self, offset: &[u8], pack64_offset: usize) -> data::Offset { 209 debug_assert_eq!(self.version, index::Version::V2); 210 let ofs32 = crate::read_u32(offset); 211 if (ofs32 & N32_HIGH_BIT) == N32_HIGH_BIT { 212 let from = pack64_offset + (ofs32 ^ N32_HIGH_BIT) as usize * N64_SIZE; 213 crate::read_u64(&self.data[from..][..N64_SIZE]) 214 } else { 215 u64::from(ofs32) 216 } 217 } 218} 219 220pub(crate) fn lookup_prefix<'a>( 221 prefix: gix_hash::Prefix, 222 candidates: Option<&mut Range<EntryIndex>>, 223 fan: &[u32; FAN_LEN], 224 oid_at_index: &dyn Fn(EntryIndex) -> &'a gix_hash::oid, 225 num_objects: u32, 226) -> Option<PrefixLookupResult> { 227 let first_byte = prefix.as_oid().first_byte() as usize; 228 let mut upper_bound = fan[first_byte]; 229 let mut lower_bound = if first_byte != 0 { 230 fan[first_byte - 1] 231 } else { 232 0 233 }; 234 235 // Bisect using indices 236 while lower_bound < upper_bound { 237 let mid = u32::midpoint(lower_bound, upper_bound); 238 let mid_sha = oid_at_index(mid); 239 240 use std::cmp::Ordering::*; 241 match prefix.cmp_oid(mid_sha) { 242 Less => upper_bound = mid, 243 Equal => match candidates { 244 Some(candidates) => { 245 let first_past_entry = ((0..mid).rev()) 246 .take_while(|prev| prefix.cmp_oid(oid_at_index(*prev)) == Equal) 247 .last(); 248 249 let last_future_entry = ((mid + 1)..num_objects) 250 .take_while(|next| prefix.cmp_oid(oid_at_index(*next)) == Equal) 251 .last(); 252 253 *candidates = match (first_past_entry, last_future_entry) { 254 (Some(first), Some(last)) => first..last + 1, 255 (Some(first), None) => first..mid + 1, 256 (None, Some(last)) => mid..last + 1, 257 (None, None) => mid..mid + 1, 258 }; 259 260 return if candidates.len() > 1 { 261 Some(Err(())) 262 } else { 263 Some(Ok(mid)) 264 }; 265 } 266 None => { 267 let next = mid + 1; 268 if next < num_objects && prefix.cmp_oid(oid_at_index(next)) == Equal { 269 return Some(Err(())); 270 } 271 if mid != 0 && prefix.cmp_oid(oid_at_index(mid - 1)) == Equal { 272 return Some(Err(())); 273 } 274 return Some(Ok(mid)); 275 } 276 }, 277 Greater => lower_bound = mid + 1, 278 } 279 } 280 281 if let Some(candidates) = candidates { 282 *candidates = 0..0; 283 } 284 None 285} 286 287pub(crate) fn lookup<'a>( 288 id: &gix_hash::oid, 289 fan: &[u32; FAN_LEN], 290 oid_at_index: &dyn Fn(EntryIndex) -> &'a gix_hash::oid, 291) -> Option<EntryIndex> { 292 let first_byte = id.first_byte() as usize; 293 let mut upper_bound = fan[first_byte]; 294 let mut lower_bound = if first_byte != 0 { 295 fan[first_byte - 1] 296 } else { 297 0 298 }; 299 300 while lower_bound < upper_bound { 301 let mid = u32::midpoint(lower_bound, upper_bound); 302 let mid_sha = oid_at_index(mid); 303 304 use std::cmp::Ordering::*; 305 match id.cmp(mid_sha) { 306 Less => upper_bound = mid, 307 Equal => return Some(mid), 308 Greater => lower_bound = mid + 1, 309 } 310 } 311 None 312}