use std::borrow::Cow; use smallvec::SmallVec; use crate::{BStr, ByteSlice, CommitRef, parse, parse::ParseResult}; /// Parse the commit message after the header/message separator. /// /// Typical input starts with the blank-line separator before the message, for /// example `\nsubject\n\nbody\n`. The returned message excludes that first /// separator newline and borrows all remaining bytes from `i`. /// /// On success, `i` is advanced to the empty suffix as commits end with a message. pub fn message<'a>(i: &mut &'a [u8]) -> ParseResult<&'a BStr> { if let Some(rest) = i.strip_prefix(parse::NL) { *i = &[]; Ok(rest.as_bstr()) } else { Err(crate::decode::Error) } } /// Parse a complete commit object body. /// /// Typical input starts with `tree \n`, followed by zero or more /// `parent \n` headers, then `author \n` and /// `committer \n`. An optional `encoding \n` header and any /// number of extra single-line or multi-line headers may follow. The headers /// are terminated by a blank line, after which all remaining bytes are the /// commit message. /// /// On success, the returned [`CommitRef`] borrows fields from `i` where /// possible, and `i` is advanced to the empty suffix. Extra single-line header /// values are borrowed, while multi-line header values are unfolded into owned /// buffers. /// /// This parser is not transactional as a whole: if a later required field or /// the final message parse fails, `i` may already have been advanced past /// earlier successfully parsed fields. pub fn commit<'a>(i: &mut &'a [u8], object_hash: gix_hash::Kind) -> ParseResult> { let tree = parse::header_field(i, b"tree", |value| parse::hex_hash(value, object_hash))?; let mut parents = SmallVec::new(); loop { let before = *i; match parse::header_field(i, b"parent", |value| parse::hex_hash(value, object_hash)) { Ok(parent) => parents.push(parent), Err(_) => { *i = before; break; } } } let author = parse::header_field(i, b"author", parse::signature_raw)?; let committer = parse::header_field(i, b"committer", parse::signature_raw)?; let before = *i; let encoding = match parse::header_field(i, b"encoding", Ok) { Ok(encoding) => Some(encoding.as_bstr()), Err(_) => { *i = before; None } }; let mut extra_headers = Vec::new(); loop { let before = *i; match parse::any_header_field_multi_line(i) .map(|(k, v)| (k.as_bstr(), Cow::Owned(v))) .or_else(|_| { *i = before; parse::any_header_field(i).map(|(k, v)| (k.as_bstr(), Cow::Borrowed(v.as_bstr()))) }) { Ok(header) => extra_headers.push(header), Err(_) => { *i = before; break; } } } let message = message(i)?; if !i.is_empty() { return Err(crate::decode::Error); } Ok(CommitRef { tree, parents, author, committer, encoding, message, extra_headers, }) }