| use gix_object::bstr::{BString, ByteSlice}; |
| use winnow::{ |
| combinator::{preceded, rest}, |
| prelude::*, |
| stream::Stream as _, |
| }; |
| |
| use crate::store_impl::{packed, packed::decode}; |
| |
| /// packed-refs specific functionality |
| impl packed::Buffer { |
| /// Return an iterator of references stored in this packed refs buffer, ordered by reference name. |
| /// |
| /// # Note |
| /// |
| /// There is no namespace support in packed iterators. It can be emulated using `iter_prefixed(…)`. |
| pub fn iter(&self) -> Result<packed::Iter<'_>, packed::iter::Error> { |
| packed::Iter::new(self.as_ref()) |
| } |
| |
| /// Return an iterator yielding only references matching the given prefix, ordered by reference name. |
| pub fn iter_prefixed(&self, prefix: BString) -> Result<packed::Iter<'_>, packed::iter::Error> { |
| let first_record_with_prefix = self.binary_search_by(prefix.as_bstr()).unwrap_or_else(|(_, pos)| pos); |
| packed::Iter::new_with_prefix(&self.as_ref()[first_record_with_prefix..], Some(prefix)) |
| } |
| } |
| |
| impl<'a> Iterator for packed::Iter<'a> { |
| type Item = Result<packed::Reference<'a>, Error>; |
| |
| fn next(&mut self) -> Option<Self::Item> { |
| if self.cursor.is_empty() { |
| return None; |
| } |
| |
| let start = self.cursor.checkpoint(); |
| match decode::reference::<()>.parse_next(&mut self.cursor) { |
| Ok(reference) => { |
| self.current_line += 1; |
| if let Some(ref prefix) = self.prefix { |
| if !reference.name.as_bstr().starts_with_str(prefix) { |
| self.cursor = &[]; |
| return None; |
| } |
| } |
| Some(Ok(reference)) |
| } |
| Err(_) => { |
| self.cursor.reset(&start); |
| let (failed_line, next_cursor) = self |
| .cursor |
| .find_byte(b'\n') |
| .map_or((self.cursor, &[][..]), |pos| self.cursor.split_at(pos + 1)); |
| self.cursor = next_cursor; |
| let line_number = self.current_line; |
| self.current_line += 1; |
| |
| Some(Err(Error::Reference { |
| invalid_line: failed_line |
| .get(..failed_line.len().saturating_sub(1)) |
| .unwrap_or(failed_line) |
| .into(), |
| line_number, |
| })) |
| } |
| } |
| } |
| } |
| |
| impl<'a> packed::Iter<'a> { |
| /// Return a new iterator after successfully parsing the possibly existing first line of the given `packed` refs buffer. |
| pub fn new(packed: &'a [u8]) -> Result<Self, Error> { |
| Self::new_with_prefix(packed, None) |
| } |
| |
| /// Returns an iterators whose references will only match the given prefix. |
| /// |
| /// It assumes that the underlying `packed` buffer is indeed sorted |
| pub(in crate::store_impl::packed) fn new_with_prefix( |
| packed: &'a [u8], |
| prefix: Option<BString>, |
| ) -> Result<Self, Error> { |
| if packed.is_empty() { |
| Ok(packed::Iter { |
| cursor: packed, |
| prefix, |
| current_line: 1, |
| }) |
| } else if packed[0] == b'#' { |
| let mut input = packed; |
| let refs = preceded(decode::header::<()>, rest) |
| .parse_next(&mut input) |
| .map_err(|_| Error::Header { |
| invalid_first_line: packed.lines().next().unwrap_or(packed).into(), |
| })?; |
| Ok(packed::Iter { |
| cursor: refs, |
| prefix, |
| current_line: 2, |
| }) |
| } else { |
| Ok(packed::Iter { |
| cursor: packed, |
| prefix, |
| current_line: 1, |
| }) |
| } |
| } |
| } |
| |
| mod error { |
| use gix_object::bstr::BString; |
| |
| /// The error returned by [`Iter`][super::packed::Iter], |
| #[derive(Debug, thiserror::Error)] |
| #[allow(missing_docs)] |
| pub enum Error { |
| #[error("The header existed but could not be parsed: {invalid_first_line:?}")] |
| Header { invalid_first_line: BString }, |
| #[error("Invalid reference in line {line_number}: {invalid_line:?}")] |
| Reference { invalid_line: BString, line_number: usize }, |
| } |
| } |
| |
| pub use error::Error; |