1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use bstr::{BString, ByteSlice};

use crate::store::{packed, packed::decode};

/// packed-refs specific functionality
impl packed::Buffer {
    /// Return an iterator of references stored in this packed refs buffer, ordered by reference name.
    pub fn iter(&self) -> Result<packed::Iter<'_>, packed::iter::Error> {
        packed::Iter::new(self.as_ref())
    }

    /// Return an iterator yielding only references matching the given prefix, ordered by reference name.
    pub fn iter_prefixed(&self, prefix: impl Into<BString>) -> Result<packed::Iter<'_>, packed::iter::Error> {
        let prefix = prefix.into();
        let first_record_with_prefix = self.binary_search_by(prefix.as_bstr()).unwrap_or_else(|(_, pos)| pos);
        packed::Iter::new_with_prefix(&self.as_ref()[first_record_with_prefix..], Some(prefix))
    }
}

impl<'a> Iterator for packed::Iter<'a> {
    type Item = Result<packed::Reference<'a>, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cursor.is_empty() {
            return None;
        }

        match decode::reference::<()>(self.cursor) {
            Ok((rest, reference)) => {
                self.cursor = rest;
                self.current_line += 1;
                if let Some(ref prefix) = self.prefix {
                    if !reference.name.as_bstr().starts_with_str(prefix) {
                        self.cursor = &[];
                        return None;
                    }
                }
                Some(Ok(reference))
            }
            Err(_) => {
                let (failed_line, next_cursor) = self
                    .cursor
                    .find_byte(b'\n')
                    .map_or((self.cursor, &[][..]), |pos| self.cursor.split_at(pos + 1));
                self.cursor = next_cursor;
                let line_number = self.current_line;
                self.current_line += 1;

                Some(Err(Error::Reference {
                    invalid_line: failed_line
                        .get(..failed_line.len().saturating_sub(1))
                        .unwrap_or(failed_line)
                        .into(),
                    line_number,
                }))
            }
        }
    }
}

impl<'a> packed::Iter<'a> {
    /// Return a new iterator after successfully parsing the possibly existing first line of the given `packed` refs buffer.
    pub fn new(packed: &'a [u8]) -> Result<Self, Error> {
        Self::new_with_prefix(packed, None)
    }

    /// Returns an iterators whose references will only match the given prefix.
    ///
    /// It assumes that the underlying `packed` buffer is indeed sorted
    pub(in crate::store::packed) fn new_with_prefix(packed: &'a [u8], prefix: Option<BString>) -> Result<Self, Error> {
        if packed.is_empty() {
            Ok(packed::Iter {
                cursor: packed,
                prefix,
                current_line: 1,
            })
        } else if packed[0] == b'#' {
            let (refs, _header) = decode::header::<()>(packed).map_err(|_| Error::Header {
                invalid_first_line: packed.lines().next().unwrap_or(packed).into(),
            })?;
            Ok(packed::Iter {
                cursor: refs,
                prefix,
                current_line: 2,
            })
        } else {
            Ok(packed::Iter {
                cursor: packed,
                prefix,
                current_line: 1,
            })
        }
    }
}

mod error {
    use bstr::BString;
    use quick_error::quick_error;

    quick_error! {
        /// The error returned by [`Iter::new(…)`][super::Iter::new()],
        #[derive(Debug)]
        #[allow(missing_docs)]
        pub enum Error {
            Header { invalid_first_line: BString } {
                display("The header existed but could not be parsed: '{}'", invalid_first_line)
            }
            Reference { invalid_line: BString, line_number: usize } {
                display("Invalid reference in line {}: '{}'", line_number, invalid_line)
            }
        }
    }
}

pub use error::Error;