use crate::{immutable::object, BStr}; /// Represents a git tag, commonly indicating a software release. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Tag<'a> { /// The hash in hexadecimal being the object this tag points to. Use [`target()`][Tag::target()] to obtain a byte representation. #[cfg_attr(feature = "serde1", serde(borrow))] pub target: &'a BStr, /// The kind of object that `target` points to. pub target_kind: crate::Kind, /// The name of the tag, e.g. "v1.0". pub name: &'a BStr, /// The author of the tag. pub tagger: Option>, /// The message describing this release. pub message: &'a BStr, /// A cryptographic signature over the entire content of the serialized tag object thus far. pub pgp_signature: Option<&'a BStr>, } impl<'a> Tag<'a> { /// Deserialize a tag from `data`. pub fn from_bytes(data: &'a [u8]) -> Result, object::decode::Error> { decode::git_tag(data) .map(|(_, t)| t) .map_err(object::decode::Error::from) } /// The object this tag points to as `Id`. pub fn target(&self) -> git_hash::ObjectId { git_hash::ObjectId::from_hex(self.target).expect("prior validation") } } mod decode { use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while, take_while1}, character::is_alphabetic, combinator::{all_consuming, opt, recognize}, error::{context, ContextError, ParseError}, sequence::{preceded, tuple}, IResult, }; use crate::{ immutable::{parse, parse::NL, Tag}, BStr, ByteSlice, }; pub fn git_tag<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&[u8], Tag<'a>, E> { let (i, target) = context("object <40 lowercase hex char>", |i| { parse::header_field(i, b"object", parse::hex_hash) })(i)?; let (i, kind) = context("type ", |i| { parse::header_field(i, b"type", take_while1(is_alphabetic)) })(i)?; let kind = crate::Kind::from_bytes(kind) .map_err(|_| nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::MapRes)))?; let (i, tag_version) = context("tag ", |i| { parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) })(i)?; let (i, signature) = context( "tagger ", opt(|i| parse::header_field(i, b"tagger", parse::signature)), )(i)?; let (i, (message, pgp_signature)) = all_consuming(message)(i)?; Ok(( i, Tag { target, name: tag_version.as_bstr(), target_kind: kind, message, tagger: signature, pgp_signature, }, )) } pub fn message<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a BStr, Option<&'a BStr>), E> { const PGP_SIGNATURE_BEGIN: &[u8] = b"\n-----BEGIN PGP SIGNATURE-----"; const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----"; if i.is_empty() { return Ok((i, (i.as_bstr(), None))); } let (i, _) = tag(NL)(i)?; fn all_to_end<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a [u8], &'a [u8]), E> { if i.is_empty() { return Err(nom::Err::Error(E::from_error_kind(i, nom::error::ErrorKind::Eof))); } // an empty signature message signals that there is none - the function signature is needed // to work with 'alt(…)'. PGP signatures are never empty Ok((&[], (i, &[]))) } let (i, (message, signature)) = alt(( tuple(( take_until(PGP_SIGNATURE_BEGIN), preceded( tag(NL), recognize(tuple(( tag(&PGP_SIGNATURE_BEGIN[1..]), take_until(PGP_SIGNATURE_END), tag(PGP_SIGNATURE_END), take_while(|_| true), ))), ), )), all_to_end, ))(i)?; let (i, _) = opt(tag(NL))(i)?; Ok(( i, ( message.as_bstr(), if signature.is_empty() { None } else { Some(signature.as_bstr()) }, ), )) } } /// pub mod iter { use bstr::BStr; use git_hash::{oid, ObjectId}; use nom::{ bytes::complete::take_while1, character::is_alphabetic, combinator::{all_consuming, opt}, error::{context, ParseError}, }; use crate::{ bstr::ByteSlice, immutable::{object, parse, parse::NL, tag::decode}, Kind, }; enum State { Target, TargetKind, Name, Tagger, Message, } impl Default for State { fn default() -> Self { State::Target } } /// Like [`immutable::Tag`][super::Tag], but as `Iterator` to support entirely allocation free parsing. /// It's particularly useful to dereference only the target chain. pub struct Iter<'a> { data: &'a [u8], state: State, } impl<'a> Iter<'a> { /// Create a tag iterator from data. pub fn from_bytes(data: &'a [u8]) -> Iter<'a> { Iter { data, state: State::default(), } } /// Returns the target id of this tag if it is the first function called and if there is no error in decoding /// the data. /// /// Note that this method must only be called once or else will always return None while consuming a single token. /// Errors are coerced into options, hiding whether there was an error or not. The caller should assume an error if they /// call the method as intended. Such a squelched error cannot be recovered unless the objects data is retrieved and parsed again. /// `next()`. pub fn target_id(&mut self) -> Option { self.next().and_then(Result::ok).and_then(Token::into_id) } } impl<'a> Iter<'a> { fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), object::decode::Error> { use State::*; Ok(match state { Target => { let (i, target) = context("object <40 lowercase hex char>", |i| { parse::header_field(i, b"object", parse::hex_hash) })(i)?; *state = State::TargetKind; ( i, Token::Target { id: ObjectId::from_hex(target).expect("parsing validation"), }, ) } TargetKind => { let (i, kind) = context("type ", |i| { parse::header_field(i, b"type", take_while1(is_alphabetic)) })(i)?; let kind = crate::Kind::from_bytes(kind).map_err(|_| { let err = object::decode::ParseError::from_error_kind(i, nom::error::ErrorKind::MapRes); nom::Err::Error(err) })?; *state = State::Name; (i, Token::TargetKind(kind)) } Name => { let (i, tag_version) = context("tag ", |i| { parse::header_field(i, b"tag", take_while1(|b| b != NL[0])) })(i)?; *state = State::Tagger; (i, Token::Name(tag_version.as_bstr())) } Tagger => { let (i, signature) = context( "tagger ", opt(|i| parse::header_field(i, b"tagger", parse::signature)), )(i)?; *state = State::Message; (i, Token::Tagger(signature)) } Message => { let (i, (message, pgp_signature)) = all_consuming(decode::message)(i)?; debug_assert!( i.is_empty(), "we should have consumed all data - otherwise iter may go forever" ); return Ok((i, Token::Body { message, pgp_signature })); } }) } } impl<'a> Iterator for Iter<'a> { type Item = Result, object::decode::Error>; fn next(&mut self) -> Option { 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)) } } } } /// A token returned by the [commit iterator][Iter]. #[allow(missing_docs)] #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Token<'a> { Target { id: ObjectId, }, TargetKind(Kind), Name(&'a BStr), Tagger(Option>), Body { message: &'a BStr, pgp_signature: Option<&'a BStr>, }, } impl<'a> Token<'a> { /// Return the object id of this token if its a [Target][Token::Target]. pub fn id(&self) -> Option<&oid> { match self { Token::Target { id } => Some(id.as_ref()), _ => None, } } /// Return the owned object id of this token if its a [Target][Token::Target]. pub fn into_id(self) -> Option { match self { Token::Target { id } => Some(id), _ => None, } } } }