Skip to main content

gix_ref/store/packed/
find.rs

1use gix_error::{ErrorExt, ExnResult, Message, ResultExt, message};
2
3use gix_object::bstr::{BStr, BString};
4
5use crate::{FullNameRef, PartialNameRef, store_impl::packed};
6
7/// packed-refs specific functionality
8impl packed::Buffer {
9    /// Find a reference with the given `name` and return it.
10    ///
11    /// Note that it will look it up verbatim and does not deal with namespaces or special prefixes like
12    /// `main-worktree/` or `worktrees/<name>/`, as this is left to the caller.
13    pub fn try_find<'a, Name, E>(&self, name: Name) -> ExnResult<Option<packed::Reference<'_>>>
14    where
15        Name: TryInto<&'a PartialNameRef, Error = E>,
16        Result<&'a PartialNameRef, E>: ResultExt<Success = &'a PartialNameRef>,
17    {
18        let name = name
19            .try_into()
20            .or_raise_erased(|| message("The ref name or path is not a valid ref name"))?;
21        let mut buf = BString::default();
22        for inbetween in &["", "tags", "heads", "remotes"] {
23            let (name, was_absolute) = if name.looks_like_full_name(false) {
24                let name = FullNameRef::new_unchecked(name.as_bstr());
25                let name = match transform_full_name_for_lookup(name) {
26                    None => return Ok(None),
27                    Some(name) => name,
28                };
29                (name, true)
30            } else {
31                let full_name = name.construct_full_name_ref(inbetween, &mut buf, false);
32                (full_name, false)
33            };
34            match self.try_find_full_name(name)? {
35                Some(r) => return Ok(Some(r)),
36                None if was_absolute => return Ok(None),
37                None => continue,
38            }
39        }
40        Ok(None)
41    }
42
43    /// Look up a resolved name. Decode failures include [metadata](gix_error::Exn::metadata()) `name` (bytes), the
44    /// requested full name.
45    pub(crate) fn try_find_full_name(&self, name: &FullNameRef) -> ExnResult<Option<packed::Reference<'_>>> {
46        match self.binary_search_by(name.as_bstr()) {
47            Ok(line_start) => {
48                let mut input = &self.as_ref()[line_start..];
49                packed::decode::reference(&mut input, self.object_hash).map(Some)
50            }
51            Err((parse_failure, _)) => {
52                if parse_failure {
53                    Err(gix_error::corruption("Malformed packed reference record").raise())
54                } else {
55                    Ok(None)
56                }
57            }
58        }
59        .or_raise_erased(|| Message::new("Could not decode packed reference").with("name", name.as_bstr()))
60    }
61
62    /// Find a reference with the given `name` and return it.
63    pub fn find<'a, Name, E>(&self, name: Name) -> ExnResult<packed::Reference<'_>>
64    where
65        Name: TryInto<&'a PartialNameRef, Error = E>,
66        Result<&'a PartialNameRef, E>: ResultExt<Success = &'a PartialNameRef>,
67    {
68        let name = name
69            .try_into()
70            .or_raise_erased(|| message("The ref name or path is not a valid ref name"))?;
71        self.try_find::<_, std::convert::Infallible>(name)?.ok_or_else(|| {
72            crate::file::find::NotFound {
73                name: name.to_partial_path().to_owned(),
74            }
75            .raise_erased()
76        })
77    }
78
79    /// Perform a binary search where `Ok(pos)` is the beginning of the line that matches `name` perfectly and `Err(pos)`
80    /// is the beginning of the line at which `name` could be inserted to still be in sort order.
81    pub(in crate::store_impl::packed) fn binary_search_by(&self, full_name: &BStr) -> Result<usize, (bool, usize)> {
82        let a = self.as_ref();
83        let mut encountered_parse_failure = false;
84        a.binary_search_by_key(&full_name.as_ref(), |b: &u8| {
85            let ofs = std::ptr::from_ref::<u8>(b) as usize - a.as_ptr() as usize;
86            let line = packed::decode::record_at_offset(a, ofs);
87            // The binary search only needs the name bytes for ordered
88            // comparison; skip ref-name and hex-hash validation here and let
89            // the final match site re-parse the record via `decode::reference`
90            // (which validates fully). This saves the `logâ‚‚(n)` per-query.
91            match packed::decode::name_at_record_start(line, self.object_hash) {
92                Some(name) => name,
93                None => {
94                    encountered_parse_failure = true;
95                    &[]
96                }
97            }
98        })
99        .map(|pos| packed::decode::record_start_at_offset(a, pos))
100        .map_err(|pos| {
101            (
102                encountered_parse_failure,
103                packed::decode::record_start_at_offset(a, pos),
104            )
105        })
106    }
107}
108
109pub(crate) fn transform_full_name_for_lookup(name: &FullNameRef) -> Option<&FullNameRef> {
110    match name.category_and_short_name() {
111        Some((c, sn)) => {
112            use crate::Category::*;
113            Some(match c {
114                MainRef | LinkedRef { .. } => FullNameRef::new_unchecked(sn),
115                Tag | RemoteBranch | LocalBranch | Bisect | Rewritten | Note => name,
116                MainPseudoRef | PseudoRef | LinkedPseudoRef { .. } | WorktreePrivate => return None,
117            })
118        }
119        None => Some(name),
120    }
121}