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
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 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 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 fn follow(&self, store: &file::Store) -> Option<Result<Reference, file::find::existing::Error>>;
42
43 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}