1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
use std::ops::Range;
use bstr::{BStr, BString, ByteSlice};
use winnow::prelude::*;
use crate::{Commit, CommitRef, TagRef};
mod decode;
///
#[allow(clippy::empty_docs)]
pub mod message;
/// A parsed commit message that assumes a title separated from the body by two consecutive newlines.
///
/// Titles can have any amount of whitespace
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MessageRef<'a> {
/// The title of the commit, as separated from the body with two consecutive newlines. The newlines are not included.
#[cfg_attr(feature = "serde", serde(borrow))]
pub title: &'a BStr,
/// All bytes not consumed by the title, excluding the separating newlines.
///
/// The body is `None` if there was now title separation or the body was empty after the separator.
pub body: Option<&'a BStr>,
}
/// The raw commit data, parseable by [`CommitRef`] or [`Commit`], which was fed into a program to produce a signature.
///
/// See [`extract_signature()`](crate::CommitRefIter::signature()) for how to obtain it.
// TODO: implement `std::io::Read` to avoid allocations
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SignedData<'a> {
/// The raw commit data that includes the signature.
data: &'a [u8],
/// The byte range at which we find the signature. All but the signature is the data that was signed.
signature_range: Range<usize>,
}
impl SignedData<'_> {
/// Convenience method to obtain a copy of the signed data.
pub fn to_bstring(&self) -> BString {
let mut buf = BString::from(&self.data[..self.signature_range.start]);
buf.extend_from_slice(&self.data[self.signature_range.end..]);
buf
}
}
impl From<SignedData<'_>> for BString {
fn from(value: SignedData<'_>) -> Self {
value.to_bstring()
}
}
///
#[allow(clippy::empty_docs)]
pub mod ref_iter;
mod write;
/// Lifecycle
impl<'a> CommitRef<'a> {
/// Deserialize a commit from the given `data` bytes while avoiding most allocations.
pub fn from_bytes(mut data: &'a [u8]) -> Result<CommitRef<'a>, crate::decode::Error> {
let input = &mut data;
match decode::commit.parse_next(input) {
Ok(tag) => Ok(tag),
Err(err) => Err(crate::decode::Error::with_err(err, input)),
}
}
}
/// Access
impl<'a> CommitRef<'a> {
/// Return the `tree` fields hash digest.
pub fn tree(&self) -> gix_hash::ObjectId {
gix_hash::ObjectId::from_hex(self.tree).expect("prior validation of tree hash during parsing")
}
/// Returns an iterator of parent object ids
pub fn parents(&self) -> impl Iterator<Item = gix_hash::ObjectId> + '_ {
self.parents
.iter()
.map(|hex_hash| gix_hash::ObjectId::from_hex(hex_hash).expect("prior validation of hashes during parsing"))
}
/// Returns a convenient iterator over all extra headers.
pub fn extra_headers(&self) -> crate::commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())))
}
/// Return the author, with whitespace trimmed.
///
/// This is different from the `author` field which may contain whitespace.
pub fn author(&self) -> gix_actor::SignatureRef<'a> {
self.author.trim()
}
/// Return the committer, with whitespace trimmed.
///
/// This is different from the `committer` field which may contain whitespace.
pub fn committer(&self) -> gix_actor::SignatureRef<'a> {
self.committer.trim()
}
/// Returns a partially parsed message from which more information can be derived.
pub fn message(&self) -> MessageRef<'a> {
MessageRef::from_bytes(self.message)
}
/// Returns the time at which this commit was created.
pub fn time(&self) -> gix_date::Time {
self.committer.time
}
}
impl Commit {
/// Returns a convenient iterator over all extra headers.
pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr())))
}
}
/// An iterator over extra headers in [owned][crate::Commit] and [borrowed][crate::CommitRef] commits.
pub struct ExtraHeaders<I> {
inner: I,
}
/// Instantiation and convenience.
impl<'a, I> ExtraHeaders<I>
where
I: Iterator<Item = (&'a BStr, &'a BStr)>,
{
/// Create a new instance from an iterator over tuples of (name, value) pairs.
pub fn new(iter: I) -> Self {
ExtraHeaders { inner: iter }
}
/// Find the _value_ of the _first_ header with the given `name`.
pub fn find(mut self, name: &str) -> Option<&'a BStr> {
self.inner
.find_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
}
/// Return an iterator over all _values_ of headers with the given `name`.
pub fn find_all(self, name: &'a str) -> impl Iterator<Item = &'a BStr> {
self.inner
.filter_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
}
/// Return an iterator over all git mergetags.
///
/// A merge tag is a tag object embedded within the respective header field of a commit, making
/// it a child object of sorts.
pub fn mergetags(self) -> impl Iterator<Item = Result<TagRef<'a>, crate::decode::Error>> {
self.find_all("mergetag").map(|b| TagRef::from_bytes(b))
}
/// Return the cryptographic signature provided by gpg/pgp verbatim.
pub fn pgp_signature(self) -> Option<&'a BStr> {
self.find("gpgsig")
}
}