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}