History

rename git-object::(owned->mutable)|(borrowed|immutable) #(67)

This categorizes them semantically without using the overloaded names of owning and borrowing, which mean different things in Rust.

Sebastian Thiel committed 5 years ago

Commit 91ee5581 parent cd7356034 files · +149 −141Browse files

Showing the changes to git-object/src/immutable/commit.rs (1 of 34 files). Show all

Unified · Split

git-object/src/immutable/commit.rs+109−0
@@ -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+}