gix_ref/store/file/
raw_ext.rs

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