use bstr::ByteSlice;
use crate::{parse, parse::ParseResult, BStr, Kind, TagRef};
pub fn git_tag<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<TagRef<'a>> {
let target = target(i, hash_kind)?;
let kind = kind(i)?;
let tag_version = name(i)?;
let tagger = tagger_raw(i)?;
let (message, pgp_signature) = message(i)?;
if !i.is_empty() {
return Err(crate::decode::Error);
}
Ok(TagRef {
target,
name: tag_version.as_bstr(),
target_kind: kind,
message,
tagger,
pgp_signature,
})
}
pub(crate) fn target<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<&'a BStr> {
parse::header_field(i, b"object", |value| parse::hex_hash(value, hash_kind))
}
pub(crate) fn kind(i: &mut &[u8]) -> ParseResult<Kind> {
parse::header_field(i, b"type", |value| {
Kind::from_bytes(value).map_err(|_| crate::decode::Error)
})
}
pub(crate) fn name<'a>(i: &mut &'a [u8]) -> ParseResult<&'a BStr> {
parse::header_field(i, b"tag", |value| {
(!value.is_empty()).then(|| value.as_bstr()).ok_or(crate::decode::Error)
})
}
pub(crate) fn tagger_raw<'a>(i: &mut &'a [u8]) -> ParseResult<Option<&'a BStr>> {
if !i.starts_with(b"tagger ") {
return Ok(None);
}
parse::header_field(i, b"tagger", |raw| {
let mut sig = raw;
gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?;
Ok(raw.as_bstr())
})
.map(Some)
}
pub(crate) fn tagger<'a>(i: &mut &'a [u8]) -> ParseResult<Option<gix_actor::SignatureRef<'a>>> {
if !i.starts_with(b"tagger ") {
return Ok(None);
}
parse::header_field(i, b"tagger", |i| {
let mut sig = i;
let signature = gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?;
Ok(signature)
})
.map(Some)
}
pub fn message<'a>(i: &mut &'a [u8]) -> ParseResult<(&'a BStr, Option<&'a BStr>)> {
const PGP_SIGNATURE_BEGIN: &[u8] = b"-----BEGIN PGP SIGNATURE-----";
if i.iter().all(|b| *b == b'\n') {
let message = i.as_bstr();
*i = &[];
return Ok((message, None));
}
let Some(rest) = i.strip_prefix(parse::NL) else {
return Err(crate::decode::Error);
};
*i = &[];
if let Some(sig_start) = find_pgp_signature(rest, PGP_SIGNATURE_BEGIN) {
let message_end = if sig_start > 0 && rest[sig_start - 1] == b'\n' {
sig_start - 1
} else {
sig_start
};
let message = rest[..message_end].as_bstr();
let signature = &rest[sig_start..];
return Ok((message, (!signature.is_empty()).then(|| signature.as_bstr())));
}
Ok((rest.as_bstr(), None))
}
fn find_pgp_signature(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if haystack.starts_with(needle) {
return Some(0);
}
let mut offset = 0;
while let Some(pos) = haystack.get(offset..)?.find_byte(b'\n') {
let found = offset + pos + 1;
if haystack[found..].starts_with(needle) {
return Some(found);
}
offset = found;
}
None
}