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}