296 lines · 10.2 KB · Rust
RawDownload
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<git_actor::immutable::Signature<'a>>,    /// 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<Tag<'a>, 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 <object kind>", |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 <version>", |i| {            parse::header_field(i, b"tag", take_while1(|b| b != NL[0]))        })(i)?;         let (i, signature) = context(            "tagger <signature>",            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<ObjectId> {            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 <object kind>", |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 <version>", |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 <signature>",                        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<Token<'a>, object::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))                }            }        }    }     /// 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<git_actor::immutable::Signature<'a>>),        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<ObjectId> {            match self {                Token::Target { id } => Some(id),                _ => None,            }        }    }}