use std::convert::TryInto; use git_object::bstr::{BStr, BString, ByteSlice}; use crate::{store_impl::packed, FullNameRef, PartialNameRef}; /// packed-refs specific functionality impl packed::Buffer { /// Find a reference with the given `name` and return it. /// /// Note that it will look it up verbatim and does not deal with namespaces or special prefixes like /// `main-worktree/` or `worktrees//`, as this is left to the caller. pub fn try_find<'a, Name, E>(&self, name: Name) -> Result>, Error> where Name: TryInto<&'a PartialNameRef, Error = E>, Error: From, { let name = name.try_into()?; let mut buf = BString::default(); for inbetween in &["", "tags", "heads", "remotes"] { let (name, was_absolute) = if name.looks_like_full_name() { let name = FullNameRef::new_unchecked(name.as_bstr()); let name = match transform_full_name_for_lookup(name) { None => return Ok(None), Some(name) => name, }; (name, true) } else { let full_name = name.construct_full_name_ref(true, inbetween, &mut buf); (full_name, false) }; match self.try_find_full_name(name)? { Some(r) => return Ok(Some(r)), None if was_absolute => return Ok(None), None => continue, } } Ok(None) } pub(crate) fn try_find_full_name(&self, name: &FullNameRef) -> Result>, Error> { match self.binary_search_by(name.as_bstr()) { Ok(line_start) => Ok(Some( packed::decode::reference::<()>(&self.as_ref()[line_start..]) .map_err(|_| Error::Parse)? .1, )), Err((parse_failure, _)) => { if parse_failure { Err(Error::Parse) } else { Ok(None) } } } } /// Find a reference with the given `name` and return it. pub fn find<'a, Name, E>(&self, name: Name) -> Result, existing::Error> where Name: TryInto<&'a PartialNameRef, Error = E>, Error: From, { match self.try_find(name) { Ok(Some(r)) => Ok(r), Ok(None) => Err(existing::Error::NotFound), Err(err) => Err(existing::Error::Find(err)), } } /// Perform a binary search where `Ok(pos)` is the beginning of the line that matches `name` perfectly and `Err(pos)` /// is the beginning of the line at which `name` could be inserted to still be in sort order. pub(in crate::store_impl::packed) fn binary_search_by(&self, full_name: &BStr) -> Result { let a = self.as_ref(); let search_start_of_record = |ofs: usize| { a[..ofs] .rfind(b"\n") .and_then(|pos| { let candidate = pos + 1; a.get(candidate).and_then(|b| { if *b == b'^' { a[..pos].rfind(b"\n").map(|pos| pos + 1) } else { Some(candidate) } }) }) .unwrap_or(0) }; let mut encountered_parse_failure = false; a.binary_search_by_key(&full_name.as_ref(), |b: &u8| { let ofs = b as *const u8 as usize - a.as_ptr() as usize; let line = &a[search_start_of_record(ofs)..]; packed::decode::reference::<()>(line) .map(|(_rest, r)| r.name.as_bstr().as_ref()) .map_err(|err| { encountered_parse_failure = true; err }) .unwrap_or(&[]) }) .map(search_start_of_record) .map_err(|pos| (encountered_parse_failure, search_start_of_record(pos))) } } mod error { use std::convert::Infallible; /// The error returned by [`find()`][super::packed::Buffer::find()] #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("The ref name or path is not a valid ref name")] RefnameValidation(#[from] crate::name::Error), #[error("The reference could not be parsed")] Parse, } impl From for Error { fn from(_: Infallible) -> Self { unreachable!("this impl is needed to allow passing a known valid partial path as parameter") } } } pub use error::Error; /// pub mod existing { /// The error returned by [`find_existing()`][super::packed::Buffer::find()] #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("The find operation failed")] Find(#[from] super::Error), #[error("The reference did not exist even though that was expected")] NotFound, } } pub(crate) fn transform_full_name_for_lookup(name: &FullNameRef) -> Option<&FullNameRef> { match name.category_and_short_name() { Some((c, sn)) => { use crate::Category::*; Some(match c { MainRef | LinkedRef { .. } => FullNameRef::new_unchecked(sn), Tag | RemoteBranch | LocalBranch | Bisect | Rewritten | Note => name, MainPseudoRef | PseudoRef | LinkedPseudoRef { .. } | WorktreePrivate => return None, }) } None => Some(name), } }