1use std::{
2 borrow::Cow,
3 io::{self, Read},
4 path::{Path, PathBuf},
5};
6
7pub use error::Error;
8
9use crate::name::is_pseudo_ref;
10use crate::{
11 file,
12 store_impl::{file::loose, packed},
13 BStr, BString, FullNameRef, PartialName, PartialNameRef, Reference,
14};
15
16impl file::Store {
27 pub fn try_find<'a, Name, E>(&self, partial: Name) -> Result<Option<Reference>, Error>
39 where
40 Name: TryInto<&'a PartialNameRef, Error = E>,
41 Error: From<E>,
42 {
43 let packed = self.assure_packed_refs_uptodate()?;
44 self.find_one_with_verified_input(partial.try_into()?, packed.as_ref().map(|b| &***b))
45 }
46
47 pub fn try_find_loose<'a, Name, E>(&self, partial: Name) -> Result<Option<loose::Reference>, Error>
53 where
54 Name: TryInto<&'a PartialNameRef, Error = E>,
55 Error: From<E>,
56 {
57 self.find_one_with_verified_input(partial.try_into()?, None)
58 .map(|r| r.map(Into::into))
59 }
60
61 pub fn try_find_packed<'a, Name, E>(
63 &self,
64 partial: Name,
65 packed: Option<&packed::Buffer>,
66 ) -> Result<Option<Reference>, Error>
67 where
68 Name: TryInto<&'a PartialNameRef, Error = E>,
69 Error: From<E>,
70 {
71 self.find_one_with_verified_input(partial.try_into()?, packed)
72 }
73
74 pub(crate) fn find_one_with_verified_input(
75 &self,
76 partial_name: &PartialNameRef,
77 packed: Option<&packed::Buffer>,
78 ) -> Result<Option<Reference>, Error> {
79 fn decompose_if(mut r: Reference, input_changed_to_precomposed: bool) -> Reference {
80 if input_changed_to_precomposed {
81 use gix_object::bstr::ByteSlice;
82 let decomposed = r
83 .name
84 .0
85 .to_str()
86 .ok()
87 .map(|name| gix_utils::str::decompose(name.into()));
88 if let Some(Cow::Owned(decomposed)) = decomposed {
89 r.name.0 = decomposed.into();
90 }
91 }
92 r
93 }
94 let mut buf = BString::default();
95 let mut precomposed_partial_name_storage = packed.filter(|_| self.precompose_unicode).and_then(|_| {
96 use gix_object::bstr::ByteSlice;
97 let precomposed = partial_name.0.to_str().ok()?;
98 let precomposed = gix_utils::str::precompose(precomposed.into());
99 match precomposed {
100 Cow::Owned(precomposed) => Some(PartialName(precomposed.into())),
101 Cow::Borrowed(_) => None,
102 }
103 });
104 let precomposed_partial_name = precomposed_partial_name_storage
105 .as_ref()
106 .map(std::convert::AsRef::as_ref);
107 for consider_pseudo_ref in [true, false] {
108 if !consider_pseudo_ref && !is_pseudo_ref(partial_name.as_bstr()) {
109 break;
110 }
111 'try_directories: for inbetween in &["", "tags", "heads", "remotes"] {
112 match self.find_inner(
113 inbetween,
114 partial_name,
115 precomposed_partial_name,
116 packed,
117 &mut buf,
118 consider_pseudo_ref,
119 ) {
120 Ok(Some(r)) => return Ok(Some(decompose_if(r, precomposed_partial_name.is_some()))),
121 Ok(None) => {
122 if consider_pseudo_ref && is_pseudo_ref(partial_name.as_bstr()) {
123 break 'try_directories;
124 }
125 continue;
126 }
127 Err(err) => return Err(err),
128 }
129 }
130 }
131 if partial_name.as_bstr() != "HEAD" {
132 if let Some(mut precomposed) = precomposed_partial_name_storage {
133 precomposed = precomposed.join("HEAD".into()).expect("HEAD is valid name");
134 precomposed_partial_name_storage = Some(precomposed);
135 }
136 self.find_inner(
137 "remotes",
138 partial_name
139 .to_owned()
140 .join("HEAD".into())
141 .expect("HEAD is valid name")
142 .as_ref(),
143 precomposed_partial_name_storage
144 .as_ref()
145 .map(std::convert::AsRef::as_ref),
146 None,
147 &mut buf,
148 true, )
150 .map(|res| res.map(|r| decompose_if(r, precomposed_partial_name_storage.is_some())))
151 } else {
152 Ok(None)
153 }
154 }
155
156 fn find_inner(
157 &self,
158 inbetween: &str,
159 partial_name: &PartialNameRef,
160 precomposed_partial_name: Option<&PartialNameRef>,
161 packed: Option<&packed::Buffer>,
162 path_buf: &mut BString,
163 consider_pseudo_ref: bool,
164 ) -> Result<Option<Reference>, Error> {
165 let full_name = precomposed_partial_name
166 .unwrap_or(partial_name)
167 .construct_full_name_ref(inbetween, path_buf, consider_pseudo_ref);
168 let content_buf = self.ref_contents(full_name).map_err(|err| Error::ReadFileContents {
169 source: err,
170 path: self.reference_path(full_name),
171 })?;
172
173 match content_buf {
174 None => {
175 if let Some(packed) = packed {
176 if let Some(full_name) = packed::find::transform_full_name_for_lookup(full_name) {
177 let full_name_backing;
178 let full_name = match &self.namespace {
179 Some(namespace) => {
180 full_name_backing = namespace.to_owned().into_namespaced_name(full_name);
181 full_name_backing.as_ref()
182 }
183 None => full_name,
184 };
185 if let Some(packed_ref) = packed.try_find_full_name(full_name)? {
186 let mut res: Reference = packed_ref.into();
187 if let Some(namespace) = &self.namespace {
188 res.strip_namespace(namespace);
189 }
190 return Ok(Some(res));
191 }
192 }
193 }
194 Ok(None)
195 }
196 Some(content) => Ok(Some(
197 loose::Reference::try_from_path(full_name.to_owned(), &content)
198 .map(Into::into)
199 .map(|mut r: Reference| {
200 if let Some(namespace) = &self.namespace {
201 r.strip_namespace(namespace);
202 }
203 r
204 })
205 .map_err(|err| Error::ReferenceCreation {
206 source: err,
207 relative_path: full_name.to_path().to_owned(),
208 })?,
209 )),
210 }
211 }
212}
213
214impl file::Store {
215 pub(crate) fn to_base_dir_and_relative_name<'a>(
216 &self,
217 name: &'a FullNameRef,
218 is_reflog: bool,
219 ) -> (Cow<'_, Path>, &'a FullNameRef) {
220 let commondir = self.common_dir_resolved();
221 let linked_git_dir =
222 |worktree_name: &BStr| commondir.join("worktrees").join(gix_path::from_bstr(worktree_name));
223 name.category_and_short_name()
224 .map(|(c, sn)| {
225 use crate::Category::*;
226 let sn = FullNameRef::new_unchecked(sn);
227 match c {
228 LinkedPseudoRef { name: worktree_name } => {
229 if is_reflog {
230 (linked_git_dir(worktree_name).into(), sn)
231 } else {
232 (commondir.into(), name)
233 }
234 }
235 Tag | LocalBranch | RemoteBranch | Note => (commondir.into(), name),
236 MainRef | MainPseudoRef => (commondir.into(), sn),
237 LinkedRef { name: worktree_name } => {
238 if sn.category().is_some_and(|cat| cat.is_worktree_private()) {
239 if is_reflog {
240 (linked_git_dir(worktree_name).into(), sn)
241 } else {
242 (commondir.into(), name)
243 }
244 } else {
245 (commondir.into(), sn)
246 }
247 }
248 PseudoRef | Bisect | Rewritten | WorktreePrivate => (self.git_dir.as_path().into(), name),
249 }
250 })
251 .unwrap_or((commondir.into(), name))
252 }
253
254 pub(crate) fn reference_path_with_base<'b>(&self, name: &'b FullNameRef) -> (Cow<'_, Path>, Cow<'b, Path>) {
256 let (base, name) = self.to_base_dir_and_relative_name(name, false);
257 (
258 base,
259 match &self.namespace {
260 None => gix_path::to_native_path_on_windows(name.as_bstr()),
261 Some(namespace) => {
262 gix_path::to_native_path_on_windows(namespace.to_owned().into_namespaced_name(name).into_inner())
263 }
264 },
265 )
266 }
267
268 pub(crate) fn reference_path(&self, name: &FullNameRef) -> PathBuf {
270 let (base, relative_path) = self.reference_path_with_base(name);
271 base.join(relative_path)
272 }
273
274 pub(crate) fn ref_contents(&self, name: &FullNameRef) -> io::Result<Option<Vec<u8>>> {
276 let (base, relative_path) = self.reference_path_with_base(name);
277 if self.prohibit_windows_device_names
278 && relative_path
279 .components()
280 .filter_map(|c| gix_path::try_os_str_into_bstr(c.as_os_str().into()).ok())
281 .any(|c| gix_validate::path::component_is_windows_device(c.as_ref()))
282 {
283 return Err(std::io::Error::new(
284 std::io::ErrorKind::Other,
285 format!("Illegal use of reserved Windows device name in \"{}\"", name.as_bstr()),
286 ));
287 }
288
289 let ref_path = base.join(relative_path);
290 match std::fs::File::open(&ref_path) {
291 Ok(mut file) => {
292 let mut buf = Vec::with_capacity(128);
293 if let Err(err) = file.read_to_end(&mut buf) {
294 return if ref_path.is_dir() { Ok(None) } else { Err(err) };
295 }
296 Ok(buf.into())
297 }
298 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
299 #[cfg(windows)]
300 Err(err) if err.kind() == std::io::ErrorKind::PermissionDenied => Ok(None),
301 Err(err) => Err(err),
302 }
303 }
304}
305
306pub mod existing {
308 pub use error::Error;
309
310 use crate::{
311 file::{self},
312 store_impl::{
313 file::{find, loose},
314 packed,
315 },
316 PartialNameRef, Reference,
317 };
318
319 impl file::Store {
320 pub fn find<'a, Name, E>(&self, partial: Name) -> Result<Reference, Error>
322 where
323 Name: TryInto<&'a PartialNameRef, Error = E>,
324 crate::name::Error: From<E>,
325 {
326 let packed = self.assure_packed_refs_uptodate().map_err(find::Error::PackedOpen)?;
327 self.find_existing_inner(partial, packed.as_ref().map(|b| &***b))
328 }
329
330 pub fn find_packed<'a, Name, E>(
332 &self,
333 partial: Name,
334 packed: Option<&packed::Buffer>,
335 ) -> Result<Reference, Error>
336 where
337 Name: TryInto<&'a PartialNameRef, Error = E>,
338 crate::name::Error: From<E>,
339 {
340 self.find_existing_inner(partial, packed)
341 }
342
343 pub fn find_loose<'a, Name, E>(&self, partial: Name) -> Result<loose::Reference, Error>
345 where
346 Name: TryInto<&'a PartialNameRef, Error = E>,
347 crate::name::Error: From<E>,
348 {
349 self.find_existing_inner(partial, None).map(Into::into)
350 }
351
352 pub(crate) fn find_existing_inner<'a, Name, E>(
354 &self,
355 partial: Name,
356 packed: Option<&packed::Buffer>,
357 ) -> Result<Reference, Error>
358 where
359 Name: TryInto<&'a PartialNameRef, Error = E>,
360 crate::name::Error: From<E>,
361 {
362 let path = partial
363 .try_into()
364 .map_err(|err| Error::Find(find::Error::RefnameValidation(err.into())))?;
365 match self.find_one_with_verified_input(path, packed) {
366 Ok(Some(r)) => Ok(r),
367 Ok(None) => Err(Error::NotFound {
368 name: path.to_partial_path().to_owned(),
369 }),
370 Err(err) => Err(err.into()),
371 }
372 }
373 }
374
375 mod error {
376 use std::path::PathBuf;
377
378 use crate::store_impl::file::find;
379
380 #[derive(Debug, thiserror::Error)]
382 #[allow(missing_docs)]
383 pub enum Error {
384 #[error("An error occurred while trying to find a reference")]
385 Find(#[from] find::Error),
386 #[error("The ref partially named {name:?} could not be found")]
387 NotFound { name: PathBuf },
388 }
389 }
390}
391
392mod error {
393 use std::{convert::Infallible, io, path::PathBuf};
394
395 use crate::{file, store_impl::packed};
396
397 #[derive(Debug, thiserror::Error)]
399 #[allow(missing_docs)]
400 pub enum Error {
401 #[error("The ref name or path is not a valid ref name")]
402 RefnameValidation(#[from] crate::name::Error),
403 #[error("The ref file {path:?} could not be read in full")]
404 ReadFileContents { source: io::Error, path: PathBuf },
405 #[error("The reference at \"{relative_path}\" could not be instantiated")]
406 ReferenceCreation {
407 source: file::loose::reference::decode::Error,
408 relative_path: PathBuf,
409 },
410 #[error("A packed ref lookup failed")]
411 PackedRef(#[from] packed::find::Error),
412 #[error("Could not open the packed refs buffer when trying to find references.")]
413 PackedOpen(#[from] packed::buffer::open::Error),
414 }
415
416 impl From<Infallible> for Error {
417 fn from(_: Infallible) -> Self {
418 unreachable!("this impl is needed to allow passing a known valid partial path as parameter")
419 }
420 }
421}