This categorizes them semantically without using the overloaded names of owning and borrowing, which mean different things in Rust.
Showing the changes to git-object/src/immutable/commit.rs (1 of 34 files). Show all
Unified · Split
| @@ -0,0 +1,109 @@ | |||
| 1 | use super::Error; | ||
| 2 | use crate::{ | ||
| 3 | commit, | ||
| 4 | immutable::{parse, parse::NL, Signature}, | ||
| 5 | BStr, ByteSlice, | ||
| 6 | }; | ||
| 7 | use nom::{ | ||
| 8 | branch::alt, | ||
| 9 | bytes::{complete::is_not, complete::tag}, | ||
| 10 | combinator::{all_consuming, opt}, | ||
| 11 | multi::many0, | ||
| 12 | IResult, | ||
| 13 | }; | ||
| 14 | use smallvec::SmallVec; | ||
| 15 | use std::borrow::Cow; | ||
| 16 | |||
| 17 | /// A git commit parsed using [`from_bytes()`][Commit::from_bytes()]. | ||
| 18 | /// | ||
| 19 | /// A commit encapsulates information about a point in time at which the state of the repository is recorded, usually after a | ||
| 20 | /// change which is documented in the commit `message`. | ||
| 21 | #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] | ||
| 22 | #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] | ||
| 23 | pub struct Commit<'a> { | ||
| 24 | /// HEX hash of tree object we point to. Usually 40 bytes long. | ||
| 25 | /// | ||
| 26 | /// Use [`tree()`][Commit::tree()] to obtain a decoded version of it. | ||
| 27 | #[cfg_attr(feature = "serde1", serde(borrow))] | ||
| 28 | pub tree: &'a BStr, | ||
| 29 | /// HEX hash of each parent commit. Empty for first commit in repository. | ||
| 30 | pub parents: SmallVec<[&'a BStr; 1]>, | ||
| 31 | /// Who wrote this commit. | ||
| 32 | pub author: Signature<'a>, | ||
| 33 | /// Who committed this commit. | ||
| 34 | /// | ||
| 35 | /// This may be different from the `author` in case the author couldn't write to the repository themselves and | ||
| 36 | /// is commonly encountered with contributed commits. | ||
| 37 | pub committer: Signature<'a>, | ||
| 38 | /// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493). | ||
| 39 | pub encoding: Option<&'a BStr>, | ||
| 40 | /// The commit message documenting the change. | ||
| 41 | pub message: &'a BStr, | ||
| 42 | /// Extra header fields, in order of them being encountered, made accessible with the iterator returned by [`extra_headers()`][Commit::extra_headers()]. | ||
| 43 | pub extra_headers: Vec<(&'a BStr, Cow<'a, BStr>)>, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl<'a> Commit<'a> { | ||
| 47 | /// Deserialize a commit from the given `data` bytes while avoiding most allocations. | ||
| 48 | pub fn from_bytes(data: &'a [u8]) -> Result<Commit<'a>, Error> { | ||
| 49 | parse(data).map(|(_, t)| t).map_err(Error::from) | ||
| 50 | } | ||
| 51 | /// Return the `tree` fields hash digest. | ||
| 52 | pub fn tree(&self) -> git_hash::ObjectId { | ||
| 53 | git_hash::ObjectId::from_hex(self.tree).expect("prior validation of tree hash during parsing") | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Returns an iterator of parent object ids | ||
| 57 | pub fn parents(&self) -> impl Iterator<Item = git_hash::ObjectId> + '_ { | ||
| 58 | self.parents | ||
| 59 | .iter() | ||
| 60 | .map(|hex_hash| git_hash::ObjectId::from_hex(hex_hash).expect("prior validation of hashes during parsing")) | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Returns a convenient iterator over all extra headers. | ||
| 64 | pub fn extra_headers(&self) -> commit::ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> { | ||
| 65 | commit::ExtraHeaders::new(self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref()))) | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | fn parse_message(i: &[u8]) -> IResult<&[u8], &BStr, Error> { | ||
| 70 | if i.is_empty() { | ||
| 71 | // newline + [message] | ||
| 72 | return Err(nom::Err::Error(Error::NomDetail(i.into(), "commit message is missing"))); | ||
| 73 | } | ||
| 74 | let (i, _) = tag(NL)(i).map_err(Error::context("a newline separates headers from the message"))?; | ||
| 75 | debug_assert!(!i.is_empty()); | ||
| 76 | Ok((&[], &i.as_bstr())) | ||
| 77 | } | ||
| 78 | |||
| 79 | fn parse(i: &[u8]) -> IResult<&[u8], Commit<'_>, Error> { | ||
| 80 | let (i, tree) = | ||
| 81 | parse::header_field(i, b"tree", parse::hex_sha1).map_err(Error::context("tree <40 lowercase hex char>"))?; | ||
| 82 | let (i, parents) = many0(|i| parse::header_field(i, b"parent", parse::hex_sha1))(i) | ||
| 83 | .map_err(Error::context("zero or more 'parent <40 lowercase hex char>'"))?; | ||
| 84 | let (i, author) = | ||
| 85 | parse::header_field(i, b"author", parse::signature).map_err(Error::context("author <signature>"))?; | ||
| 86 | let (i, committer) = | ||
| 87 | parse::header_field(i, b"committer", parse::signature).map_err(Error::context("committer <signature>"))?; | ||
| 88 | let (i, encoding) = | ||
| 89 | opt(|i| parse::header_field(i, b"encoding", is_not(NL)))(i).map_err(Error::context("encoding <encoding>"))?; | ||
| 90 | let (i, extra_headers) = many0(alt(( | ||
| 91 | |i| parse::any_header_field_multi_line(i).map(|(i, (k, o))| (i, (k.as_bstr(), Cow::Owned(o)))), | ||
| 92 | |i| parse::any_header_field(i, is_not(NL)).map(|(i, (k, o))| (i, (k.as_bstr(), Cow::Borrowed(o.as_bstr())))), | ||
| 93 | )))(i) | ||
| 94 | .map_err(Error::context("<field> <single-line|multi-line>"))?; | ||
| 95 | let (i, message) = all_consuming(parse_message)(i)?; | ||
| 96 | |||
| 97 | Ok(( | ||
| 98 | i, | ||
| 99 | Commit { | ||
| 100 | tree, | ||
| 101 | parents: SmallVec::from(parents), | ||
| 102 | author, | ||
| 103 | committer, | ||
| 104 | encoding: encoding.map(ByteSlice::as_bstr), | ||
| 105 | message, | ||
| 106 | extra_headers, | ||
| 107 | }, | ||
| 108 | )) | ||
| 109 | } | ||