1use std::ops::Deref;
2
3use gix_pack::cache::DecodeEntry;
4
5use crate::store::{handle, load_index};
6
7pub(crate) mod error {
8 use crate::{loose, pack};
9
10 #[derive(thiserror::Error, Debug)]
12 #[expect(missing_docs)]
13 pub enum Error {
14 #[error("An error occurred while obtaining an object from the loose object store")]
15 Loose(#[from] loose::find::Error),
16 #[error("An error occurred while obtaining an object from the packed object store")]
17 Pack(#[from] pack::data::decode::Error),
18 #[error(transparent)]
19 LoadIndex(#[from] crate::store::load_index::Error),
20 #[error(transparent)]
21 LoadPack(#[from] std::io::Error),
22 #[error(transparent)]
23 EntryType(#[from] gix_pack::data::entry::decode::Error),
24 #[error("Reached recursion limit of {} while resolving ref delta bases for {}", .max_depth, .id)]
25 DeltaBaseRecursionLimit {
26 max_depth: usize,
28 id: gix_hash::ObjectId,
30 },
31 #[error("The base object {} could not be found but is required to decode {}", .base_id, .id)]
32 DeltaBaseMissing {
33 base_id: gix_hash::ObjectId,
35 id: gix_hash::ObjectId,
37 },
38 #[error("An error occurred when looking up a ref delta base object {} to decode {}", .base_id, .id)]
39 DeltaBaseLookup {
40 #[source]
41 err: Box<Self>,
42 base_id: gix_hash::ObjectId,
44 id: gix_hash::ObjectId,
46 },
47 }
48
49 #[derive(Copy, Clone)]
50 pub(crate) struct DeltaBaseRecursion<'a> {
51 pub depth: usize,
52 pub original_id: &'a gix_hash::oid,
53 }
54
55 impl<'a> DeltaBaseRecursion<'a> {
56 pub fn new(id: &'a gix_hash::oid) -> Self {
57 Self {
58 original_id: id,
59 depth: 0,
60 }
61 }
62 pub fn inc_depth(mut self) -> Self {
63 self.depth += 1;
64 self
65 }
66 }
67
68 #[cfg(test)]
69 mod tests {
70 use super::*;
71
72 #[test]
73 fn error_size() {
74 let actual = std::mem::size_of::<Error>();
75 assert!(actual <= 88, "{actual} <= 88: should not grow without us noticing");
76 }
77 }
78}
79pub use error::Error;
80
81use crate::store::types::PackId;
82
83impl<S> super::Handle<S>
84where
85 S: Deref<Target = super::Store> + Clone,
86{
87 fn try_find_cached_inner<'a, 'b>(
88 &'b self,
89 mut id: &'b gix_hash::oid,
90 buffer: &'a mut Vec<u8>,
91 inflate: &mut gix_zlib::Inflate,
92 pack_cache: &mut dyn DecodeEntry,
93 snapshot: &mut load_index::Snapshot,
94 recursion: Option<error::DeltaBaseRecursion<'_>>,
95 ) -> Result<Option<(gix_object::Data<'a>, Option<gix_pack::data::entry::Location>)>, Error> {
96 if let Some(r) = recursion {
97 if r.depth >= self.max_recursion_depth {
98 return Err(Error::DeltaBaseRecursionLimit {
99 max_depth: self.max_recursion_depth,
100 id: r.original_id.to_owned(),
101 });
102 }
103 } else if !self.ignore_replacements {
104 if let Ok(pos) = self
105 .store
106 .replacements
107 .binary_search_by(|(map_this, _)| map_this.as_ref().cmp(id))
108 {
109 id = self.store.replacements[pos].1.as_ref();
110 }
111 }
112
113 'outer: loop {
114 {
115 let marker = snapshot.marker;
116 for (idx, index) in snapshot.indices.iter_mut().enumerate() {
117 if let Some(handle::index_lookup::Outcome {
118 object_index: handle::IndexForObjectInPack { pack_id, pack_offset },
119 index_file,
120 pack: possibly_pack,
121 }) = index.lookup(id)
122 {
123 let pack = match possibly_pack {
124 Some(pack) => pack,
125 None => match self.store.load_pack(pack_id, marker)? {
126 Some(pack) => {
127 *possibly_pack = Some(pack);
128 possibly_pack.as_deref().expect("just put it in")
129 }
130 None => {
131 match self.store.load_one_index(self.index_ctx(snapshot.marker))? {
133 Some(new_snapshot) => {
134 *snapshot = new_snapshot;
135 self.clear_cache();
136 continue 'outer;
137 }
138 None => {
139 return Ok(None);
143 }
144 }
145 }
146 },
147 };
148 let entry = pack.entry(pack_offset)?;
149 let header_size = entry.header_size();
150 let res = pack.decode_entry(
151 entry,
152 buffer,
153 inflate,
154 &|id, _out| {
155 let pack_offset = index_file.pack_offset_by_id(id)?;
156 pack.entry(pack_offset)
157 .ok()
158 .map(gix_pack::data::decode::entry::ResolvedBase::InPack)
159 },
160 pack_cache,
161 );
162 let res = match res {
163 Ok(r) => Ok((
164 gix_object::Data {
165 kind: r.kind,
166 object_hash: pack.object_hash(),
167 data: buffer.as_slice(),
168 },
169 Some(gix_pack::data::entry::Location {
170 pack_id: pack.id,
171 pack_offset,
172 entry_size: r.compressed_size + header_size,
173 }),
174 )),
175 Err(gix_pack::data::decode::Error::DeltaBaseUnresolved(base_id)) => {
176 let mut buf = Vec::new();
188 let obj_kind = self
189 .try_find_cached_inner(
190 &base_id,
191 &mut buf,
192 inflate,
193 pack_cache,
194 snapshot,
195 recursion
196 .map(error::DeltaBaseRecursion::inc_depth)
197 .or_else(|| error::DeltaBaseRecursion::new(id).into()),
198 )
199 .map_err(|err| Error::DeltaBaseLookup {
200 err: Box::new(err),
201 base_id,
202 id: id.to_owned(),
203 })?
204 .ok_or_else(|| Error::DeltaBaseMissing {
205 base_id,
206 id: id.to_owned(),
207 })?
208 .0
209 .kind;
210 let handle::index_lookup::Outcome {
211 object_index:
212 handle::IndexForObjectInPack {
213 pack_id: _,
214 pack_offset,
215 },
216 index_file,
217 pack: possibly_pack,
218 } = match snapshot.indices[idx].lookup(id) {
219 Some(res) => res,
220 None => {
221 let mut out = None;
222 for index in &mut snapshot.indices {
223 out = index.lookup(id);
224 if out.is_some() {
225 break;
226 }
227 }
228
229 out.unwrap_or_else(|| {
230 panic!("could not find object {id} in any index after looking up one of its base objects {base_id}" )
231 })
232 }
233 };
234 let pack = possibly_pack
235 .as_ref()
236 .expect("pack to still be available like just now");
237 let entry = pack.entry(pack_offset)?;
238 let header_size = entry.header_size();
239 pack.decode_entry(
240 entry,
241 buffer,
242 inflate,
243 &|id, out| {
244 index_file
245 .pack_offset_by_id(id)
246 .and_then(|pack_offset| {
247 pack.entry(pack_offset)
248 .ok()
249 .map(gix_pack::data::decode::entry::ResolvedBase::InPack)
250 })
251 .or_else(|| {
252 (id == base_id).then(|| {
253 out.resize(buf.len(), 0);
254 out.copy_from_slice(buf.as_slice());
255 gix_pack::data::decode::entry::ResolvedBase::OutOfPack {
256 kind: obj_kind,
257 end: out.len(),
258 }
259 })
260 })
261 },
262 pack_cache,
263 )
264 .map(move |r| {
265 (
266 gix_object::Data {
267 kind: r.kind,
268 object_hash: pack.object_hash(),
269 data: buffer.as_slice(),
270 },
271 Some(gix_pack::data::entry::Location {
272 pack_id: pack.id,
273 pack_offset,
274 entry_size: r.compressed_size + header_size,
275 }),
276 )
277 })
278 }
279 Err(err) => Err(err),
280 }?;
281
282 if idx != 0 {
283 snapshot.indices.swap(0, idx);
284 }
285 return Ok(Some(res));
286 }
287 }
288 }
289
290 for lodb in snapshot.loose_dbs.iter() {
291 if lodb.contains(id) {
293 return lodb
294 .try_find(id, buffer)
295 .map(|obj| obj.map(|obj| (obj, None)))
296 .map_err(Into::into);
297 }
298 }
299
300 match self.store.load_one_index(self.index_ctx(snapshot.marker))? {
301 Some(new_snapshot) => {
302 *snapshot = new_snapshot;
303 self.clear_cache();
304 }
305 None => return Ok(None),
306 }
307 }
308 }
309
310 pub(crate) fn clear_cache(&self) {
311 self.packed_object_count.borrow_mut().take();
312 }
313}
314
315impl<S> gix_pack::Find for super::Handle<S>
316where
317 S: Deref<Target = super::Store> + Clone,
318{
319 fn contains(&self, id: &gix_hash::oid) -> bool {
321 let mut snapshot = self.snapshot.borrow_mut();
322 loop {
323 for (idx, index) in snapshot.indices.iter().enumerate() {
324 if index.contains(id) {
325 if idx != 0 {
326 snapshot.indices.swap(0, idx);
327 }
328 return true;
329 }
330 }
331
332 for lodb in snapshot.loose_dbs.iter() {
333 if lodb.contains(id) {
334 return true;
335 }
336 }
337
338 match self.store.load_one_index(self.index_ctx(snapshot.marker)) {
339 Ok(Some(new_snapshot)) => {
340 *snapshot = new_snapshot;
341 self.clear_cache();
342 }
343 Ok(None) => return false, Err(_) => return false, }
346 }
347 }
348
349 fn try_find_cached<'a>(
350 &self,
351 id: &gix_hash::oid,
352 buffer: &'a mut Vec<u8>,
353 pack_cache: &mut dyn DecodeEntry,
354 ) -> Result<Option<(gix_object::Data<'a>, Option<gix_pack::data::entry::Location>)>, gix_object::find::Error> {
355 let mut snapshot = self.snapshot.borrow_mut();
356 let mut inflate = self.inflate.borrow_mut();
357 self.try_find_cached_inner(id, buffer, &mut inflate, pack_cache, &mut snapshot, None)
358 .map_err(|err| Box::new(err) as _)
359 }
360
361 fn location_by_oid(&self, id: &gix_hash::oid, buf: &mut Vec<u8>) -> Option<gix_pack::data::entry::Location> {
362 assert!(
363 matches!(self.token.as_ref(), Some(handle::Mode::KeepDeletedPacksAvailable)),
364 "BUG: handle must be configured to `prevent_pack_unload()` before using this method"
365 );
366
367 assert!(
368 self.store_ref().replacements.is_empty() || self.ignore_replacements,
369 "Everything related to packing must not use replacements. These are not used here, but it should be turned off for good measure."
370 );
371
372 let mut snapshot = self.snapshot.borrow_mut();
373 let mut inflate = self.inflate.borrow_mut();
374 'outer: loop {
375 {
376 let marker = snapshot.marker;
377 for (idx, index) in snapshot.indices.iter_mut().enumerate() {
378 if let Some(handle::index_lookup::Outcome {
379 object_index: handle::IndexForObjectInPack { pack_id, pack_offset },
380 index_file: _,
381 pack: possibly_pack,
382 }) = index.lookup(id)
383 {
384 let pack = match possibly_pack {
385 Some(pack) => pack,
386 None => match self.store.load_pack(pack_id, marker).ok()? {
387 Some(pack) => {
388 *possibly_pack = Some(pack);
389 possibly_pack.as_deref().expect("just put it in")
390 }
391 None => {
392 match self.store.load_one_index(self.index_ctx(snapshot.marker)).ok()? {
394 Some(new_snapshot) => {
395 *snapshot = new_snapshot;
396 self.clear_cache();
397 continue 'outer;
398 }
399 None => {
400 return None;
404 }
405 }
406 }
407 },
408 };
409 let entry = pack.entry(pack_offset).ok()?;
410 let size: usize = entry.decompressed_size.try_into().ok()?;
413 if pack.alloc_limit_bytes.is_some_and(|limit| size > limit) {
414 return None;
415 }
416 buf.resize(size, 0);
417 assert_eq!(pack.id, pack_id.to_intrinsic_pack_id(), "both ids must always match");
418
419 let res = pack
420 .decompress_entry(&entry, &mut inflate, buf)
421 .ok()
422 .map(|entry_size_past_header| gix_pack::data::entry::Location {
423 pack_id: pack.id,
424 pack_offset,
425 entry_size: entry.header_size() + entry_size_past_header,
426 });
427
428 if idx != 0 {
429 snapshot.indices.swap(0, idx);
430 }
431 return res;
432 }
433 }
434 }
435
436 {
437 let new_snapshot = self.store.load_one_index(self.index_ctx(snapshot.marker)).ok()??;
438 *snapshot = new_snapshot;
439 self.clear_cache();
440 }
441 }
442 }
443
444 fn pack_offsets_and_oid(&self, pack_id: u32) -> Option<Vec<(u64, gix_hash::ObjectId)>> {
445 assert!(
446 matches!(self.token.as_ref(), Some(handle::Mode::KeepDeletedPacksAvailable)),
447 "BUG: handle must be configured to `prevent_pack_unload()` before using this method"
448 );
449 let pack_id = PackId::from_intrinsic_pack_id(pack_id);
450 loop {
451 let snapshot = self.snapshot.borrow();
452 {
453 for index in &snapshot.indices {
454 if let Some(iter) = index.iter(pack_id) {
455 return Some(iter.map(|e| (e.pack_offset, e.oid)).collect());
456 }
457 }
458 }
459
460 {
461 let new_snapshot = self.store.load_one_index(self.index_ctx(snapshot.marker)).ok()??;
462 drop(snapshot);
463 *self.snapshot.borrow_mut() = new_snapshot;
464 }
465 }
466 }
467
468 fn entry_by_location(&self, location: &gix_pack::data::entry::Location) -> Option<gix_pack::find::Entry> {
469 assert!(
470 matches!(self.token.as_ref(), Some(handle::Mode::KeepDeletedPacksAvailable)),
471 "BUG: handle must be configured to `prevent_pack_unload()` before using this method"
472 );
473 let pack_id = PackId::from_intrinsic_pack_id(location.pack_id);
474 let mut snapshot = self.snapshot.borrow_mut();
475 let marker = snapshot.marker;
476 loop {
477 {
478 for index in &mut snapshot.indices {
479 if let Some(possibly_pack) = index.pack(pack_id) {
480 let pack = match possibly_pack {
481 Some(pack) => pack,
482 None => {
483 let pack = self.store.load_pack(pack_id, marker).ok()?.expect(
484 "BUG: pack must exist from previous call to location_by_oid() and must not be unloaded",
485 );
486 *possibly_pack = Some(pack);
487 possibly_pack.as_deref().expect("just put it in")
488 }
489 };
490 return pack
491 .entry_slice(location.entry_range(location.pack_offset))
492 .map(|data| gix_pack::find::Entry {
493 data: data.to_owned(),
494 version: pack.version(),
495 });
496 }
497 }
498 }
499
500 snapshot.indices.insert(
501 0,
502 self.store
503 .index_by_id(pack_id, marker)
504 .expect("BUG: index must always be present, must not be unloaded or overwritten"),
505 );
506 }
507 }
508}
509
510impl<S> gix_object::Find for super::Handle<S>
511where
512 S: Deref<Target = super::Store> + Clone,
513 Self: gix_pack::Find,
514{
515 fn try_find<'a>(
516 &self,
517 id: &gix_hash::oid,
518 buffer: &'a mut Vec<u8>,
519 ) -> Result<Option<gix_object::Data<'a>>, gix_object::find::Error> {
520 gix_pack::Find::try_find(self, id, buffer).map(|t| t.map(|t| t.0))
521 }
522}
523
524impl<S> gix_object::FindHeader for super::Handle<S>
525where
526 S: Deref<Target = super::Store> + Clone,
527{
528 fn try_header(&self, id: &gix_hash::oid) -> Result<Option<gix_object::Header>, gix_object::find::Error> {
529 let mut snapshot = self.snapshot.borrow_mut();
530 let mut inflate = self.inflate.borrow_mut();
531 self.try_header_inner(id, &mut inflate, &mut snapshot, None)
532 .map(|maybe_header| {
533 maybe_header.map(|hdr| gix_object::Header {
534 kind: hdr.kind(),
535 size: hdr.size(),
536 })
537 })
538 .map_err(|err| Box::new(err) as _)
539 }
540}
541
542impl<S> gix_object::Exists for super::Handle<S>
543where
544 S: Deref<Target = super::Store> + Clone,
545 Self: gix_pack::Find,
546{
547 fn exists(&self, id: &gix_hash::oid) -> bool {
548 gix_pack::Find::contains(self, id)
549 }
550}