use std::{borrow::Cow, ops::Range};
use bstr::BStr;
use gix_hash::{oid, ObjectId};
use winnow::{
combinator::{alt, eof, opt, terminated},
error::StrContext,
prelude::*,
token::take_till,
};
use crate::{
bstr::ByteSlice,
commit::{decode, SignedData},
parse,
parse::NL,
CommitRefIter,
};
#[derive(Copy, Clone)]
pub(crate) enum SignatureKind {
Author,
Committer,
}
#[derive(Default, Copy, Clone)]
pub(crate) enum State {
#[default]
Tree,
Parents,
Signature {
of: SignatureKind,
},
Encoding,
ExtraHeaders,
Message,
}
impl<'a> CommitRefIter<'a> {
pub fn from_bytes(data: &'a [u8]) -> CommitRefIter<'a> {
CommitRefIter {
data,
state: State::default(),
}
}
}
impl<'a> CommitRefIter<'a> {
pub fn signature(data: &'a [u8]) -> Result<Option<(Cow<'a, BStr>, SignedData<'a>)>, crate::decode::Error> {
let mut signature_and_range = None;
let raw_tokens = CommitRefIterRaw {
data,
state: State::default(),
offset: 0,
};
for token in raw_tokens {
let token = token?;
if let Token::ExtraHeader((name, value)) = &token.token {
if *name == "gpgsig" {
signature_and_range = Some((value.clone(), token.token_range));
break;
}
}
}
Ok(signature_and_range.map(|(sig, signature_range)| (sig, SignedData { data, signature_range })))
}
pub fn tree_id(&mut self) -> Result<ObjectId, crate::decode::Error> {
let tree_id = self.next().ok_or_else(missing_field)??;
Token::try_into_id(tree_id).ok_or_else(missing_field)
}
pub fn parent_ids(self) -> impl Iterator<Item = gix_hash::ObjectId> + 'a {
self.filter_map(|t| match t {
Ok(Token::Parent { id }) => Some(id),
_ => None,
})
}
pub fn signatures(self) -> impl Iterator<Item = gix_actor::SignatureRef<'a>> + 'a {
self.filter_map(|t| match t {
Ok(Token::Author { signature } | Token::Committer { signature }) => Some(signature),
_ => None,
})
}
pub fn committer(mut self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
self.find_map(|t| match t {
Ok(Token::Committer { signature }) => Some(Ok(signature)),
Err(err) => Some(Err(err)),
_ => None,
})
.ok_or_else(missing_field)?
}
pub fn author(mut self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
self.find_map(|t| match t {
Ok(Token::Author { signature }) => Some(Ok(signature)),
Err(err) => Some(Err(err)),
_ => None,
})
.ok_or_else(missing_field)?
}
pub fn message(mut self) -> Result<&'a BStr, crate::decode::Error> {
self.find_map(|t| match t {
Ok(Token::Message(msg)) => Some(Ok(msg)),
Err(err) => Some(Err(err)),
_ => None,
})
.transpose()
.map(Option::unwrap_or_default)
}
}
fn missing_field() -> crate::decode::Error {
crate::decode::empty_error()
}
impl<'a> CommitRefIter<'a> {
#[inline]
fn next_inner(mut i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), crate::decode::Error> {
let input = &mut i;
match Self::next_inner_(input, state) {
Ok(token) => Ok((*input, token)),
Err(err) => Err(crate::decode::Error::with_err(err, input)),
}
}
fn next_inner_(
input: &mut &'a [u8],
state: &mut State,
) -> Result<Token<'a>, winnow::error::ErrMode<crate::decode::ParseError>> {
use State::*;
Ok(match state {
Tree => {
let tree = (|i: &mut _| parse::header_field(i, b"tree", parse::hex_hash))
.context(StrContext::Expected("tree <40 lowercase hex char>".into()))
.parse_next(input)?;
*state = State::Parents;
Token::Tree {
id: ObjectId::from_hex(tree).expect("parsing validation"),
}
}
Parents => {
let parent = opt(|i: &mut _| parse::header_field(i, b"parent", parse::hex_hash))
.context(StrContext::Expected("commit <40 lowercase hex char>".into()))
.parse_next(input)?;
match parent {
Some(parent) => Token::Parent {
id: ObjectId::from_hex(parent).expect("parsing validation"),
},
None => {
*state = State::Signature {
of: SignatureKind::Author,
};
Self::next_inner_(input, state)?
}
}
}
Signature { ref mut of } => {
let who = *of;
let (field_name, err_msg) = match of {
SignatureKind::Author => {
*of = SignatureKind::Committer;
(&b"author"[..], "author <signature>")
}
SignatureKind::Committer => {
*state = State::Encoding;
(&b"committer"[..], "committer <signature>")
}
};
let signature = (|i: &mut _| parse::header_field(i, field_name, parse::signature))
.context(StrContext::Expected(err_msg.into()))
.parse_next(input)?;
match who {
SignatureKind::Author => Token::Author { signature },
SignatureKind::Committer => Token::Committer { signature },
}
}
Encoding => {
let encoding = opt(|i: &mut _| parse::header_field(i, b"encoding", take_till(1.., NL)))
.context(StrContext::Expected("encoding <encoding>".into()))
.parse_next(input)?;
*state = State::ExtraHeaders;
match encoding {
Some(encoding) => Token::Encoding(encoding.as_bstr()),
None => Self::next_inner_(input, state)?,
}
}
ExtraHeaders => {
let extra_header = opt(alt((
|i: &mut _| parse::any_header_field_multi_line(i).map(|(k, o)| (k.as_bstr(), Cow::Owned(o))),
|i: &mut _| {
parse::any_header_field(i, take_till(1.., NL))
.map(|(k, o)| (k.as_bstr(), Cow::Borrowed(o.as_bstr())))
},
)))
.context(StrContext::Expected("<field> <single-line|multi-line>".into()))
.parse_next(input)?;
match extra_header {
Some(extra_header) => Token::ExtraHeader(extra_header),
None => {
*state = State::Message;
Self::next_inner_(input, state)?
}
}
}
Message => {
let message = terminated(decode::message, eof).parse_next(input)?;
debug_assert!(
input.is_empty(),
"we should have consumed all data - otherwise iter may go forever"
);
Token::Message(message)
}
})
}
}
impl<'a> Iterator for CommitRefIter<'a> {
type Item = Result<Token<'a>, crate::decode::Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
match Self::next_inner(self.data, &mut self.state) {
Ok((data, token)) => {
self.data = data;
Some(Ok(token))
}
Err(err) => {
self.data = &[];
Some(Err(err))
}
}
}
}
struct CommitRefIterRaw<'a> {
data: &'a [u8],
state: State,
offset: usize,
}
impl<'a> Iterator for CommitRefIterRaw<'a> {
type Item = Result<RawToken<'a>, crate::decode::Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
match CommitRefIter::next_inner(self.data, &mut self.state) {
Ok((remaining, token)) => {
let consumed = self.data.len() - remaining.len();
let start = self.offset;
let end = start + consumed;
self.offset = end;
self.data = remaining;
Some(Ok(RawToken {
token,
token_range: start..end,
}))
}
Err(err) => {
self.data = &[];
Some(Err(err))
}
}
}
}
struct RawToken<'a> {
token: Token<'a>,
token_range: Range<usize>,
}
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Token<'a> {
Tree {
id: ObjectId,
},
Parent {
id: ObjectId,
},
Author {
signature: gix_actor::SignatureRef<'a>,
},
Committer {
signature: gix_actor::SignatureRef<'a>,
},
Encoding(&'a BStr),
ExtraHeader((&'a BStr, Cow<'a, BStr>)),
Message(&'a BStr),
}
impl Token<'_> {
pub fn id(&self) -> Option<&oid> {
match self {
Token::Tree { id } | Token::Parent { id } => Some(id.as_ref()),
_ => None,
}
}
pub fn try_into_id(self) -> Option<ObjectId> {
match self {
Token::Tree { id } | Token::Parent { id } => Some(id),
_ => None,
}
}
}