git_ref/store/file/
find.rs

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    /// Find a single reference by the given `path` which is required to be a valid reference name.
23    ///
24    /// Returns `Ok(None)` if no such ref exists.
25    ///
26    /// ### Note
27    ///
28    /// * The lookup algorithm follows the one in [the git documentation][git-lookup-docs].
29    /// * The packed buffer is checked for modifications each time the method is called. See [`file::Store::try_find_packed()`]
30    ///   for a version with more control.
31    ///
32    /// [git-lookup-docs]: https://github.com/git/git/blob/5d5b1473453400224ebb126bf3947e0a3276bdf5/Documentation/revisions.txt#L34-L46
33    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    /// Similar to [`file::Store::find()`] but a non-existing ref is treated as error.
43    ///
44    /// Find only loose references, that is references that aren't in the packed-refs buffer.
45    /// All symbolic references are loose references.
46    /// `HEAD` is always a loose reference.
47    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    /// Similar to [`file::Store::find()`], but allows to pass a snapshotted packed buffer instead.
57    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    /// Implements the logic required to transform a fully qualified refname into a filesystem path
196    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    /// Implements the logic required to transform a fully qualified refname into a filesystem path
210    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    /// Read the file contents with a verified full reference path and return it in the given vector if possible.
216    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
235///
236pub 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        /// Similar to [`file::Store::try_find()`] but a non-existing ref is treated as error.
252        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        /// Similar to [`file::Store::find()`], but supports a stable packed buffer.
262        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        /// Similar to [`file::Store::find()`] won't handle packed-refs.
275        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        /// Similar to [`file::Store::find()`] but a non-existing ref is treated as error.
285        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        /// The error returned by [file::Store::find_existing()][crate::file::Store::find()].
313        #[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    /// The error returned by [file::Store::find()].
330    #[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}