| @@ -0,0 +1,240 @@ |
| | 1 | use std::borrow::Cow; |
| | 2 | |
| | 3 | use bstr::BStr; |
| | 4 | use nom::{ |
| | 5 | branch::alt, |
| | 6 | bytes::complete::is_not, |
| | 7 | combinator::{all_consuming, opt}, |
| | 8 | error::context, |
| | 9 | }; |
| | 10 | |
| | 11 | use git_hash::{oid, ObjectId}; |
| | 12 | |
| | 13 | use crate::commit::decode; |
| | 14 | use crate::{ |
| | 15 | bstr::ByteSlice, |
| | 16 | immutable::{object, parse, parse::NL}, |
| | 17 | }; |
| | 18 | |
| | 19 | #[derive(Copy, Clone)] |
| | 20 | enum SignatureKind { |
| | 21 | Author, |
| | 22 | Committer, |
| | 23 | } |
| | 24 | |
| | 25 | enum State { |
| | 26 | Tree, |
| | 27 | Parents, |
| | 28 | Signature { of: SignatureKind }, |
| | 29 | Encoding, |
| | 30 | ExtraHeaders, |
| | 31 | Message, |
| | 32 | } |
| | 33 | |
| | 34 | impl Default for State { |
| | 35 | fn default() -> Self { |
| | 36 | State::Tree |
| | 37 | } |
| | 38 | } |
| | 39 | |
| | 40 | /// Like [`CommitRef`][crate::CommitRef], but as `Iterator` to support (up to) entirely allocation free parsing. |
| | 41 | /// It's particularly useful to traverse the commit graph without ever allocating arrays for parents. |
| | 42 | pub struct CommitRefIter<'a> { |
| | 43 | data: &'a [u8], |
| | 44 | state: State, |
| | 45 | } |
| | 46 | |
| | 47 | impl<'a> CommitRefIter<'a> { |
| | 48 | /// Create a commit iterator from data. |
| | 49 | pub fn from_bytes(data: &'a [u8]) -> CommitRefIter<'a> { |
| | 50 | CommitRefIter { |
| | 51 | data, |
| | 52 | state: State::default(), |
| | 53 | } |
| | 54 | } |
| | 55 | |
| | 56 | /// Returns the object id of this commits tree if it is the first function called and if there is no error in decoding |
| | 57 | /// the data. |
| | 58 | /// |
| | 59 | /// Note that this method must only be called once or else will always return None while consuming a single token. |
| | 60 | /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they |
| | 61 | /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again. |
| | 62 | /// `next()`. |
| | 63 | pub fn tree_id(&mut self) -> Option<ObjectId> { |
| | 64 | self.next().and_then(Result::ok).and_then(Token::into_id) |
| | 65 | } |
| | 66 | |
| | 67 | /// Returns all signatures, first the author, then the committer, if there is no decoding error. |
| | 68 | /// |
| | 69 | /// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not |
| | 70 | /// if not exactly two signatures were iterable. |
| | 71 | /// Errors are not the common case - if an error needs to be detectable, use this instance as iterator. |
| | 72 | pub fn signatures(&'a mut self) -> impl Iterator<Item = git_actor::SignatureRef<'_>> + 'a { |
| | 73 | self.filter_map(Result::ok) |
| | 74 | .skip_while(|t| !matches!(t, Token::Author { .. } | Token::Committer { .. })) |
| | 75 | .filter_map(|t| match t { |
| | 76 | Token::Author { signature } | Token::Committer { signature } => Some(signature), |
| | 77 | _ => None, |
| | 78 | }) |
| | 79 | } |
| | 80 | } |
| | 81 | |
| | 82 | impl<'a> CommitRefIter<'a> { |
| | 83 | fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), object::decode::Error> { |
| | 84 | use State::*; |
| | 85 | Ok(match state { |
| | 86 | Tree => { |
| | 87 | let (i, tree) = context("tree <40 lowercase hex char>", |i| { |
| | 88 | parse::header_field(i, b"tree", parse::hex_hash) |
| | 89 | })(i)?; |
| | 90 | *state = State::Parents; |
| | 91 | ( |
| | 92 | i, |
| | 93 | Token::Tree { |
| | 94 | id: ObjectId::from_hex(tree).expect("parsing validation"), |
| | 95 | }, |
| | 96 | ) |
| | 97 | } |
| | 98 | Parents => { |
| | 99 | let (i, parent) = context( |
| | 100 | "commit <40 lowercase hex char>", |
| | 101 | opt(|i| parse::header_field(i, b"parent", parse::hex_hash)), |
| | 102 | )(i)?; |
| | 103 | match parent { |
| | 104 | Some(parent) => ( |
| | 105 | i, |
| | 106 | Token::Parent { |
| | 107 | id: ObjectId::from_hex(parent).expect("parsing validation"), |
| | 108 | }, |
| | 109 | ), |
| | 110 | None => { |
| | 111 | *state = State::Signature { |
| | 112 | of: SignatureKind::Author, |
| | 113 | }; |
| | 114 | return Self::next_inner(i, state); |
| | 115 | } |
| | 116 | } |
| | 117 | } |
| | 118 | Signature { ref mut of } => { |
| | 119 | let who = *of; |
| | 120 | let (field_name, err_msg) = match of { |
| | 121 | SignatureKind::Author => { |
| | 122 | *of = SignatureKind::Committer; |
| | 123 | (&b"author"[..], "author <signature>") |
| | 124 | } |
| | 125 | SignatureKind::Committer => { |
| | 126 | *state = State::Encoding; |
| | 127 | (&b"committer"[..], "committer <signature>") |
| | 128 | } |
| | 129 | }; |
| | 130 | let (i, signature) = context(err_msg, |i| parse::header_field(i, field_name, parse::signature))(i)?; |
| | 131 | ( |
| | 132 | i, |
| | 133 | match who { |
| | 134 | SignatureKind::Author => Token::Author { signature }, |
| | 135 | SignatureKind::Committer => Token::Committer { signature }, |
| | 136 | }, |
| | 137 | ) |
| | 138 | } |
| | 139 | Encoding => { |
| | 140 | let (i, encoding) = context( |
| | 141 | "encoding <encoding>", |
| | 142 | opt(|i| parse::header_field(i, b"encoding", is_not(NL))), |
| | 143 | )(i)?; |
| | 144 | *state = State::ExtraHeaders; |
| | 145 | match encoding { |
| | 146 | Some(encoding) => (i, Token::Encoding(encoding.as_bstr())), |
| | 147 | None => return Self::next_inner(i, state), |
| | 148 | } |
| | 149 | } |
| | 150 | ExtraHeaders => { |
| | 151 | let (i, extra_header) = context( |
| | 152 | "<field> <single-line|multi-line>", |
| | 153 | opt(alt(( |
| | 154 | |i| parse::any_header_field_multi_line(i).map(|(i, (k, o))| (i, (k.as_bstr(), Cow::Owned(o)))), |
| | 155 | |i| { |
| | 156 | parse::any_header_field(i, is_not(NL)) |
| | 157 | .map(|(i, (k, o))| (i, (k.as_bstr(), Cow::Borrowed(o.as_bstr())))) |
| | 158 | }, |
| | 159 | ))), |
| | 160 | )(i)?; |
| | 161 | match extra_header { |
| | 162 | Some(extra_header) => (i, Token::ExtraHeader(extra_header)), |
| | 163 | None => { |
| | 164 | *state = State::Message; |
| | 165 | return Self::next_inner(i, state); |
| | 166 | } |
| | 167 | } |
| | 168 | } |
| | 169 | Message => { |
| | 170 | let (i, message) = all_consuming(decode::message)(i)?; |
| | 171 | debug_assert!( |
| | 172 | i.is_empty(), |
| | 173 | "we should have consumed all data - otherwise iter may go forever" |
| | 174 | ); |
| | 175 | return Ok((i, Token::Message(message))); |
| | 176 | } |
| | 177 | }) |
| | 178 | } |
| | 179 | } |
| | 180 | |
| | 181 | impl<'a> Iterator for CommitRefIter<'a> { |
| | 182 | type Item = Result<Token<'a>, object::decode::Error>; |
| | 183 | |
| | 184 | fn next(&mut self) -> Option<Self::Item> { |
| | 185 | if self.data.is_empty() { |
| | 186 | return None; |
| | 187 | } |
| | 188 | match Self::next_inner(self.data, &mut self.state) { |
| | 189 | Ok((data, token)) => { |
| | 190 | self.data = data; |
| | 191 | Some(Ok(token)) |
| | 192 | } |
| | 193 | Err(err) => { |
| | 194 | self.data = &[]; |
| | 195 | Some(Err(err)) |
| | 196 | } |
| | 197 | } |
| | 198 | } |
| | 199 | } |
| | 200 | |
| | 201 | /// A token returned by the [commit iterator][RefIter]. |
| | 202 | #[allow(missing_docs)] |
| | 203 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] |
| | 204 | pub enum Token<'a> { |
| | 205 | Tree { |
| | 206 | id: ObjectId, |
| | 207 | }, |
| | 208 | Parent { |
| | 209 | id: ObjectId, |
| | 210 | }, |
| | 211 | /// A person who authored the content of the commit. |
| | 212 | Author { |
| | 213 | signature: git_actor::SignatureRef<'a>, |
| | 214 | }, |
| | 215 | /// A person who committed the authors work to the repository. |
| | 216 | Committer { |
| | 217 | signature: git_actor::SignatureRef<'a>, |
| | 218 | }, |
| | 219 | Encoding(&'a BStr), |
| | 220 | ExtraHeader((&'a BStr, Cow<'a, BStr>)), |
| | 221 | Message(&'a BStr), |
| | 222 | } |
| | 223 | |
| | 224 | impl<'a> Token<'a> { |
| | 225 | /// Return the object id of this token if its a [tree][Token::Tree] or a [parent commit][Token::Parent]. |
| | 226 | pub fn id(&self) -> Option<&oid> { |
| | 227 | match self { |
| | 228 | Token::Tree { id } | Token::Parent { id } => Some(id.as_ref()), |
| | 229 | _ => None, |
| | 230 | } |
| | 231 | } |
| | 232 | |
| | 233 | /// Return the owned object id of this token if its a [tree][Token::Tree] or a [parent commit][Token::Parent]. |
| | 234 | pub fn into_id(self) -> Option<ObjectId> { |
| | 235 | match self { |
| | 236 | Token::Tree { id } | Token::Parent { id } => Some(id), |
| | 237 | _ => None, |
| | 238 | } |
| | 239 | } |
| | 240 | } |