git_object/tree/
ref_iter.rs

1use std::convert::TryFrom;
2
3use nom::error::ParseError;
4
5use crate::{tree, tree::EntryRef, TreeRef, TreeRefIter};
6
7impl<'a> TreeRefIter<'a> {
8    /// Instantiate an iterator from the given tree data.
9    pub fn from_bytes(data: &'a [u8]) -> TreeRefIter<'a> {
10        TreeRefIter { data }
11    }
12}
13
14impl<'a> TreeRef<'a> {
15    /// Deserialize a Tree from `data`.
16    pub fn from_bytes(data: &'a [u8]) -> Result<TreeRef<'a>, crate::decode::Error> {
17        decode::tree(data).map(|(_, t)| t).map_err(crate::decode::Error::from)
18    }
19
20    /// Create an instance of the empty tree.
21    ///
22    /// It's particularly useful as static part of a program.
23    pub const fn empty() -> TreeRef<'static> {
24        TreeRef { entries: Vec::new() }
25    }
26}
27
28impl<'a> TreeRefIter<'a> {
29    /// Consume self and return all parsed entries.
30    pub fn entries(self) -> Result<Vec<EntryRef<'a>>, crate::decode::Error> {
31        self.collect()
32    }
33}
34
35impl<'a> Iterator for TreeRefIter<'a> {
36    type Item = Result<EntryRef<'a>, crate::decode::Error>;
37
38    fn next(&mut self) -> Option<Self::Item> {
39        if self.data.is_empty() {
40            return None;
41        }
42        match decode::fast_entry(self.data) {
43            Some((data_left, entry)) => {
44                self.data = data_left;
45                Some(Ok(entry))
46            }
47            None => {
48                self.data = &[];
49                #[allow(clippy::unit_arg)]
50                Some(Err(nom::Err::Error(crate::decode::ParseError::from_error_kind(
51                    &[] as &[u8],
52                    nom::error::ErrorKind::MapRes,
53                ))
54                .into()))
55            }
56        }
57    }
58}
59
60impl<'a> TryFrom<&'a [u8]> for tree::EntryMode {
61    type Error = &'a [u8];
62
63    fn try_from(mode: &'a [u8]) -> Result<Self, Self::Error> {
64        Ok(match mode {
65            b"40000" => tree::EntryMode::Tree,
66            b"100644" => tree::EntryMode::Blob,
67            b"100755" => tree::EntryMode::BlobExecutable,
68            b"120000" => tree::EntryMode::Link,
69            b"160000" => tree::EntryMode::Commit,
70            b"100664" => tree::EntryMode::Blob, // rare and found in the linux kernel
71            b"100640" => tree::EntryMode::Blob, // rare and found in the Rust repo
72            _ => return Err(mode),
73        })
74    }
75}
76
77impl TryFrom<u32> for tree::EntryMode {
78    type Error = u32;
79
80    fn try_from(mode: u32) -> Result<Self, Self::Error> {
81        Ok(match mode {
82            0o40000 => tree::EntryMode::Tree,
83            0o100644 => tree::EntryMode::Blob,
84            0o100755 => tree::EntryMode::BlobExecutable,
85            0o120000 => tree::EntryMode::Link,
86            0o160000 => tree::EntryMode::Commit,
87            0o100664 => tree::EntryMode::Blob, // rare and found in the linux kernel
88            0o100640 => tree::EntryMode::Blob, // rare and found in the Rust repo
89            _ => return Err(mode),
90        })
91    }
92}
93
94mod decode {
95    use std::convert::TryFrom;
96
97    use bstr::ByteSlice;
98    use nom::{
99        bytes::complete::{tag, take, take_while1, take_while_m_n},
100        character::is_digit,
101        combinator::all_consuming,
102        error::ParseError,
103        multi::many0,
104        sequence::terminated,
105        IResult,
106    };
107
108    use crate::{parse::SPACE, tree, tree::EntryRef, TreeRef};
109
110    const NULL: &[u8] = b"\0";
111
112    pub fn fast_entry(i: &[u8]) -> Option<(&[u8], EntryRef<'_>)> {
113        let mut mode = 0u32;
114        let mut spacer_pos = 1;
115        for b in i.iter().take_while(|b| **b != b' ') {
116            if *b < b'0' || *b > b'7' {
117                return None;
118            }
119            mode = (mode << 3) + (b - b'0') as u32;
120            spacer_pos += 1;
121        }
122        let (_, i) = i.split_at(spacer_pos);
123        let mode = tree::EntryMode::try_from(mode).ok()?;
124        let (filename, i) = i.split_at(i.find_byte(0)?);
125        let i = &i[1..];
126        const HASH_LEN_FIXME: usize = 20; // TODO: know actual /desired length or we may overshoot
127        let (oid, i) = match i.len() {
128            len if len < HASH_LEN_FIXME => return None,
129            _ => i.split_at(20),
130        };
131        Some((
132            i,
133            EntryRef {
134                mode,
135                filename: filename.as_bstr(),
136                oid: git_hash::oid::try_from_bytes(oid).expect("we counted exactly 20 bytes"),
137            },
138        ))
139    }
140
141    pub fn entry<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], EntryRef<'_>, E> {
142        let (i, mode) = terminated(take_while_m_n(5, 6, is_digit), tag(SPACE))(i)?;
143        let mode = tree::EntryMode::try_from(mode)
144            .map_err(|invalid| nom::Err::Error(E::from_error_kind(invalid, nom::error::ErrorKind::MapRes)))?;
145        let (i, filename) = terminated(take_while1(|b| b != NULL[0]), tag(NULL))(i)?;
146        let (i, oid) = take(20u8)(i)?; // TODO: make this compatible with other hash lengths
147
148        Ok((
149            i,
150            EntryRef {
151                mode,
152                filename: filename.as_bstr(),
153                oid: git_hash::oid::try_from_bytes(oid).expect("we counted exactly 20 bytes"),
154            },
155        ))
156    }
157
158    pub fn tree<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], TreeRef<'a>, E> {
159        let (i, entries) = all_consuming(many0(entry))(i)?;
160        Ok((i, TreeRef { entries }))
161    }
162}