git_odb/store_impls/dynamic/
handle.rs

1use std::{
2    cell::RefCell,
3    convert::{TryFrom, TryInto},
4    ops::Deref,
5    rc::Rc,
6    sync::{atomic::Ordering, Arc},
7};
8
9use git_features::threading::OwnShared;
10use git_hash::oid;
11
12use crate::store::{handle, types, RefreshMode};
13
14pub(crate) enum SingleOrMultiIndex {
15    Single {
16        index: Arc<git_pack::index::File>,
17        data: Option<Arc<git_pack::data::File>>,
18    },
19    Multi {
20        index: Arc<git_pack::multi_index::File>,
21        data: Vec<Option<Arc<git_pack::data::File>>>,
22    },
23}
24
25/// A utility to allow looking up pack offsets for a particular pack
26pub(crate) enum IntraPackLookup<'a> {
27    Single(&'a git_pack::index::File),
28    /// the internal pack-id inside of a multi-index for which the lookup is supposed to be.
29    /// Used to prevent ref-delta OIDs to, for some reason, point to a different pack.
30    Multi {
31        index: &'a git_pack::multi_index::File,
32        required_pack_index: git_pack::multi_index::PackIndex,
33    },
34}
35
36impl<'a> IntraPackLookup<'a> {
37    pub(crate) fn pack_offset_by_id(&self, id: &oid) -> Option<git_pack::data::Offset> {
38        match self {
39            IntraPackLookup::Single(index) => index
40                .lookup(id)
41                .map(|entry_index| index.pack_offset_at_index(entry_index)),
42            IntraPackLookup::Multi {
43                index,
44                required_pack_index,
45            } => index.lookup(id).and_then(|entry_index| {
46                let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(entry_index);
47                (pack_index == *required_pack_index).then_some(pack_offset)
48            }),
49        }
50    }
51}
52
53pub struct IndexLookup {
54    pub(crate) file: SingleOrMultiIndex,
55    /// The index we were found at in the slot map
56    pub(crate) id: types::IndexId,
57}
58
59pub struct IndexForObjectInPack {
60    /// The internal identifier of the pack itself, which either is referred to by an index or a multi-pack index.
61    pub(crate) pack_id: types::PackId,
62    /// The offset at which the object's entry can be found
63    pub(crate) pack_offset: u64,
64}
65
66pub(crate) mod index_lookup {
67    use std::{collections::HashSet, sync::Arc};
68
69    use git_hash::oid;
70
71    use crate::store::{handle, handle::IntraPackLookup, types};
72
73    pub(crate) struct Outcome<'a> {
74        pub object_index: handle::IndexForObjectInPack,
75        pub index_file: IntraPackLookup<'a>,
76        pub pack: &'a mut Option<Arc<git_pack::data::File>>,
77    }
78
79    impl handle::IndexLookup {
80        /// Return an iterator over the entries of the given pack. The `pack_id` is required to identify a pack uniquely within
81        /// a potential multi-pack index.
82        pub(crate) fn iter(
83            &self,
84            pack_id: types::PackId,
85        ) -> Option<Box<dyn Iterator<Item = git_pack::index::Entry> + '_>> {
86            (self.id == pack_id.index).then(|| match &self.file {
87                handle::SingleOrMultiIndex::Single { index, .. } => index.iter(),
88                handle::SingleOrMultiIndex::Multi { index, .. } => {
89                    let pack_index = pack_id.multipack_index.expect(
90                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",
91                    );
92                    Box::new(index.iter().filter_map(move |e| {
93                        (e.pack_index == pack_index).then_some(git_pack::index::Entry {
94                            oid: e.oid,
95                            pack_offset: e.pack_offset,
96                            crc32: None,
97                        })
98                    }))
99                }
100            })
101        }
102
103        pub(crate) fn pack(&mut self, pack_id: types::PackId) -> Option<&'_ mut Option<Arc<git_pack::data::File>>> {
104            (self.id == pack_id.index).then(move || match &mut self.file {
105                handle::SingleOrMultiIndex::Single { data, .. } => data,
106                handle::SingleOrMultiIndex::Multi { data, .. } => {
107                    let pack_index = pack_id.multipack_index.expect(
108                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",
109                    );
110                    &mut data[pack_index as usize]
111                }
112            })
113        }
114
115        /// Return true if the given object id exists in this index
116        pub(crate) fn contains(&self, object_id: &oid) -> bool {
117            match &self.file {
118                handle::SingleOrMultiIndex::Single { index, .. } => index.lookup(object_id).is_some(),
119                handle::SingleOrMultiIndex::Multi { index, .. } => index.lookup(object_id).is_some(),
120            }
121        }
122
123        /// Return true if the given object id exists in this index
124        pub(crate) fn oid_at_index(&self, entry_index: u32) -> &git_hash::oid {
125            match &self.file {
126                handle::SingleOrMultiIndex::Single { index, .. } => index.oid_at_index(entry_index),
127                handle::SingleOrMultiIndex::Multi { index, .. } => index.oid_at_index(entry_index),
128            }
129        }
130
131        /// Return the amount of objects contained in the index, essentially the number of object ids.
132        pub(crate) fn num_objects(&self) -> u32 {
133            match &self.file {
134                handle::SingleOrMultiIndex::Single { index, .. } => index.num_objects(),
135                handle::SingleOrMultiIndex::Multi { index, .. } => index.num_objects(),
136            }
137        }
138
139        /// Call `lookup_prefix(…)` on either index or multi-index, and transform matches into an object id.
140        pub(crate) fn lookup_prefix(
141            &self,
142            prefix: git_hash::Prefix,
143            candidates: Option<&mut HashSet<git_hash::ObjectId>>,
144        ) -> Option<crate::store::prefix::lookup::Outcome> {
145            let mut candidate_entries = candidates.as_ref().map(|_| 0..0);
146            let res = match &self.file {
147                handle::SingleOrMultiIndex::Single { index, .. } => {
148                    index.lookup_prefix(prefix, candidate_entries.as_mut())
149                }
150                handle::SingleOrMultiIndex::Multi { index, .. } => {
151                    index.lookup_prefix(prefix, candidate_entries.as_mut())
152                }
153            }?;
154
155            if let Some((candidates, entries)) = candidates.zip(candidate_entries) {
156                candidates.extend(entries.map(|entry| self.oid_at_index(entry).to_owned()));
157            }
158            Some(res.map(|entry_index| self.oid_at_index(entry_index).to_owned()))
159        }
160
161        /// See if the oid is contained in this index, and return its full id for lookup possibly alongside its data file if already
162        /// loaded.
163        /// Also return the index itself as it's needed to resolve intra-pack ref-delta objects. They are a possibility even though
164        /// they won't be used in practice as it's more efficient to store their offsets.
165        /// If it is not loaded, ask it to be loaded and put it into the returned mutable option for safe-keeping.
166        pub(crate) fn lookup(&mut self, object_id: &oid) -> Option<Outcome<'_>> {
167            let id = self.id;
168            match &mut self.file {
169                handle::SingleOrMultiIndex::Single { index, data } => index.lookup(object_id).map(move |idx| Outcome {
170                    object_index: handle::IndexForObjectInPack {
171                        pack_id: types::PackId {
172                            index: id,
173                            multipack_index: None,
174                        },
175                        pack_offset: index.pack_offset_at_index(idx),
176                    },
177                    index_file: IntraPackLookup::Single(index),
178                    pack: data,
179                }),
180                handle::SingleOrMultiIndex::Multi { index, data } => index.lookup(object_id).map(move |idx| {
181                    let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(idx);
182                    Outcome {
183                        object_index: handle::IndexForObjectInPack {
184                            pack_id: types::PackId {
185                                index: id,
186                                multipack_index: Some(pack_index),
187                            },
188                            pack_offset,
189                        },
190                        index_file: IntraPackLookup::Multi {
191                            index,
192                            required_pack_index: pack_index,
193                        },
194                        pack: &mut data[pack_index as usize],
195                    }
196                }),
197            }
198        }
199    }
200}
201
202pub(crate) enum Mode {
203    DeletedPacksAreInaccessible,
204    /// This mode signals that we should not unload packs even after they went missing.
205    KeepDeletedPacksAvailable,
206}
207
208/// Handle registration
209impl super::Store {
210    pub(crate) fn register_handle(&self) -> Mode {
211        self.num_handles_unstable.fetch_add(1, Ordering::Relaxed);
212        Mode::DeletedPacksAreInaccessible
213    }
214    pub(crate) fn remove_handle(&self, mode: Mode) {
215        match mode {
216            Mode::KeepDeletedPacksAvailable => {
217                let _lock = self.write.lock();
218                self.num_handles_stable.fetch_sub(1, Ordering::SeqCst)
219            }
220            Mode::DeletedPacksAreInaccessible => self.num_handles_unstable.fetch_sub(1, Ordering::Relaxed),
221        };
222    }
223    pub(crate) fn upgrade_handle(&self, mode: Mode) -> Mode {
224        if let Mode::DeletedPacksAreInaccessible = mode {
225            let _lock = self.write.lock();
226            self.num_handles_stable.fetch_add(1, Ordering::SeqCst);
227            self.num_handles_unstable.fetch_sub(1, Ordering::SeqCst);
228        }
229        Mode::KeepDeletedPacksAvailable
230    }
231}
232
233/// Handle creation
234impl super::Store {
235    /// The amount of times a ref-delta base can be followed when multi-indices are involved.
236    pub const INITIAL_MAX_RECURSION_DEPTH: usize = 32;
237
238    /// Create a new cache filled with a handle to this store, if this store is supporting shared ownership.
239    ///
240    /// Note that the actual type of `OwnShared` depends on the `parallel` feature toggle of the `git-features` crate.
241    pub fn to_cache(self: &OwnShared<Self>) -> crate::Cache<super::Handle<OwnShared<super::Store>>> {
242        self.to_handle().into()
243    }
244
245    /// Create a new cache filled with a handle to this store if this store is held in an `Arc`.
246    pub fn to_cache_arc(self: &Arc<Self>) -> crate::Cache<super::Handle<Arc<super::Store>>> {
247        self.to_handle_arc().into()
248    }
249
250    /// Create a new database handle to this store if this store is supporting shared ownership.
251    ///
252    /// See also, [`to_cache()`][super::Store::to_cache()] which is probably more useful.
253    pub fn to_handle(self: &OwnShared<Self>) -> super::Handle<OwnShared<super::Store>> {
254        let token = self.register_handle();
255        super::Handle {
256            store: self.clone(),
257            refresh: RefreshMode::default(),
258            ignore_replacements: false,
259            token: Some(token),
260            snapshot: RefCell::new(self.collect_snapshot()),
261            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,
262            packed_object_count: Default::default(),
263        }
264    }
265
266    /// Create a new database handle to this store if this store is held in an `Arc`.
267    ///
268    /// This method is useful in applications that know they will use threads.
269    pub fn to_handle_arc(self: &Arc<Self>) -> super::Handle<Arc<super::Store>> {
270        let token = self.register_handle();
271        super::Handle {
272            store: self.clone(),
273            refresh: Default::default(),
274            ignore_replacements: false,
275            token: Some(token),
276            snapshot: RefCell::new(self.collect_snapshot()),
277            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,
278            packed_object_count: Default::default(),
279        }
280    }
281
282    /// Transform the only instance into an `Arc<Self>` or panic if this is not the only Rc handle
283    /// to the contained store.
284    ///
285    /// This is meant to be used when the `git_features::threading::OwnShared` refers to an `Rc` as it was compiled without the
286    /// `parallel` feature toggle.
287    pub fn into_shared_arc(self: OwnShared<Self>) -> Arc<Self> {
288        match OwnShared::try_unwrap(self) {
289            Ok(this) => Arc::new(this),
290            Err(_) => panic!("BUG: Must be called when there is only one owner for this RC"),
291        }
292    }
293}
294
295impl<S> super::Handle<S>
296where
297    S: Deref<Target = super::Store> + Clone,
298{
299    /// Call once if pack ids are stored and later used for lookup, meaning they should always remain mapped and not be unloaded
300    /// even if they disappear from disk.
301    /// This must be called if there is a chance that git maintenance is happening while a pack is created.
302    pub fn prevent_pack_unload(&mut self) {
303        self.token = self.token.take().map(|token| self.store.upgrade_handle(token));
304    }
305
306    /// Return a shared reference to the contained store.
307    pub fn store_ref(&self) -> &S::Target {
308        &self.store
309    }
310
311    /// Return an owned store with shared ownership.
312    pub fn store(&self) -> S {
313        self.store.clone()
314    }
315
316    /// Set the handle to never cause ODB refreshes if an object could not be found.
317    ///
318    /// The latter is the default, as typically all objects referenced in a git repository are contained in the local clone.
319    /// More recently, however, this doesn't always have to be the case due to sparse checkouts and other ways to only have a
320    /// limited amount of objects available locally.
321    pub fn refresh_never(&mut self) {
322        self.refresh = RefreshMode::Never;
323    }
324
325    /// Return the current refresh mode.
326    pub fn refresh_mode(&mut self) -> RefreshMode {
327        self.refresh
328    }
329}
330
331impl<S> Drop for super::Handle<S>
332where
333    S: Deref<Target = super::Store> + Clone,
334{
335    fn drop(&mut self) {
336        if let Some(token) = self.token.take() {
337            self.store.remove_handle(token)
338        }
339    }
340}
341
342impl TryFrom<&super::Store> for super::Store {
343    type Error = std::io::Error;
344
345    fn try_from(s: &super::Store) -> Result<Self, Self::Error> {
346        super::Store::at_opts(
347            s.path(),
348            s.replacements(),
349            crate::store::init::Options {
350                slots: crate::store::init::Slots::Given(s.files.len().try_into().expect("BUG: too many slots")),
351                object_hash: Default::default(),
352                use_multi_pack_index: false,
353                current_dir: s.current_dir.clone().into(),
354            },
355        )
356    }
357}
358
359impl super::Handle<Rc<super::Store>> {
360    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store.
361    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {
362        let store = Arc::new(super::Store::try_from(self.store_ref())?);
363        let mut cache = store.to_handle_arc();
364        cache.refresh = self.refresh;
365        cache.max_recursion_depth = self.max_recursion_depth;
366        Ok(cache)
367    }
368}
369
370impl super::Handle<Arc<super::Store>> {
371    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store
372    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {
373        Ok(self)
374    }
375}
376
377impl<S> Clone for super::Handle<S>
378where
379    S: Deref<Target = super::Store> + Clone,
380{
381    fn clone(&self) -> Self {
382        super::Handle {
383            store: self.store.clone(),
384            refresh: self.refresh,
385            ignore_replacements: self.ignore_replacements,
386            token: {
387                let token = self.store.register_handle();
388                match self.token.as_ref().expect("token is always set here ") {
389                    handle::Mode::DeletedPacksAreInaccessible => token,
390                    handle::Mode::KeepDeletedPacksAvailable => self.store.upgrade_handle(token),
391                }
392                .into()
393            },
394            snapshot: RefCell::new(self.store.collect_snapshot()),
395            max_recursion_depth: self.max_recursion_depth,
396            packed_object_count: Default::default(),
397        }
398    }
399}