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
15pub trait ReferenceExt: Sealed {
17 fn log_iter<'a, 's>(&'a self, store: &'s file::Store) -> log::iter::Platform<'a, 's>;
19
20 fn log_exists(&self, store: &file::Store) -> bool;
22
23 #[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 fn peel_to_id(&mut self, store: &file::Store, objects: &dyn gix_object::Find) -> ExnResult<ObjectId>;
42
43 #[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 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 #[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 fn follow_to_object_packed(&mut self, store: &file::Store, packed: Option<&packed::Buffer>) -> ExnResult<ObjectId>;
79
80 fn follow(&self, store: &file::Store) -> Option<ExnResult<Reference>>;
84
85 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 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 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}