Skip to main content

gix_ref/store/packed/
iter.rs

1use gix_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(), self.hash_kind)
14    }
15
16    /// Return an iterator yielding only references matching the given prefix, ordered by reference name.
17    pub fn iter_prefixed(&self, prefix: BString) -> Result<packed::Iter<'_>, packed::iter::Error> {
18        let first_record_with_prefix = self.binary_search_by(prefix.as_bstr()).unwrap_or_else(|(_, pos)| pos);
19        packed::Iter::new_with_prefix(&self.as_ref()[first_record_with_prefix..], self.hash_kind, Some(prefix))
20    }
21}
22
23impl<'a> Iterator for packed::Iter<'a> {
24    type Item = Result<packed::Reference<'a>, Error>;
25
26    fn next(&mut self) -> Option<Self::Item> {
27        if self.cursor.is_empty() {
28            return None;
29        }
30
31        let start = self.cursor;
32        match decode::reference(&mut self.cursor, self.hash_kind) {
33            Ok(reference) => {
34                self.current_line += 1;
35                if let Some(ref prefix) = self.prefix {
36                    if !reference.name.as_bstr().starts_with_str(prefix) {
37                        self.cursor = &[];
38                        return None;
39                    }
40                }
41                Some(Ok(reference))
42            }
43            Err(_) => {
44                self.cursor = start;
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    /// parsing object ids as `hash_kind`.
68    pub fn new(packed: &'a [u8], hash_kind: gix_hash::Kind) -> Result<Self, Error> {
69        Self::new_with_prefix(packed, hash_kind, None)
70    }
71
72    /// Returns an iterator whose references will only match `prefix`.
73    ///
74    /// It assumes that the underlying `packed` buffer is indeed sorted and parses object ids as `hash_kind`.
75    pub(in crate::store_impl::packed) fn new_with_prefix(
76        packed: &'a [u8],
77        hash_kind: gix_hash::Kind,
78        prefix: Option<BString>,
79    ) -> Result<Self, Error> {
80        if packed.is_empty() {
81            Ok(packed::Iter {
82                cursor: packed,
83                hash_kind,
84                prefix,
85                current_line: 1,
86            })
87        } else if packed[0] == b'#' {
88            let mut input = packed;
89            decode::header(&mut input).map_err(|_| Error::Header {
90                invalid_first_line: packed.lines().next().unwrap_or(packed).into(),
91            })?;
92            let refs = input;
93            Ok(packed::Iter {
94                cursor: refs,
95                hash_kind,
96                prefix,
97                current_line: 2,
98            })
99        } else {
100            Ok(packed::Iter {
101                cursor: packed,
102                hash_kind,
103                prefix,
104                current_line: 1,
105            })
106        }
107    }
108}
109
110mod error {
111    use gix_object::bstr::BString;
112
113    /// The error returned by [`Iter`][super::packed::Iter],
114    #[derive(Debug, thiserror::Error)]
115    #[allow(missing_docs)]
116    pub enum Error {
117        #[error("The header existed but could not be parsed: {invalid_first_line:?}")]
118        Header { invalid_first_line: BString },
119        #[error("Invalid reference in line {line_number}: {invalid_line:?}")]
120        Reference { invalid_line: BString, line_number: usize },
121    }
122}
123
124pub use error::Error;