git_ref/store/packed/
iter.rs

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