1use std::{
2 borrow::Cow,
3 convert::TryInto,
4 io::{self, Read},
5 path::{Path, PathBuf},
6};
7
8pub use error::Error;
9
10use crate::{
11 file,
12 store_impl::{file::loose, packed},
13 BStr, BString, FullNameRef, PartialNameRef, Reference,
14};
15
16enum Transform {
17 EnforceRefsPrefix,
18 None,
19}
20
21impl file::Store {
22 pub fn try_find<'a, Name, E>(&self, partial: Name) -> Result<Option<Reference>, Error>
34 where
35 Name: TryInto<&'a PartialNameRef, Error = E>,
36 Error: From<E>,
37 {
38 let packed = self.assure_packed_refs_uptodate()?;
39 self.find_one_with_verified_input(partial.try_into()?, packed.as_ref().map(|b| &***b))
40 }
41
42 pub fn try_find_loose<'a, Name, E>(&self, partial: Name) -> Result<Option<loose::Reference>, Error>
48 where
49 Name: TryInto<&'a PartialNameRef, Error = E>,
50 Error: From<E>,
51 {
52 self.find_one_with_verified_input(partial.try_into()?, None)
53 .map(|r| r.map(|r| r.try_into().expect("only loose refs are found without pack")))
54 }
55
56 pub fn try_find_packed<'a, Name, E>(
58 &self,
59 partial: Name,
60 packed: Option<&packed::Buffer>,
61 ) -> Result<Option<Reference>, Error>
62 where
63 Name: TryInto<&'a PartialNameRef, Error = E>,
64 Error: From<E>,
65 {
66 self.find_one_with_verified_input(partial.try_into()?, packed)
67 }
68
69 pub(crate) fn find_one_with_verified_input(
70 &self,
71 partial_name: &PartialNameRef,
72 packed: Option<&packed::Buffer>,
73 ) -> Result<Option<Reference>, Error> {
74 let mut buf = BString::default();
75 if partial_name.looks_like_full_name() {
76 if let Some(r) = self.find_inner("", partial_name, None, Transform::None, &mut buf)? {
77 return Ok(Some(r));
78 }
79 }
80
81 for inbetween in &["", "tags", "heads", "remotes"] {
82 match self.find_inner(inbetween, partial_name, packed, Transform::EnforceRefsPrefix, &mut buf) {
83 Ok(Some(r)) => return Ok(Some(r)),
84 Ok(None) => {
85 continue;
86 }
87 Err(err) => return Err(err),
88 }
89 }
90 self.find_inner(
91 "remotes",
92 partial_name
93 .to_owned()
94 .join("HEAD")
95 .expect("HEAD is valid name")
96 .as_ref(),
97 None,
98 Transform::EnforceRefsPrefix,
99 &mut buf,
100 )
101 }
102
103 fn find_inner(
104 &self,
105 inbetween: &str,
106 partial_name: &PartialNameRef,
107 packed: Option<&packed::Buffer>,
108 transform: Transform,
109 path_buf: &mut BString,
110 ) -> Result<Option<Reference>, Error> {
111 let add_refs_prefix = matches!(transform, Transform::EnforceRefsPrefix);
112 let full_name = partial_name.construct_full_name_ref(add_refs_prefix, inbetween, path_buf);
113 let content_buf = self.ref_contents(full_name).map_err(|err| Error::ReadFileContents {
114 source: err,
115 path: self.reference_path(full_name),
116 })?;
117
118 match content_buf {
119 None => {
120 if let Some(packed) = packed {
121 if let Some(full_name) = packed::find::transform_full_name_for_lookup(full_name) {
122 let full_name_backing;
123 let full_name = match &self.namespace {
124 Some(namespace) => {
125 full_name_backing = namespace.to_owned().into_namespaced_name(full_name);
126 full_name_backing.as_ref()
127 }
128 None => full_name,
129 };
130 if let Some(packed_ref) = packed.try_find_full_name(full_name)? {
131 let mut res: Reference = packed_ref.into();
132 if let Some(namespace) = &self.namespace {
133 res.strip_namespace(namespace);
134 }
135 return Ok(Some(res));
136 };
137 }
138 }
139 Ok(None)
140 }
141 Some(content) => Ok(Some(
142 loose::Reference::try_from_path(full_name.to_owned(), &content)
143 .map(Into::into)
144 .map(|mut r: Reference| {
145 if let Some(namespace) = &self.namespace {
146 r.strip_namespace(namespace);
147 }
148 r
149 })
150 .map_err(|err| Error::ReferenceCreation {
151 source: err,
152 relative_path: full_name.to_path().to_owned(),
153 })?,
154 )),
155 }
156 }
157}
158
159impl file::Store {
160 pub(crate) fn to_base_dir_and_relative_name<'a>(
161 &self,
162 name: &'a FullNameRef,
163 is_reflog: bool,
164 ) -> (Cow<'_, Path>, &'a FullNameRef) {
165 let commondir = self.common_dir_resolved();
166 let linked_git_dir =
167 |worktree_name: &BStr| commondir.join("worktrees").join(git_path::from_bstr(worktree_name));
168 name.category_and_short_name()
169 .and_then(|(c, sn)| {
170 use crate::Category::*;
171 let sn = FullNameRef::new_unchecked(sn);
172 Some(match c {
173 LinkedPseudoRef { name: worktree_name } => is_reflog
174 .then(|| (linked_git_dir(worktree_name).into(), sn))
175 .unwrap_or((commondir.into(), name)),
176 Tag | LocalBranch | RemoteBranch | Note => (commondir.into(), name),
177 MainRef | MainPseudoRef => (commondir.into(), sn),
178 LinkedRef { name: worktree_name } => sn
179 .category()
180 .map_or(false, |cat| cat.is_worktree_private())
181 .then(|| {
182 if is_reflog {
183 (linked_git_dir(worktree_name).into(), sn)
184 } else {
185 (commondir.into(), name)
186 }
187 })
188 .unwrap_or((commondir.into(), sn)),
189 PseudoRef | Bisect | Rewritten | WorktreePrivate => return None,
190 })
191 })
192 .unwrap_or((self.git_dir.as_path().into(), name))
193 }
194
195 pub(crate) fn reference_path_with_base<'b>(&self, name: &'b FullNameRef) -> (Cow<'_, Path>, Cow<'b, Path>) {
197 let (base, name) = self.to_base_dir_and_relative_name(name, false);
198 (
199 base,
200 match &self.namespace {
201 None => git_path::to_native_path_on_windows(name.as_bstr()),
202 Some(namespace) => {
203 git_path::to_native_path_on_windows(namespace.to_owned().into_namespaced_name(name).into_inner())
204 }
205 },
206 )
207 }
208
209 pub(crate) fn reference_path(&self, name: &FullNameRef) -> PathBuf {
211 let (base, relative_path) = self.reference_path_with_base(name);
212 base.join(relative_path)
213 }
214
215 pub(crate) fn ref_contents(&self, name: &FullNameRef) -> io::Result<Option<Vec<u8>>> {
217 let ref_path = self.reference_path(name);
218
219 match std::fs::File::open(&ref_path) {
220 Ok(mut file) => {
221 let mut buf = Vec::with_capacity(128);
222 if let Err(err) = file.read_to_end(&mut buf) {
223 return if ref_path.is_dir() { Ok(None) } else { Err(err) };
224 }
225 Ok(buf.into())
226 }
227 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
228 #[cfg(windows)]
229 Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => Ok(None),
230 Err(err) => Err(err),
231 }
232 }
233}
234
235pub mod existing {
237 use std::convert::TryInto;
238
239 pub use error::Error;
240
241 use crate::{
242 file::{self},
243 store_impl::{
244 file::{find, loose},
245 packed,
246 },
247 PartialNameRef, Reference,
248 };
249
250 impl file::Store {
251 pub fn find<'a, Name, E>(&self, partial: Name) -> Result<Reference, Error>
253 where
254 Name: TryInto<&'a PartialNameRef, Error = E>,
255 crate::name::Error: From<E>,
256 {
257 let packed = self.assure_packed_refs_uptodate().map_err(find::Error::PackedOpen)?;
258 self.find_existing_inner(partial, packed.as_ref().map(|b| &***b))
259 }
260
261 pub fn find_packed<'a, Name, E>(
263 &self,
264 partial: Name,
265 packed: Option<&packed::Buffer>,
266 ) -> Result<Reference, Error>
267 where
268 Name: TryInto<&'a PartialNameRef, Error = E>,
269 crate::name::Error: From<E>,
270 {
271 self.find_existing_inner(partial, packed)
272 }
273
274 pub fn find_loose<'a, Name, E>(&self, partial: Name) -> Result<loose::Reference, Error>
276 where
277 Name: TryInto<&'a PartialNameRef, Error = E>,
278 crate::name::Error: From<E>,
279 {
280 self.find_existing_inner(partial, None)
281 .map(|r| r.try_into().expect("always loose without packed"))
282 }
283
284 pub(crate) fn find_existing_inner<'a, Name, E>(
286 &self,
287 partial: Name,
288 packed: Option<&packed::Buffer>,
289 ) -> Result<Reference, Error>
290 where
291 Name: TryInto<&'a PartialNameRef, Error = E>,
292 crate::name::Error: From<E>,
293 {
294 let path = partial
295 .try_into()
296 .map_err(|err| Error::Find(find::Error::RefnameValidation(err.into())))?;
297 match self.find_one_with_verified_input(path, packed) {
298 Ok(Some(r)) => Ok(r),
299 Ok(None) => Err(Error::NotFound {
300 name: path.to_partial_path().to_owned(),
301 }),
302 Err(err) => Err(err.into()),
303 }
304 }
305 }
306
307 mod error {
308 use std::path::PathBuf;
309
310 use crate::store_impl::file::find;
311
312 #[derive(Debug, thiserror::Error)]
314 #[allow(missing_docs)]
315 pub enum Error {
316 #[error("An error occurred while trying to find a reference")]
317 Find(#[from] find::Error),
318 #[error("The ref partially named {name:?} could not be found")]
319 NotFound { name: PathBuf },
320 }
321 }
322}
323
324mod error {
325 use std::{convert::Infallible, io, path::PathBuf};
326
327 use crate::{file, store_impl::packed};
328
329 #[derive(Debug, thiserror::Error)]
331 #[allow(missing_docs)]
332 pub enum Error {
333 #[error("The ref name or path is not a valid ref name")]
334 RefnameValidation(#[from] crate::name::Error),
335 #[error("The ref file {path:?} could not be read in full")]
336 ReadFileContents { source: io::Error, path: PathBuf },
337 #[error("The reference at \"{relative_path}\" could not be instantiated")]
338 ReferenceCreation {
339 source: file::loose::reference::decode::Error,
340 relative_path: PathBuf,
341 },
342 #[error("A packed ref lookup failed")]
343 PackedRef(#[from] packed::find::Error),
344 #[error("Could not open the packed refs buffer when trying to find references.")]
345 PackedOpen(#[from] packed::buffer::open::Error),
346 }
347
348 impl From<Infallible> for Error {
349 fn from(_: Infallible) -> Self {
350 unreachable!("this impl is needed to allow passing a known valid partial path as parameter")
351 }
352 }
353}