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