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    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    /// Like [`ReferenceExt::peel_to_id_in_place()`], but with support for a known stable `packed` buffer
36    /// to use for resolving symbolic links.
37    fn peel_to_id_in_place_packed(
38        &mut self,
39        store: &file::Store,
40        objects: &dyn gix_object::Find,
41        packed: Option<&packed::Buffer>,
42    ) -> Result<ObjectId, peel::to_id::Error>;
43
44    /// Like [`ReferenceExt::follow()`], but follows all symbolic references while gracefully handling loops,
45    /// altering this instance in place.
46    fn follow_to_object_in_place_packed(
47        &mut self,
48        store: &file::Store,
49        packed: Option<&packed::Buffer>,
50    ) -> Result<ObjectId, peel::to_object::Error>;
51
52    /// Follow this symbolic reference one level and return the ref it refers to.
53    ///
54    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
55    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>;
56
57    /// Follow this symbolic reference one level and return the ref it refers to,
58    /// possibly providing access to `packed` references for lookup if it contains the referent.
59    ///
60    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
61    fn follow_packed(
62        &self,
63        store: &file::Store,
64        packed: Option<&packed::Buffer>,
65    ) -> Option<Result<Reference, file::find::existing::Error>>;
66}
67
68impl ReferenceExt for Reference {
69    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> {
70        log::iter::Platform {
71            store,
72            name: self.name.as_ref(),
73            buf: Vec::new(),
74        }
75    }
76
77    fn log_exists(&self, store: &file::Store) -> bool {
78        store
79            .reflog_exists(self.name.as_ref())
80            .expect("infallible name conversion")
81    }
82
83    fn peel_to_id_in_place(
84        &mut self,
85        store: &file::Store,
86        objects: &dyn gix_object::Find,
87    ) -> Result<ObjectId, peel::to_id::Error> {
88        let packed = store.assure_packed_refs_uptodate().map_err(|err| {
89            peel::to_id::Error::FollowToObject(peel::to_object::Error::Follow(file::find::existing::Error::Find(
90                file::find::Error::PackedOpen(err),
91            )))
92        })?;
93        self.peel_to_id_in_place_packed(store, objects, packed.as_ref().map(|b| &***b))
94    }
95
96    fn peel_to_id_in_place_packed(
97        &mut self,
98        store: &file::Store,
99        objects: &dyn gix_object::Find,
100        packed: Option<&packed::Buffer>,
101    ) -> Result<ObjectId, peel::to_id::Error> {
102        match self.peeled {
103            Some(peeled) => {
104                self.target = Target::Object(peeled.to_owned());
105                Ok(peeled)
106            }
107            None => {
108                let mut oid = self.follow_to_object_in_place_packed(store, packed)?;
109                let mut buf = Vec::new();
110                let peeled_id = loop {
111                    let gix_object::Data { kind, data } =
112                        objects
113                            .try_find(&oid, &mut buf)?
114                            .ok_or_else(|| peel::to_id::Error::NotFound {
115                                oid,
116                                name: self.name.0.clone(),
117                            })?;
118                    match kind {
119                        gix_object::Kind::Tag => {
120                            oid = gix_object::TagRefIter::from_bytes(data).target_id().map_err(|_err| {
121                                peel::to_id::Error::NotFound {
122                                    oid,
123                                    name: self.name.0.clone(),
124                                }
125                            })?;
126                        }
127                        _ => break oid,
128                    }
129                };
130                self.peeled = Some(peeled_id);
131                self.target = Target::Object(peeled_id);
132                Ok(peeled_id)
133            }
134        }
135    }
136
137    fn follow_to_object_in_place_packed(
138        &mut self,
139        store: &file::Store,
140        packed: Option<&packed::Buffer>,
141    ) -> Result<ObjectId, peel::to_object::Error> {
142        match self.target {
143            Target::Object(id) => Ok(id),
144            Target::Symbolic(_) => {
145                let mut seen = BTreeSet::new();
146                let cursor = &mut *self;
147                while let Some(next) = cursor.follow_packed(store, packed) {
148                    let next = next?;
149                    if seen.contains(&next.name) {
150                        return Err(peel::to_object::Error::Cycle {
151                            start_absolute: store.reference_path(cursor.name.as_ref()),
152                        });
153                    }
154                    *cursor = next;
155                    seen.insert(cursor.name.clone());
156                    const MAX_REF_DEPTH: usize = 5;
157                    if seen.len() == MAX_REF_DEPTH {
158                        return Err(peel::to_object::Error::DepthLimitExceeded {
159                            max_depth: MAX_REF_DEPTH,
160                        });
161                    }
162                }
163                let oid = self.target.try_id().expect("peeled ref").to_owned();
164                Ok(oid)
165            }
166        }
167    }
168
169    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>> {
170        let packed = match store
171            .assure_packed_refs_uptodate()
172            .map_err(|err| file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
173        {
174            Ok(packed) => packed,
175            Err(err) => return Some(Err(err)),
176        };
177        self.follow_packed(store, packed.as_ref().map(|b| &***b))
178    }
179
180    fn follow_packed(
181        &self,
182        store: &file::Store,
183        packed: Option<&packed::Buffer>,
184    ) -> Option<Result<Reference, file::find::existing::Error>> {
185        match &self.target {
186            Target::Object(_) => None,
187            Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) {
188                Ok(Some(next)) => Some(Ok(next)),
189                Ok(None) => Some(Err(file::find::existing::Error::NotFound {
190                    name: full_name.to_path().to_owned(),
191                })),
192                Err(err) => Some(Err(file::find::existing::Error::Find(err))),
193            },
194        }
195    }
196}