use nom::bytes::complete::take_while; use nom::{ branch::alt, bytes::complete::{tag, take_until, take_while1}, character::is_alphabetic, combinator::{all_consuming, opt, recognize}, sequence::{preceded, tuple}, IResult, }; use crate::{ immutable::{object::decode, parse, parse::NL, Signature}, BStr, ByteSlice, }; /// 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 name of the tag, e.g. "v1.0". pub name: &'a BStr, /// The kind of object that `target` points to. pub target_kind: crate::Kind, /// The message describing this release. pub message: &'a BStr, /// The author of the tag. pub signature: Option>, /// 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, decode::Error> { parse(data).map(|(_, t)| t).map_err(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") } } fn parse(i: &[u8]) -> IResult<&[u8], Tag<'_>, decode::Error> { let (i, target) = parse::header_field(i, b"object", parse::hex_sha1) .map_err(decode::Error::context("object <40 lowercase hex char>"))?; let (i, kind) = parse::header_field(i, b"type", take_while1(is_alphabetic)) .map_err(decode::Error::context("type "))?; let kind = crate::Kind::from_bytes(kind).map_err(|e| nom::Err::Error(decode::Error::ParseKindError(e)))?; let (i, tag_version) = parse::header_field(i, b"tag", take_while1(|b| b != NL[0])).map_err(decode::Error::context("tag "))?; let (i, signature) = opt(|i| parse::header_field(i, b"tagger", parse::signature))(i) .map_err(decode::Error::context("tagger "))?; let (i, (message, pgp_signature)) = all_consuming(parse_message)(i)?; Ok(( i, Tag { target, name: tag_version.as_bstr(), target_kind: kind, message, signature, pgp_signature, }, )) } fn parse_message(i: &[u8]) -> IResult<&[u8], (&BStr, Option<&BStr>), decode::Error> { 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(i: &[u8]) -> IResult<&[u8], (&[u8], &[u8]), decode::Error> { if i.is_empty() { return Err(nom::Err::Error(decode::Error::NomDetail( i.into(), "tag message is missing", ))); } // 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()) }, ), )) }