1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::collections::BTreeSet;

use gix_hash::ObjectId;

use crate::{
    packed, peel,
    raw::Reference,
    store_impl::{file, file::log},
    Target,
};

pub trait Sealed {}
impl Sealed for crate::Reference {}

/// A trait to extend [Reference][crate::Reference] with functionality requiring a [file::Store].
pub trait ReferenceExt: Sealed {
    /// A step towards obtaining forward or reverse iterators on reference logs.
    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's>;

    /// For details, see [`Reference::log_exists()`].
    fn log_exists(&self, store: &file::Store) -> bool;

    /// Follow all symbolic targets this reference might point to and peel the underlying object
    /// to the end of the chain, and return it, using `objects` to access them.
    ///
    /// This is useful to learn where this reference is ultimately pointing to.
    fn peel_to_id_in_place(
        &mut self,
        store: &file::Store,
        objects: &dyn gix_object::Find,
    ) -> Result<ObjectId, peel::to_id::Error>;

    /// Like [`ReferenceExt::peel_to_id_in_place()`], but with support for a known stable packed buffer
    /// to use for resolving symbolic links.
    fn peel_to_id_in_place_packed(
        &mut self,
        store: &file::Store,
        objects: &dyn gix_object::Find,
        packed: Option<&packed::Buffer>,
    ) -> Result<ObjectId, peel::to_id::Error>;

    /// Follow this symbolic reference one level and return the ref it refers to.
    ///
    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>;

    /// Follow this symbolic reference one level and return the ref it refers to,
    /// possibly providing access to `packed` references for lookup if it contains the referent.
    ///
    /// Returns `None` if this is not a symbolic reference, hence the leaf of the chain.
    fn follow_packed(
        &self,
        store: &file::Store,
        packed: Option<&packed::Buffer>,
    ) -> Option<Result<Reference, file::find::existing::Error>>;
}

impl ReferenceExt for Reference {
    fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's> {
        log::iter::Platform {
            store,
            name: self.name.as_ref(),
            buf: Vec::new(),
        }
    }

    fn log_exists(&self, store: &file::Store) -> bool {
        store
            .reflog_exists(self.name.as_ref())
            .expect("infallible name conversion")
    }

    fn peel_to_id_in_place(
        &mut self,
        store: &file::Store,
        objects: &dyn gix_object::Find,
    ) -> Result<ObjectId, peel::to_id::Error> {
        let packed = store.assure_packed_refs_uptodate().map_err(|err| {
            peel::to_id::Error::Follow(file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
        })?;
        self.peel_to_id_in_place_packed(store, objects, packed.as_ref().map(|b| &***b))
    }

    fn peel_to_id_in_place_packed(
        &mut self,
        store: &file::Store,
        objects: &dyn gix_object::Find,
        packed: Option<&packed::Buffer>,
    ) -> Result<ObjectId, peel::to_id::Error> {
        match self.peeled {
            Some(peeled) => {
                self.target = Target::Peeled(peeled.to_owned());
                Ok(peeled)
            }
            None => {
                if self.target.kind() == crate::Kind::Symbolic {
                    let mut seen = BTreeSet::new();
                    let cursor = &mut *self;
                    while let Some(next) = cursor.follow_packed(store, packed) {
                        let next = next?;
                        if seen.contains(&next.name) {
                            return Err(peel::to_id::Error::Cycle {
                                start_absolute: store.reference_path(cursor.name.as_ref()),
                            });
                        }
                        *cursor = next;
                        seen.insert(cursor.name.clone());
                        const MAX_REF_DEPTH: usize = 5;
                        if seen.len() == MAX_REF_DEPTH {
                            return Err(peel::to_id::Error::DepthLimitExceeded {
                                max_depth: MAX_REF_DEPTH,
                            });
                        }
                    }
                };
                let mut buf = Vec::new();
                let mut oid = self.target.try_id().expect("peeled ref").to_owned();
                let peeled_id = loop {
                    let gix_object::Data { kind, data } =
                        objects
                            .try_find(&oid, &mut buf)?
                            .ok_or_else(|| peel::to_id::Error::NotFound {
                                oid,
                                name: self.name.0.clone(),
                            })?;
                    match kind {
                        gix_object::Kind::Tag => {
                            oid = gix_object::TagRefIter::from_bytes(data).target_id().map_err(|_err| {
                                peel::to_id::Error::NotFound {
                                    oid,
                                    name: self.name.0.clone(),
                                }
                            })?;
                        }
                        _ => break oid,
                    };
                };
                self.peeled = Some(peeled_id);
                self.target = Target::Peeled(peeled_id);
                Ok(peeled_id)
            }
        }
    }

    fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>> {
        let packed = match store
            .assure_packed_refs_uptodate()
            .map_err(|err| file::find::existing::Error::Find(file::find::Error::PackedOpen(err)))
        {
            Ok(packed) => packed,
            Err(err) => return Some(Err(err)),
        };
        self.follow_packed(store, packed.as_ref().map(|b| &***b))
    }

    fn follow_packed(
        &self,
        store: &file::Store,
        packed: Option<&packed::Buffer>,
    ) -> Option<Result<Reference, file::find::existing::Error>> {
        match self.peeled {
            Some(peeled) => Some(Ok(Reference {
                name: self.name.clone(),
                target: Target::Peeled(peeled),
                peeled: None,
            })),
            None => match &self.target {
                Target::Peeled(_) => None,
                Target::Symbolic(full_name) => match store.try_find_packed(full_name.as_ref(), packed) {
                    Ok(Some(next)) => Some(Ok(next)),
                    Ok(None) => Some(Err(file::find::existing::Error::NotFound {
                        name: full_name.to_path().to_owned(),
                    })),
                    Err(err) => Some(Err(file::find::existing::Error::Find(err))),
                },
            },
        }
    }
}