git_ref/store/file/
raw_ext.rs

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