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