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