Skip to main content

gix_ref/store/file/
raw_ext.rs

1use std::collections::BTreeSet;
2
3use gix_hash::ObjectId;
4
5use crate::{
6    Target, packed, peel,
7    raw::Reference,
8    store_impl::{file, file::log},
9};
10
11pub trait Sealed {}
12impl Sealed for crate::Reference {}
13
14/// A trait to extend [Reference][crate::Reference] with functionality requiring a [file::Store].
15pub trait ReferenceExt: Sealed {
16    /// A step towards obtaining forward or reverse iterators on reference logs.
17    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's>;
18
19    /// For details, see [`Reference::log_exists()`].
20    fn log_exists(&self, store: &file::Store) -> bool;
21
22    /// Follow all symbolic targets this reference might point to and peel the underlying object
23    /// to the end of the tag-chain, returning the first non-tag object the annotated tag points to,
24    /// using `objects` to access them and `store` to lookup symbolic references.
25    ///
26    /// This is useful to learn where this reference is ultimately pointing to after following all symbolic
27    /// refs and all annotated tags to the first non-tag object.
28    #[deprecated = "Use `peel_to_id()` instead"]
29    fn peel_to_id_in_place(
30        &mut self,
31        store: &file::Store,
32        objects: &dyn gix_object::Find,
33    ) -> Result<ObjectId, peel::to_id::Error>;
34
35    /// Follow all symbolic targets this reference might point to and peel the underlying object
36    /// to the end of the tag-chain, returning the first non-tag object the annotated tag points to,
37    /// using `objects` to access them and `store` to lookup symbolic references.
38    ///
39    /// This is useful to learn where this reference is ultimately pointing to after following all symbolic
40    /// refs and all annotated tags to the first non-tag object.
41    ///
42    /// Note that this method mutates `self` in place if it does not already point to a
43    /// non-symbolic object.
44    fn peel_to_id(
45        &mut self,
46        store: &file::Store,
47        objects: &dyn gix_object::Find,
48    ) -> Result<ObjectId, peel::to_id::Error>;
49
50    /// Like [`ReferenceExt::peel_to_id_in_place()`], but with support for a known stable `packed` buffer
51    /// to use for resolving symbolic links.
52    #[deprecated = "Use `peel_to_id_packed()` instead"]
53    fn peel_to_id_in_place_packed(
54        &mut self,
55        store: &file::Store,
56        objects: &dyn gix_object::Find,
57        packed: Option<&packed::Buffer>,
58    ) -> Result<ObjectId, peel::to_id::Error>;
59
60    /// Like [`ReferenceExt::peel_to_id()`], but with support for a known stable `packed` buffer to
61    /// use for resolving symbolic links.
62    fn peel_to_id_packed(
63        &mut self,
64        store: &file::Store,
65        objects: &dyn gix_object::Find,
66        packed: Option<&packed::Buffer>,
67    ) -> Result<ObjectId, peel::to_id::Error>;
68
69    /// Like [`ReferenceExt::follow()`], but follows all symbolic references while gracefully handling loops,
70    /// altering this instance in place.
71    #[deprecated = "Use `follow_to_object_packed()` instead"]
72    fn follow_to_object_in_place_packed(
73        &mut self,
74        store: &file::Store,
75        packed: Option<&packed::Buffer>,
76    ) -> Result<ObjectId, peel::to_object::Error>;
77
78    /// Like [`ReferenceExt::follow()`], but follows all symbolic references while gracefully handling loops,
79    /// altering this instance in place.
80    fn follow_to_object_packed(
81        &mut self,
82        store: &file::Store,
83        packed: Option<&packed::Buffer>,
84    ) -> Result<ObjectId, peel::to_object::Error>;
85
86    /// Follow this symbolic reference one level and return the ref it refers to.
87    ///
88    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
89    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>;
90
91    /// Follow this symbolic reference one level and return the ref it refers to,
92    /// possibly providing access to `packed` references for lookup if it contains the referent.
93    ///
94    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
95    fn follow_packed(
96        &self,
97        store: &file::Store,
98        packed: Option<&packed::Buffer>,
99    ) -> Option<Result<Reference, file::find::existing::Error>>;
100}
101
102impl ReferenceExt for Reference {
103    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> {
104        log::iter::Platform {
105            store,
106            name: self.name.as_ref(),
107            buf: Vec::new(),
108        }
109    }
110
111    fn log_exists(&self, store: &file::Store) -> bool {
112        store
113            .reflog_exists(self.name.as_ref())
114            .expect("infallible name conversion")
115    }
116
117    fn peel_to_id_in_place(
118        &mut self,
119        store: &file::Store,
120        objects: &dyn gix_object::Find,
121    ) -> Result<ObjectId, peel::to_id::Error> {
122        self.peel_to_id(store, objects)
123    }
124
125    fn peel_to_id(
126        &mut self,
127        store: &file::Store,
128        objects: &dyn gix_object::Find,
129    ) -> Result<ObjectId, peel::to_id::Error> {
130        let packed = store.assure_packed_refs_uptodate().map_err(|err| {
131            peel::to_id::Error::FollowToObject(peel::to_object::Error::Follow(file::find::existing::Error::Find(
132                file::find::Error::PackedOpen(err),
133            )))
134        })?;
135        self.peel_to_id_packed(store, objects, packed.as_ref().map(|b| &***b))
136    }
137
138    fn peel_to_id_in_place_packed(
139        &mut self,
140        store: &file::Store,
141        objects: &dyn gix_object::Find,
142        packed: Option<&packed::Buffer>,
143    ) -> Result<ObjectId, peel::to_id::Error> {
144        self.peel_to_id_packed(store, objects, packed)
145    }
146
147    fn peel_to_id_packed(
148        &mut self,
149        store: &file::Store,
150        objects: &dyn gix_object::Find,
151        packed: Option<&packed::Buffer>,
152    ) -> Result<ObjectId, peel::to_id::Error> {
153        match self.peeled {
154            Some(peeled) => {
155                self.target = Target::Object(peeled.to_owned());
156                Ok(peeled)
157            }
158            None => {
159                let mut oid = self.follow_to_object_packed(store, packed)?;
160                let mut buf = Vec::new();
161                let peeled_id = loop {
162                    let gix_object::Data {
163                        kind,
164                        data,
165                        object_hash: hash_kind,
166                    } = objects
167                        .try_find(&oid, &mut buf)?
168                        .ok_or_else(|| peel::to_id::Error::NotFound {
169                            oid,
170                            name: self.name.0.clone(),
171                        })?;
172                    match kind {
173                        gix_object::Kind::Tag => {
174                            oid = gix_object::TagRefIter::from_bytes(data, hash_kind)
175                                .target_id()
176                                .map_err(|_err| peel::to_id::Error::NotFound {
177                                    oid,
178                                    name: self.name.0.clone(),
179                                })?;
180                        }
181                        _ => break oid,
182                    }
183                };
184                self.peeled = Some(peeled_id);
185                self.target = Target::Object(peeled_id);
186                Ok(peeled_id)
187            }
188        }
189    }
190
191    fn follow_to_object_in_place_packed(
192        &mut self,
193        store: &file::Store,
194        packed: Option<&packed::Buffer>,
195    ) -> Result<ObjectId, peel::to_object::Error> {
196        self.follow_to_object_packed(store, packed)
197    }
198
199    fn follow_to_object_packed(
200        &mut self,
201        store: &file::Store,
202        packed: Option<&packed::Buffer>,
203    ) -> Result<ObjectId, peel::to_object::Error> {
204        match self.target {
205            Target::Object(id) => Ok(id),
206            Target::Symbolic(_) => {
207                let mut seen = BTreeSet::new();
208                let cursor = &mut *self;
209                while let Some(next) = cursor.follow_packed(store, packed) {
210                    let next = next?;
211                    if seen.contains(&next.name) {
212                        return Err(peel::to_object::Error::Cycle {
213                            start_absolute: store.reference_path(cursor.name.as_ref()),
214                        });
215                    }
216                    *cursor = next;
217                    seen.insert(cursor.name.clone());
218                    const MAX_REF_DEPTH: usize = 5;
219                    if seen.len() == MAX_REF_DEPTH {
220                        return Err(peel::to_object::Error::DepthLimitExceeded {
221                            max_depth: MAX_REF_DEPTH,
222                        });
223                    }
224                }
225                let oid = self.target.try_id().expect("peeled ref").to_owned();
226                Ok(oid)
227            }
228        }
229    }
230
231    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>> {
232        let packed = match store
233            .assure_packed_refs_uptodate()
234            .map_err(|err| file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
235        {
236            Ok(packed) => packed,
237            Err(err) => return Some(Err(err)),
238        };
239        self.follow_packed(store, packed.as_ref().map(|b| &***b))
240    }
241
242    fn follow_packed(
243        &self,
244        store: &file::Store,
245        packed: Option<&packed::Buffer>,
246    ) -> Option<Result<Reference, file::find::existing::Error>> {
247        match &self.target {
248            Target::Object(_) => None,
249            Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) {
250                Ok(Some(next)) => Some(Ok(next)),
251                Ok(None) => Some(Err(file::find::existing::Error::NotFound {
252                    name: full_name.to_path().to_owned(),
253                })),
254                Err(err) => Some(Err(file::find::existing::Error::Find(err))),
255            },
256        }
257    }
258}