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