gix_ref/store/packed/
iter.rs

1use gix_object::bstr::{BString, ByteSlice};
2use winnow::{
3    combinator::{preceded, rest},
4    prelude::*,
5    stream::Stream as _,
6};
7
8use crate::store_impl::{packed, packed::decode};
9
10/// packed-refs specific functionality
11impl packed::Buffer {
12    /// Return an iterator of references stored in this packed refs buffer, ordered by reference name.
13    ///
14    /// # Note
15    ///
16    /// There is no namespace support in packed iterators. It can be emulated using `iter_prefixed(…)`.
17    pub fn iter(&self) -> Result<packed::Iter<'_>, packed::iter::Error> {
18        packed::Iter::new(self.as_ref())
19    }
20
21    /// Return an iterator yielding only references matching the given prefix, ordered by reference name.
22    pub fn iter_prefixed(&self, prefix: BString) -> Result<packed::Iter<'_>, packed::iter::Error> {
23        let first_record_with_prefix = self.binary_search_by(prefix.as_bstr()).unwrap_or_else(|(_, pos)| pos);
24        packed::Iter::new_with_prefix(&self.as_ref()[first_record_with_prefix..], Some(prefix))
25    }
26}
27
28impl<'a> Iterator for packed::Iter<'a> {
29    type Item = Result<packed::Reference<'a>, Error>;
30
31    fn next(&mut self) -> Option<Self::Item> {
32        if self.cursor.is_empty() {
33            return None;
34        }
35
36        let start = self.cursor.checkpoint();
37        match decode::reference::<()>.parse_next(&mut self.cursor) {
38            Ok(reference) => {
39                self.current_line += 1;
40                if let Some(ref prefix) = self.prefix {
41                    if !reference.name.as_bstr().starts_with_str(prefix) {
42                        self.cursor = &[];
43                        return None;
44                    }
45                }
46                Some(Ok(reference))
47            }
48            Err(_) => {
49                self.cursor.reset(&start);
50                let (failed_line, next_cursor) = self
51                    .cursor
52                    .find_byte(b'\n')
53                    .map_or((self.cursor, &[][..]), |pos| self.cursor.split_at(pos + 1));
54                self.cursor = next_cursor;
55                let line_number = self.current_line;
56                self.current_line += 1;
57
58                Some(Err(Error::Reference {
59                    invalid_line: failed_line
60                        .get(..failed_line.len().saturating_sub(1))
61                        .unwrap_or(failed_line)
62                        .into(),
63                    line_number,
64                }))
65            }
66        }
67    }
68}
69
70impl<'a> packed::Iter<'a> {
71    /// Return a new iterator after successfully parsing the possibly existing first line of the given `packed` refs buffer.
72    pub fn new(packed: &'a [u8]) -> Result<Self, Error> {
73        Self::new_with_prefix(packed, None)
74    }
75
76    /// Returns an iterators whose references will only match the given prefix.
77    ///
78    /// It assumes that the underlying `packed` buffer is indeed sorted
79    pub(in crate::store_impl::packed) fn new_with_prefix(
80        packed: &'a [u8],
81        prefix: Option<BString>,
82    ) -> Result<Self, Error> {
83        if packed.is_empty() {
84            Ok(packed::Iter {
85                cursor: packed,
86                prefix,
87                current_line: 1,
88            })
89        } else if packed[0] == b'#' {
90            let mut input = packed;
91            let refs = preceded(decode::header::<()>, rest)
92                .parse_next(&mut input)
93                .map_err(|_| Error::Header {
94                    invalid_first_line: packed.lines().next().unwrap_or(packed).into(),
95                })?;
96            Ok(packed::Iter {
97                cursor: refs,
98                prefix,
99                current_line: 2,
100            })
101        } else {
102            Ok(packed::Iter {
103                cursor: packed,
104                prefix,
105                current_line: 1,
106            })
107        }
108    }
109}
110
111mod error {
112    use gix_object::bstr::BString;
113
114    /// The error returned by [`Iter`][super::packed::Iter],
115    #[derive(Debug, thiserror::Error)]
116    #[allow(missing_docs)]
117    pub enum Error {
118        #[error("The header existed but could not be parsed: {invalid_first_line:?}")]
119        Header { invalid_first_line: BString },
120        #[error("Invalid reference in line {line_number}: {invalid_line:?}")]
121        Reference { invalid_line: BString, line_number: usize },
122    }
123}
124
125pub use error::Error;