git_pack/
find_traits.rs

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