Skip to main content

gix_pack/
find_traits.rs

1use crate::{data, find};
2use gix_error::Result;
3
4/// Describe how object can be located in an object store with built-in facilities to supports packs specifically.
5///
6/// ## Notes
7///
8/// Find effectively needs [generic associated types][issue] to allow a trait for the returned object type.
9/// Until then, we will have to make due with explicit types and give them the potentially added features we want.
10///
11/// Furthermore, despite this trait being in `gix-pack`, it leaks knowledge about objects potentially not being packed.
12/// This is a necessary trade-off to allow this trait to live in `gix-pack` where it is used in functions to create a pack.
13///
14/// [issue]: https://github.com/rust-lang/rust/issues/44265
15pub trait Find {
16    /// Returns true if the object exists in the database.
17    fn contains(&self, id: &gix_hash::oid) -> bool;
18
19    /// Find an object matching `id` in the database while placing its raw, decoded data into `buffer`.
20    /// A `pack_cache` can be used to speed up subsequent lookups, set it to [`crate::cache::Never`] if the
21    /// workload isn't suitable for caching.
22    ///
23    /// Returns `Some((<object data>, <pack location if packed>))` if it was present in the database,
24    /// or the error that occurred during lookup or object retrieval.
25    fn try_find<'a>(
26        &self,
27        id: &gix_hash::oid,
28        buffer: &'a mut Vec<u8>,
29    ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>> {
30        self.try_find_cached(id, buffer, &mut crate::cache::Never)
31    }
32
33    /// Like [`Find::try_find()`], but with support for controlling the pack cache.
34    /// A `pack_cache` can be used to speed up subsequent lookups, set it to [`crate::cache::Never`] if the
35    /// workload isn't suitable for caching.
36    ///
37    /// Returns `Some((<object data>, <pack location if packed>))` if it was present in the database,
38    /// or the error that occurred during lookup or object retrieval.
39    fn try_find_cached<'a>(
40        &self,
41        id: &gix_hash::oid,
42        buffer: &'a mut Vec<u8>,
43        pack_cache: &mut dyn crate::cache::DecodeEntry,
44    ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>>;
45
46    /// Find the pack location where an object with `id` can be found in the database.
47    ///
48    /// Returns `Ok(None)` if no pack holds the object, including when it exists only as a loose object.
49    /// Lookup, corruption, and object retrieval failures are returned as errors.
50    fn location_by_oid(&self, id: &gix_hash::oid, buf: &mut Vec<u8>) -> Result<Option<data::entry::Location>>;
51
52    /// Obtain a vector of all offsets, in index order, along with their object id.
53    ///
54    /// Returns `Ok(None)` if the pack is unavailable, or an error if loading or reading its index fails.
55    fn pack_offsets_and_oid(&self, pack_id: u32) -> Result<Option<Vec<(data::Offset, gix_hash::ObjectId)>>>;
56
57    /// Return the [`find::Entry`] for `location` if it is backed by a pack.
58    ///
59    /// Note that this is only in the interest of avoiding duplicate work during pack generation.
60    /// Pack locations can be obtained from [`Find::try_find()`].
61    ///
62    /// # Notes
63    ///
64    /// Custom implementations might be interested in providing their own meta-data with `object`,
65    /// which currently isn't possible as the `Locate` trait requires GATs to work like that.
66    fn entry_by_location(&self, location: &data::entry::Location) -> Option<find::Entry>;
67}
68
69mod ext {
70    use gix_error::{Error, Result};
71    use gix_error::{ResultExt, message};
72    use gix_object::{BlobRef, CommitRef, CommitRefIter, Kind, ObjectRef, TagRef, TagRefIter, TreeRef, TreeRefIter};
73
74    fn not_found(id: &gix_hash::oid) -> Error {
75        message!("An object with id {id} could not be found").not_found_error()
76    }
77
78    fn wrong_kind(id: &gix_hash::oid, actual: Kind, expected: Kind) -> Error {
79        message!("Expected object of kind {expected} but got {actual} at {id}").validation_error()
80    }
81
82    macro_rules! make_obj_lookup {
83        ($method:ident, $object_variant:path, $object_kind:path, $object_type:ty) => {
84            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
85            /// while returning the desired object type.
86            fn $method<'a>(
87                &self,
88                id: &gix_hash::oid,
89                buffer: &'a mut Vec<u8>,
90            ) -> Result<($object_type, Option<crate::data::entry::Location>)> {
91                let id = id.as_ref();
92                self.try_find(id, buffer)?
93                    .ok_or_else(|| not_found(id))
94                    .and_then(|(o, l)| {
95                        o.decode()
96                            .or_raise(|| message!("Could not decode object at {id}").corrupted())
97                            .map(|o| (o, l))
98                    })
99                    .and_then(|(o, l)| match o {
100                        $object_variant(o) => return Ok((o, l)),
101                        o => Err(wrong_kind(id, o.kind(), $object_kind)),
102                    })
103            }
104        };
105    }
106
107    macro_rules! make_iter_lookup {
108        ($method:ident, $object_kind:path, $object_type:ty, $into_iter:tt) => {
109            /// Like [`find(…)`][Self::find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error
110            /// while returning the desired iterator type.
111            fn $method<'a>(
112                &self,
113                id: &gix_hash::oid,
114                buffer: &'a mut Vec<u8>,
115            ) -> Result<($object_type, Option<crate::data::entry::Location>)> {
116                let id = id.as_ref();
117                self.try_find(id, buffer)?
118                    .ok_or_else(|| not_found(id))
119                    .and_then(|(o, l)| {
120                        o.$into_iter()
121                            .ok_or_else(|| wrong_kind(id, o.kind, $object_kind))
122                            .map(|i| (i, l))
123                    })
124            }
125        };
126    }
127
128    /// An extension trait with convenience functions.
129    pub trait FindExt: super::Find {
130        /// Like [`try_find(…)`][super::Find::try_find()], but flattens the `Result<Option<_>>` into a single `Result` making a non-existing object an error.
131        fn find<'a>(
132            &self,
133            id: &gix_hash::oid,
134            buffer: &'a mut Vec<u8>,
135        ) -> Result<(gix_object::Data<'a>, Option<crate::data::entry::Location>)> {
136            self.try_find(id, buffer)?.ok_or_else(|| not_found(id))
137        }
138
139        make_obj_lookup!(find_commit, ObjectRef::Commit, Kind::Commit, CommitRef<'a>);
140        make_obj_lookup!(find_tree, ObjectRef::Tree, Kind::Tree, TreeRef<'a>);
141        make_obj_lookup!(find_tag, ObjectRef::Tag, Kind::Tag, TagRef<'a>);
142        make_obj_lookup!(find_blob, ObjectRef::Blob, Kind::Blob, BlobRef<'a>);
143        make_iter_lookup!(find_commit_iter, Kind::Blob, CommitRefIter<'a>, try_into_commit_iter);
144        make_iter_lookup!(find_tree_iter, Kind::Tree, TreeRefIter<'a>, try_into_tree_iter);
145        make_iter_lookup!(find_tag_iter, Kind::Tag, TagRefIter<'a>, try_into_tag_iter);
146    }
147
148    impl<T: super::Find + ?Sized> FindExt for T {}
149}
150pub use ext::FindExt;
151
152mod find_impls {
153    use gix_error::Result;
154    use std::{ops::Deref, rc::Rc};
155
156    use gix_hash::oid;
157
158    use crate::{data, find};
159
160    impl<T> crate::Find for &T
161    where
162        T: crate::Find,
163    {
164        fn contains(&self, id: &oid) -> bool {
165            (*self).contains(id)
166        }
167
168        fn try_find_cached<'a>(
169            &self,
170            id: &oid,
171            buffer: &'a mut Vec<u8>,
172            pack_cache: &mut dyn crate::cache::DecodeEntry,
173        ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>> {
174            (*self).try_find_cached(id, buffer, pack_cache)
175        }
176
177        fn location_by_oid(&self, id: &oid, buf: &mut Vec<u8>) -> Result<Option<data::entry::Location>> {
178            (*self).location_by_oid(id, buf)
179        }
180
181        fn pack_offsets_and_oid(&self, pack_id: u32) -> Result<Option<Vec<(data::Offset, gix_hash::ObjectId)>>> {
182            (*self).pack_offsets_and_oid(pack_id)
183        }
184
185        fn entry_by_location(&self, location: &data::entry::Location) -> Option<find::Entry> {
186            (*self).entry_by_location(location)
187        }
188    }
189
190    impl<T> super::Find for std::sync::Arc<T>
191    where
192        T: super::Find,
193    {
194        fn contains(&self, id: &oid) -> bool {
195            self.deref().contains(id)
196        }
197
198        fn try_find_cached<'a>(
199            &self,
200            id: &oid,
201            buffer: &'a mut Vec<u8>,
202            pack_cache: &mut dyn crate::cache::DecodeEntry,
203        ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>> {
204            self.deref().try_find_cached(id, buffer, pack_cache)
205        }
206
207        fn location_by_oid(&self, id: &oid, buf: &mut Vec<u8>) -> Result<Option<data::entry::Location>> {
208            self.deref().location_by_oid(id, buf)
209        }
210
211        fn pack_offsets_and_oid(&self, pack_id: u32) -> Result<Option<Vec<(data::Offset, gix_hash::ObjectId)>>> {
212            self.deref().pack_offsets_and_oid(pack_id)
213        }
214
215        fn entry_by_location(&self, object: &data::entry::Location) -> Option<find::Entry> {
216            self.deref().entry_by_location(object)
217        }
218    }
219
220    impl<T> super::Find for Rc<T>
221    where
222        T: super::Find,
223    {
224        fn contains(&self, id: &oid) -> bool {
225            self.deref().contains(id)
226        }
227
228        fn try_find_cached<'a>(
229            &self,
230            id: &oid,
231            buffer: &'a mut Vec<u8>,
232            pack_cache: &mut dyn crate::cache::DecodeEntry,
233        ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>> {
234            self.deref().try_find_cached(id, buffer, pack_cache)
235        }
236
237        fn location_by_oid(&self, id: &oid, buf: &mut Vec<u8>) -> Result<Option<data::entry::Location>> {
238            self.deref().location_by_oid(id, buf)
239        }
240
241        fn pack_offsets_and_oid(&self, pack_id: u32) -> Result<Option<Vec<(data::Offset, gix_hash::ObjectId)>>> {
242            self.deref().pack_offsets_and_oid(pack_id)
243        }
244
245        fn entry_by_location(&self, location: &data::entry::Location) -> Option<find::Entry> {
246            self.deref().entry_by_location(location)
247        }
248    }
249
250    impl<T> super::Find for Box<T>
251    where
252        T: super::Find,
253    {
254        fn contains(&self, id: &oid) -> bool {
255            self.deref().contains(id)
256        }
257
258        fn try_find_cached<'a>(
259            &self,
260            id: &oid,
261            buffer: &'a mut Vec<u8>,
262            pack_cache: &mut dyn crate::cache::DecodeEntry,
263        ) -> Result<Option<(gix_object::Data<'a>, Option<data::entry::Location>)>> {
264            self.deref().try_find_cached(id, buffer, pack_cache)
265        }
266
267        fn location_by_oid(&self, id: &oid, buf: &mut Vec<u8>) -> Result<Option<data::entry::Location>> {
268            self.deref().location_by_oid(id, buf)
269        }
270
271        fn pack_offsets_and_oid(&self, pack_id: u32) -> Result<Option<Vec<(data::Offset, gix_hash::ObjectId)>>> {
272            self.deref().pack_offsets_and_oid(pack_id)
273        }
274
275        fn entry_by_location(&self, location: &data::entry::Location) -> Option<find::Entry> {
276            self.deref().entry_by_location(location)
277        }
278    }
279}