gix_odb/store_impls/dynamic/header.rs
1use std::ops::Deref;
2
3use gix_features::zlib;
4use gix_hash::oid;
5
6use super::find::Error;
7use crate::{
8 find::Header,
9 store::{find::error::DeltaBaseRecursion, handle, load_index},
10};
11
12impl<S> super::Handle<S>
13where
14 S: Deref<Target = super::Store> + Clone,
15{
16 pub(crate) fn try_header_inner<'b>(
17 &'b self,
18 mut id: &'b gix_hash::oid,
19 inflate: &mut zlib::Inflate,
20 snapshot: &mut load_index::Snapshot,
21 recursion: Option<DeltaBaseRecursion<'_>>,
22 ) -> Result<Option<Header>, Error> {
23 if let Some(r) = recursion {
24 if r.depth >= self.max_recursion_depth {
25 return Err(Error::DeltaBaseRecursionLimit {
26 max_depth: self.max_recursion_depth,
27 id: r.original_id.to_owned(),
28 });
29 }
30 } else if !self.ignore_replacements {
31 if let Ok(pos) = self
32 .store
33 .replacements
34 .binary_search_by(|(map_this, _)| map_this.as_ref().cmp(id))
35 {
36 id = self.store.replacements[pos].1.as_ref();
37 }
38 }
39
40 'outer: loop {
41 {
42 let marker = snapshot.marker;
43 for (idx, index) in snapshot.indices.iter_mut().enumerate() {
44 if let Some(handle::index_lookup::Outcome {
45 object_index: handle::IndexForObjectInPack { pack_id, pack_offset },
46 index_file,
47 pack: possibly_pack,
48 }) = index.lookup(id)
49 {
50 let pack = match possibly_pack {
51 Some(pack) => pack,
52 None => match self.store.load_pack(pack_id, marker)? {
53 Some(pack) => {
54 *possibly_pack = Some(pack);
55 possibly_pack.as_deref().expect("just put it in")
56 }
57 None => {
58 // The pack wasn't available anymore so we are supposed to try another round with a fresh index
59 match self.store.load_one_index(self.refresh, snapshot.marker)? {
60 Some(new_snapshot) => {
61 *snapshot = new_snapshot;
62 self.clear_cache();
63 continue 'outer;
64 }
65 None => {
66 // nothing new in the index, kind of unexpected to not have a pack but to also
67 // to have no new index yet. We set the new index before removing any slots, so
68 // this should be observable.
69 return Ok(None);
70 }
71 }
72 }
73 },
74 };
75 let entry = pack.entry(pack_offset)?;
76 let res = match pack.decode_header(entry, inflate, &|id| {
77 index_file.pack_offset_by_id(id).and_then(|pack_offset| {
78 pack.entry(pack_offset)
79 .ok()
80 .map(gix_pack::data::decode::header::ResolvedBase::InPack)
81 })
82 }) {
83 Ok(header) => Ok(header.into()),
84 Err(gix_pack::data::decode::Error::DeltaBaseUnresolved(base_id)) => {
85 // Only with multi-pack indices it's allowed to jump to refer to other packs within this
86 // multi-pack. Otherwise this would constitute a thin pack which is only allowed in transit.
87 // However, if we somehow end up with that, we will resolve it safely, even though we could
88 // avoid handling this case and error instead.
89 let hdr = self
90 .try_header_inner(
91 &base_id,
92 inflate,
93 snapshot,
94 recursion
95 .map(DeltaBaseRecursion::inc_depth)
96 .or_else(|| DeltaBaseRecursion::new(id).into()),
97 )
98 .map_err(|err| Error::DeltaBaseLookup {
99 err: Box::new(err),
100 base_id,
101 id: id.to_owned(),
102 })?
103 .ok_or_else(|| Error::DeltaBaseMissing {
104 base_id,
105 id: id.to_owned(),
106 })?;
107 let handle::index_lookup::Outcome {
108 object_index:
109 handle::IndexForObjectInPack {
110 pack_id: _,
111 pack_offset,
112 },
113 index_file,
114 pack: possibly_pack,
115 } = match snapshot.indices[idx].lookup(id) {
116 Some(res) => res,
117 None => {
118 let mut out = None;
119 for index in &mut snapshot.indices {
120 out = index.lookup(id);
121 if out.is_some() {
122 break;
123 }
124 }
125
126 out.unwrap_or_else(|| {
127 panic!("could not find object {id} in any index after looking up one of its base objects {base_id}")
128 })
129 }
130 };
131 let pack = possibly_pack
132 .as_ref()
133 .expect("pack to still be available like just now");
134 let entry = pack.entry(pack_offset)?;
135 pack.decode_header(entry, inflate, &|id| {
136 index_file
137 .pack_offset_by_id(id)
138 .and_then(|pack_offset| {
139 pack.entry(pack_offset)
140 .ok()
141 .map(gix_pack::data::decode::header::ResolvedBase::InPack)
142 })
143 .or_else(|| {
144 (id == base_id).then(|| {
145 gix_pack::data::decode::header::ResolvedBase::OutOfPack {
146 kind: hdr.kind(),
147 num_deltas: hdr.num_deltas(),
148 }
149 })
150 })
151 })
152 .map(Into::into)
153 }
154 Err(err) => Err(err),
155 }?;
156
157 if idx != 0 {
158 snapshot.indices.swap(0, idx);
159 }
160 return Ok(Some(res));
161 }
162 }
163 }
164
165 for lodb in snapshot.loose_dbs.iter() {
166 // TODO: remove this double-lookup once the borrow checker allows it.
167 if lodb.contains(id) {
168 return lodb.try_header(id).map(|opt| opt.map(Into::into)).map_err(Into::into);
169 }
170 }
171
172 match self.store.load_one_index(self.refresh, snapshot.marker)? {
173 Some(new_snapshot) => {
174 *snapshot = new_snapshot;
175 self.clear_cache();
176 }
177 None => return Ok(None),
178 }
179 }
180 }
181}
182
183impl<S> crate::Header for super::Handle<S>
184where
185 S: Deref<Target = super::Store> + Clone,
186{
187 fn try_header(&self, id: &oid) -> Result<Option<Header>, gix_object::find::Error> {
188 let mut snapshot = self.snapshot.borrow_mut();
189 let mut inflate = self.inflate.borrow_mut();
190 self.try_header_inner(id, &mut inflate, &mut snapshot, None)
191 .map_err(|err| Box::new(err) as _)
192 }
193}