gix_ref/store/file/
raw_ext.rs

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