Skip to main content

gix_ref/store/file/
raw_ext.rs

1use std::collections::BTreeSet;
2
3use gix_error::{ErrorExt, ExnResult, Message, ResultExt, corruption, not_found};
4use gix_hash::ObjectId;
5
6use crate::{
7    Target, packed,
8    raw::Reference,
9    store_impl::{file, file::log},
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(&mut self, store: &file::Store, objects: &dyn gix_object::Find) -> ExnResult<ObjectId>;
31
32    /// Follow all symbolic targets this reference might point to and peel the underlying object
33    /// to the end of the tag-chain, returning the first non-tag object the annotated tag points to,
34    /// using `objects` to access them and `store` to lookup symbolic references.
35    ///
36    /// This is useful to learn where this reference is ultimately pointing to after following all symbolic
37    /// refs and all annotated tags to the first non-tag object.
38    ///
39    /// Note that this method mutates `self` in place if it does not already point to a
40    /// non-symbolic object.
41    fn peel_to_id(&mut self, store: &file::Store, objects: &dyn gix_object::Find) -> ExnResult<ObjectId>;
42
43    /// Like [`ReferenceExt::peel_to_id_in_place()`], but with support for a known stable `packed` buffer
44    /// to use for resolving symbolic links.
45    #[deprecated = "Use `peel_to_id_packed()` instead"]
46    fn peel_to_id_in_place_packed(
47        &mut self,
48        store: &file::Store,
49        objects: &dyn gix_object::Find,
50        packed: Option<&packed::Buffer>,
51    ) -> ExnResult<ObjectId>;
52
53    /// Like [`ReferenceExt::peel_to_id()`], but with support for a known stable `packed` buffer to
54    /// use for resolving symbolic links.
55    /// Object lookup failures include [metadata](gix_error::Exn::metadata()) `object_id` (hex text) and `reference`
56    /// (name bytes).
57    /// Missing objects are classified as not found; lookup errors retain their own classifications.
58    fn peel_to_id_packed(
59        &mut self,
60        store: &file::Store,
61        objects: &dyn gix_object::Find,
62        packed: Option<&packed::Buffer>,
63    ) -> ExnResult<ObjectId>;
64
65    /// Like [`ReferenceExt::follow()`], but follows all symbolic references while gracefully handling loops,
66    /// altering this instance in place.
67    #[deprecated = "Use `follow_to_object_packed()` instead"]
68    fn follow_to_object_in_place_packed(
69        &mut self,
70        store: &file::Store,
71        packed: Option<&packed::Buffer>,
72    ) -> ExnResult<ObjectId>;
73
74    /// Like [`ReferenceExt::follow()`], but follows all symbolic references while gracefully handling loops,
75    /// altering this instance in place.
76    /// Cycle failures include [metadata](gix_error::Exn::metadata()) `path` (native path); depth-limit failures include
77    /// `max_depth` (unsigned integer).
78    fn follow_to_object_packed(&mut self, store: &file::Store, packed: Option<&packed::Buffer>) -> ExnResult<ObjectId>;
79
80    /// Follow this symbolic reference one level and return the ref it refers to.
81    ///
82    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
83    fn follow(&self, store: &file::Store) -> Option<ExnResult<Reference>>;
84
85    /// Follow this symbolic reference one level and return the ref it refers to,
86    /// possibly providing access to `packed` references for lookup if it contains the referent.
87    ///
88    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
89    fn follow_packed(&self, store: &file::Store, packed: Option<&packed::Buffer>) -> Option<ExnResult<Reference>>;
90}
91
92impl ReferenceExt for Reference {
93    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> {
94        log::iter::Platform {
95            store,
96            name: self.name.as_ref(),
97            buf: Vec::new(),
98        }
99    }
100
101    fn log_exists(&self, store: &file::Store) -> bool {
102        store
103            .reflog_exists(self.name.as_ref())
104            .expect("infallible name conversion")
105    }
106
107    fn peel_to_id_in_place(&mut self, store: &file::Store, objects: &dyn gix_object::Find) -> ExnResult<ObjectId> {
108        self.peel_to_id(store, objects)
109    }
110
111    fn peel_to_id(&mut self, store: &file::Store, objects: &dyn gix_object::Find) -> ExnResult<ObjectId> {
112        let packed = store.assure_packed_refs_uptodate()?;
113        self.peel_to_id_packed(store, objects, packed.as_ref().map(|b| &***b))
114    }
115
116    fn peel_to_id_in_place_packed(
117        &mut self,
118        store: &file::Store,
119        objects: &dyn gix_object::Find,
120        packed: Option<&packed::Buffer>,
121    ) -> ExnResult<ObjectId> {
122        self.peel_to_id_packed(store, objects, packed)
123    }
124
125    /// Object lookup failures include [metadata](gix_error::Exn::metadata()) `object_id` (hex text) and `reference`
126    /// (name bytes).
127    fn peel_to_id_packed(
128        &mut self,
129        store: &file::Store,
130        objects: &dyn gix_object::Find,
131        packed: Option<&packed::Buffer>,
132    ) -> ExnResult<ObjectId> {
133        match self.peeled {
134            Some(peeled) => {
135                self.target = Target::Object(peeled.to_owned());
136                Ok(peeled)
137            }
138            None => {
139                let mut object_id = self.follow_to_object_packed(store, packed)?;
140                let mut buf = Vec::new();
141                let peeled_id = loop {
142                    let gix_object::Data {
143                        kind,
144                        data,
145                        object_hash: hash_kind,
146                    } = objects
147                        .try_find(&object_id, &mut buf)
148                        .or_raise_erased(|| {
149                            Message::new("Could not peel reference to an object")
150                                .with("object_id", object_id.to_string())
151                                .with("reference", self.name.as_bstr())
152                        })?
153                        .ok_or_else(|| {
154                            not_found("Could not peel reference to an object: object could not be found")
155                                .with("object_id", object_id.to_string())
156                                .with("reference", self.name.as_bstr())
157                                .raise_erased()
158                        })?;
159                    match kind {
160                        gix_object::Kind::Tag => {
161                            object_id = gix_object::TagRefIter::from_bytes(data, hash_kind)
162                                .target_id()
163                                .or_raise(|| {
164                                    corruption(format!(
165                                        "Could not decode tag {object_id} as referred to by {:?}",
166                                        self.name.0
167                                    ))
168                                })
169                                .or_erased()?;
170                        }
171                        _ => break object_id,
172                    }
173                };
174                self.peeled = Some(peeled_id);
175                self.target = Target::Object(peeled_id);
176                Ok(peeled_id)
177            }
178        }
179    }
180
181    fn follow_to_object_in_place_packed(
182        &mut self,
183        store: &file::Store,
184        packed: Option<&packed::Buffer>,
185    ) -> ExnResult<ObjectId> {
186        self.follow_to_object_packed(store, packed)
187    }
188
189    /// Cycle failures include [metadata](gix_error::Exn::metadata()) `path` (native path); depth-limit failures include
190    /// `max_depth` (unsigned integer).
191    fn follow_to_object_packed(&mut self, store: &file::Store, packed: Option<&packed::Buffer>) -> ExnResult<ObjectId> {
192        match self.target {
193            Target::Object(id) => Ok(id),
194            Target::Symbolic(_) => {
195                let mut seen = BTreeSet::new();
196                let cursor = &mut *self;
197                while let Some(next) = cursor.follow_packed(store, packed) {
198                    let next = next?;
199                    if seen.contains(&next.name) {
200                        return Err(corruption("Aborting symbolic reference cycle")
201                            .with("path", store.reference_path(cursor.name.as_ref()))
202                            .raise_erased());
203                    }
204                    *cursor = next;
205                    seen.insert(cursor.name.clone());
206                    const MAX_REF_DEPTH: usize = 5;
207                    if seen.len() == MAX_REF_DEPTH {
208                        return Err(Message::new("Symbolic reference depth limit exceeded")
209                            .with("max_depth", MAX_REF_DEPTH)
210                            .raise_erased());
211                    }
212                }
213                let oid = self.target.try_id().expect("peeled ref").to_owned();
214                Ok(oid)
215            }
216        }
217    }
218
219    fn follow(&self, store: &file::Store) -> Option<ExnResult<Reference>> {
220        let packed = match store.assure_packed_refs_uptodate() {
221            Ok(packed) => packed,
222            Err(err) => return Some(Err(err)),
223        };
224        self.follow_packed(store, packed.as_ref().map(|b| &***b))
225    }
226
227    fn follow_packed(&self, store: &file::Store, packed: Option<&packed::Buffer>) -> Option<ExnResult<Reference>> {
228        match &self.target {
229            Target::Object(_) => None,
230            Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) {
231                Ok(Some(next)) => Some(Ok(next)),
232                Ok(None) => Some(Err(file::find::NotFound {
233                    name: full_name.to_path().to_owned(),
234                }
235                .raise_erased())),
236                Err(err) => Some(Err(err)),
237            },
238        }
239    }
240}