gix_ref/store/packed/
iter.rs

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