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 (8 of 34 files). Show all

Unified · Split

8 files
  1. git-object/src/immutable/blob.rs +16 −0
  2. git-object/src/immutable/commit.rs +109 −0
  3. git-object/src/immutable/mod.rs +22 −0
  4. git-object/src/immutable/object.rs +184 −0
  5. git-object/src/immutable/object/error.rs +59 −0
  6. git-object/src/immutable/parse.rs +171 −0
  7. git-object/src/immutable/tag.rs +115 −0
  8. git-object/src/immutable/tree.rs +79 −0
git-object/src/immutable/blob.rs+16−0
@@ -0,0 +1,16 @@
1use std::convert::Infallible;
2
3/// A chunk of any [`data`][Blob::data].
4#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
5#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
6pub struct Blob<'a> {
7 /// The bytes themselves.
8 pub data: &'a [u8],
9}
10
11impl<'a> Blob<'a> {
12 /// Instantiate a `Blob` from the given `data`, which is used as-is.
13 pub fn from_bytes(data: &[u8]) -> Result<Blob<'_>, Infallible> {
14 Ok(Blob { data })
15 }
16}
git-object/src/immutable/commit.rs+109−0
@@ -0,0 +1,109 @@
1use super::Error;
2use crate::{
3 commit,
4 immutable::{parse, parse::NL, Signature},
5 BStr, ByteSlice,
6};
7use nom::{
8 branch::alt,
9 bytes::{complete::is_not, complete::tag},
10 combinator::{all_consuming, opt},
11 multi::many0,
12 IResult,
13};
14use smallvec::SmallVec;
15use 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))]
23pub 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
46impl<'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
69fn 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
79fn 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}
git-object/src/immutable/mod.rs+22−0
@@ -0,0 +1,22 @@
1//! Immutable objects are read-only structures referencing most data from [a byte slice][Object::from_bytes()].
2//!
3//! Immutable objects are expected to be deserialized from bytes that acts as backing store, and they
4//! cannot be mutated or serialized. Instead, one will [convert][Object::into_mutable()] them into their [`mutable`][crate::mutable] counterparts
5//! which support mutation and serialization.
6mod commit;
7pub use commit::Commit;
8
9mod tag;
10pub use tag::Tag;
11
12///
13pub mod tree;
14pub use tree::Tree;
15
16mod blob;
17pub use blob::Blob;
18
19mod object;
20pub use object::{Error, Object, Signature};
21
22mod parse;
git-object/src/immutable/object.rs+184−0
@@ -0,0 +1,184 @@
1use bstr::BStr;
2
3use crate::{
4 immutable,
5 immutable::{parse, Blob, Commit, Tag, Tree},
6 Kind, Time,
7};
8
9mod error;
10pub use error::Error;
11
12/// A signature is created by an actor at a certain time.
13///
14/// Note that this is not a cryptographical signature.
15#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
16#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
17pub struct Signature<'a> {
18 /// The actor's name.
19 #[cfg_attr(feature = "serde1", serde(borrow))]
20 pub name: &'a BStr,
21 /// The actor's email.
22 pub email: &'a BStr,
23 /// The time stamp at which the signature was performed.
24 pub time: Time,
25}
26
27impl<'a> Signature<'a> {
28 /// Deserialize a signature from the given `data`.
29 pub fn from_bytes(data: &'a [u8]) -> Result<Signature<'a>, Error> {
30 parse::signature(data).map(|(_, t)| t).map_err(Error::from)
31 }
32}
33
34/// An immutable object representing [`Trees`][Tree], [`Blobs`][Blob], [`Commits`][Commit], or [`Tags`][Tag].
35#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
36#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
37#[allow(missing_docs)]
38pub enum Object<'a> {
39 #[cfg_attr(feature = "serde1", serde(borrow))]
40 Tree(Tree<'a>),
41 Blob(Blob<'a>),
42 Commit(Commit<'a>),
43 Tag(Tag<'a>),
44}
45
46impl<'a> Object<'a> {
47 /// Deserialize an object of `kind` from the given `data`.
48 pub fn from_bytes(kind: Kind, data: &'a [u8]) -> Result<Object<'a>, Error> {
49 Ok(match kind {
50 Kind::Tree => Object::Tree(Tree::from_bytes(data)?),
51 Kind::Blob => Object::Blob(Blob { data }),
52 Kind::Commit => Object::Commit(Commit::from_bytes(data)?),
53 Kind::Tag => Object::Tag(Tag::from_bytes(data)?),
54 })
55 }
56
57 /// Convert the immutable object into a mutable version, consuming the source in the process.
58 ///
59 /// Note that this is an expensive operation.
60 pub fn into_mutable(self) -> crate::mutable::Object {
61 self.into()
62 }
63
64 /// Convert this immutable object into its mutable counterpart.
65 ///
66 /// Note that this is an expensive operation.
67 pub fn to_mutable(&self) -> crate::mutable::Object {
68 self.clone().into()
69 }
70}
71
72/// Convenient access to contained objects.
73impl<'a> Object<'a> {
74 /// Interpret this object as blob.
75 pub fn as_blob(&self) -> Option<&immutable::Blob<'_>> {
76 match self {
77 Object::Blob(v) => Some(v),
78 _ => None,
79 }
80 }
81 /// Interpret this object as commit.
82 pub fn as_commit(&self) -> Option<&immutable::Commit<'a>> {
83 match self {
84 Object::Commit(v) => Some(v),
85 _ => None,
86 }
87 }
88 /// Interpret this object as tree.
89 pub fn as_tree(&self) -> Option<&immutable::Tree<'_>> {
90 match self {
91 Object::Tree(v) => Some(v),
92 _ => None,
93 }
94 }
95 /// Interpret this object as tag.
96 pub fn as_tag(&self) -> Option<&immutable::Tag<'_>> {
97 match self {
98 Object::Tag(v) => Some(v),
99 _ => None,
100 }
101 }
102 /// Return the kind of object.
103 pub fn kind(&self) -> Kind {
104 match self {
105 Object::Tree(_) => Kind::Tree,
106 Object::Blob(_) => Kind::Blob,
107 Object::Commit(_) => Kind::Commit,
108 Object::Tag(_) => Kind::Tag,
109 }
110 }
111}
112
113mod convert {
114 use crate::immutable::{Blob, Commit, Object, Tag, Tree};
115 use std::convert::TryFrom;
116
117 impl<'a> From<Tag<'a>> for Object<'a> {
118 fn from(v: Tag<'a>) -> Self {
119 Object::Tag(v)
120 }
121 }
122
123 impl<'a> From<Commit<'a>> for Object<'a> {
124 fn from(v: Commit<'a>) -> Self {
125 Object::Commit(v)
126 }
127 }
128
129 impl<'a> From<Tree<'a>> for Object<'a> {
130 fn from(v: Tree<'a>) -> Self {
131 Object::Tree(v)
132 }
133 }
134
135 impl<'a> From<Blob<'a>> for Object<'a> {
136 fn from(v: Blob<'a>) -> Self {
137 Object::Blob(v)
138 }
139 }
140
141 impl<'a> TryFrom<Object<'a>> for Tag<'a> {
142 type Error = Object<'a>;
143
144 fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
145 Ok(match value {
146 Object::Tag(v) => v,
147 _ => return Err(value),
148 })
149 }
150 }
151
152 impl<'a> TryFrom<Object<'a>> for Commit<'a> {
153 type Error = Object<'a>;
154
155 fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
156 Ok(match value {
157 Object::Commit(v) => v,
158 _ => return Err(value),
159 })
160 }
161 }
162
163 impl<'a> TryFrom<Object<'a>> for Tree<'a> {
164 type Error = Object<'a>;
165
166 fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
167 Ok(match value {
168 Object::Tree(v) => v,
169 _ => return Err(value),
170 })
171 }
172 }
173
174 impl<'a> TryFrom<Object<'a>> for Blob<'a> {
175 type Error = Object<'a>;
176
177 fn try_from(value: Object<'a>) -> Result<Self, Self::Error> {
178 Ok(match value {
179 Object::Blob(v) => v,
180 _ => return Err(value),
181 })
182 }
183 }
184}
git-object/src/immutable/object/error.rs+59−0
@@ -0,0 +1,59 @@
1use nom::error::ParseError;
2use quick_error::quick_error;
3quick_error! {
4 /// An error returned by various [`Commit`][crate::immutable::Commit] and [`Signature`][crate::immutable::Signature] methods.
5 #[derive(Debug)]
6 #[allow(missing_docs)]
7 pub enum Error {
8 ParseIntegerError(msg: &'static str, number: crate::BString, err: btoi::ParseIntegerError) {
9 display("{}: {:?}", msg, number)
10 source(err)
11 }
12 Nom(err_msg: String) {
13 display("{}", err_msg)
14 }
15 NomDetail(input: crate::BString, msg: &'static str) {
16 display("{}: '{}' could not be parsed", msg, input)
17 }
18 ParseKindError(err: crate::types::Error) {
19 display("{}", err)
20 source(err)
21 }
22 ObjectKind(err: crate::Error) {
23 from()
24 source(err)
25 }
26 }
27}
28
29impl Error {
30 fn set_parse_context(mut self, ctx: &'static str) -> Self {
31 if let Error::NomDetail(_, ref mut message) = self {
32 *message = ctx
33 }
34 self
35 }
36
37 pub(crate) fn context(msg: &'static str) -> impl Fn(nom::Err<Self>) -> nom::Err<Self> {
38 move |e: nom::Err<Self>| e.map(|e| e.set_parse_context(msg))
39 }
40}
41
42impl ParseError<&[u8]> for Error {
43 fn from_error_kind(input: &[u8], _kind: nom::error::ErrorKind) -> Self {
44 Error::NomDetail(input.into(), "parse error")
45 }
46
47 fn append(_: &[u8], _: nom::error::ErrorKind, other: Self) -> Self {
48 other
49 }
50}
51
52impl From<nom::Err<Error>> for Error {
53 fn from(e: nom::Err<Error>) -> Self {
54 match e {
55 nom::Err::Error(err) | nom::Err::Failure(err) => Error::Nom(err.to_string()),
56 nom::Err::Incomplete(_) => unreachable!("we do not implement streaming parsers"),
57 }
58 }
59}
git-object/src/immutable/parse.rs+171−0
@@ -0,0 +1,171 @@
1use crate::{
2 immutable::{Error, Signature},
3 ByteSlice, Sign, Time,
4};
5use bstr::{BStr, BString, ByteVec};
6use btoi::btoi;
7use nom::{
8 branch::alt,
9 bytes::complete::{is_not, tag, take, take_until, take_while_m_n},
10 character::is_digit,
11 combinator::{peek, recognize},
12 multi::many1_count,
13 sequence::{preceded, terminated, tuple},
14 IResult,
15};
16
17pub(crate) const NL: &[u8] = b"\n";
18pub(crate) const SPACE: &[u8] = b" ";
19pub(crate) const SPACE_OR_NL: &[u8] = b" \n";
20
21pub(crate) fn any_header_field_multi_line(i: &[u8]) -> IResult<&[u8], (&[u8], BString), Error> {
22 let (i, (k, o)) = peek(tuple((
23 terminated(is_not(SPACE_OR_NL), tag(SPACE)),
24 recognize(tuple((
25 is_not(NL),
26 tag(NL),
27 many1_count(terminated(tuple((tag(SPACE), take_until(NL))), tag(NL))),
28 ))),
29 )))(i)?;
30 assert!(!o.is_empty());
31 let end = &o[o.len() - 1] as *const u8 as usize;
32 let start_input = &i[0] as *const u8 as usize;
33
34 let bytes = o[..o.len() - 1].as_bstr();
35 let mut out = BString::from(Vec::with_capacity(bytes.len()));
36 let mut lines = bytes.lines();
37 out.push_str(lines.next().expect("first line"));
38 for line in lines {
39 out.push(b'\n');
40 out.push_str(&line[1..]); // cut leading space
41 }
42 Ok((&i[end - start_input + 1..], (k, out)))
43}
44
45pub(crate) fn header_field<'a, T>(
46 i: &'a [u8],
47 name: &'static [u8],
48 parse_value: impl Fn(&'a [u8]) -> IResult<&'a [u8], T, Error>,
49) -> IResult<&'a [u8], T, Error> {
50 terminated(preceded(terminated(tag(name), tag(SPACE)), parse_value), tag(NL))(i)
51}
52
53pub(crate) fn any_header_field<'a, T>(
54 i: &'a [u8],
55 parse_value: impl Fn(&'a [u8]) -> IResult<&'a [u8], T, Error>,
56) -> IResult<&'a [u8], (&'a [u8], T), Error> {
57 terminated(
58 tuple((terminated(is_not(SPACE_OR_NL), tag(SPACE)), parse_value)),
59 tag(NL),
60 )(i)
61}
62
63fn is_hex_digit_lc(b: u8) -> bool {
64 matches!(b, b'0'..=b'9' | b'a'..=b'f')
65}
66
67pub(crate) fn hex_sha1(i: &[u8]) -> IResult<&[u8], &BStr, Error> {
68 take_while_m_n(40usize, 40, is_hex_digit_lc)(i).map(|(i, o)| (i, o.as_bstr()))
69}
70
71pub(crate) fn signature(i: &[u8]) -> IResult<&[u8], Signature<'_>, Error> {
72 let (i, (name, email, time_in_seconds, tzsign, tzhour, tzminute)) = tuple((
73 terminated(take_until(&b" <"[..]), take(2usize)),
74 terminated(take_until(&b"> "[..]), take(2usize)),
75 terminated(take_until(SPACE), take(1usize)),
76 alt((tag(b"-"), tag(b"+"))),
77 take_while_m_n(2usize, 2, is_digit),
78 take_while_m_n(2usize, 2, is_digit),
79 ))(i)
80 .map_err(Error::context(
81 "tagger <name> <<email>> <time seconds since epoch> <+|-><HHMM>",
82 ))?;
83
84 let sign = if tzsign[0] == b'-' { Sign::Minus } else { Sign::Plus };
85 let hours = btoi::<i32>(&tzhour)
86 .map_err(|e| nom::Err::Error(Error::ParseIntegerError("invalid 'hours' string", tzhour.into(), e)))?;
87 let minutes = btoi::<i32>(&tzminute)
88 .map_err(|e| nom::Err::Error(Error::ParseIntegerError("invalid 'minutes' string", tzminute.into(), e)))?;
89 let offset = (hours * 3600 + minutes * 60) * if sign == Sign::Minus { -1 } else { 1 };
90
91 Ok((
92 i,
93 Signature {
94 name: name.as_bstr(),
95 email: email.as_bstr(),
96 time: Time {
97 time: btoi::<u32>(time_in_seconds).map_err(|e| {
98 nom::Err::Error(Error::ParseIntegerError(
99 "Could parse to seconds",
100 time_in_seconds.into(),
101 e,
102 ))
103 })?,
104 offset,
105 sign,
106 },
107 },
108 ))
109}
110
111#[cfg(test)]
112mod tests {
113 mod parse_signature {
114 use crate::{
115 immutable::{parse, Signature},
116 ByteSlice, Sign, Time,
117 };
118
119 fn signature(
120 name: &'static str,
121 email: &'static str,
122 time: u32,
123 sign: Sign,
124 offset: i32,
125 ) -> Signature<'static> {
126 Signature {
127 name: name.as_bytes().as_bstr(),
128 email: email.as_bytes().as_bstr(),
129 time: Time { time, offset, sign },
130 }
131 }
132
133 #[test]
134 fn tz_minus() {
135 assert_eq!(
136 parse::signature(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0230")
137 .expect("parse to work")
138 .1,
139 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, -9000)
140 );
141 }
142
143 #[test]
144 fn tz_plus() {
145 assert_eq!(
146 parse::signature(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 +0230")
147 .expect("parse to work")
148 .1,
149 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Plus, 9000)
150 );
151 }
152
153 #[test]
154 fn negative_offset_0000() {
155 assert_eq!(
156 parse::signature(b"Sebastian Thiel <byronimo@gmail.com> 1528473343 -0000")
157 .expect("parse to work")
158 .1,
159 signature("Sebastian Thiel", "byronimo@gmail.com", 1528473343, Sign::Minus, 0)
160 );
161 }
162
163 #[test]
164 fn empty_name_and_email() {
165 assert_eq!(
166 parse::signature(b" <> 12345 -1215").expect("parse to work").1,
167 signature("", "", 12345, Sign::Minus, -44100)
168 );
169 }
170 }
171}
git-object/src/immutable/tag.rs+115−0
@@ -0,0 +1,115 @@
1use crate::{
2 immutable::{parse, parse::NL, Error, Signature},
3 BStr, ByteSlice,
4};
5use nom::bytes::complete::take_while;
6use nom::{
7 branch::alt,
8 bytes::complete::{tag, take_until, take_while1},
9 character::is_alphabetic,
10 combinator::{all_consuming, opt, recognize},
11 sequence::{preceded, tuple},
12 IResult,
13};
14
15/// Represents a git tag, commonly indicating a software release.
16#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
17#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
18pub struct Tag<'a> {
19 /// The hash in hexadecimal being the object this tag points to. Use [`target()`][Tag::target()] to obtain a byte representation.
20 #[cfg_attr(feature = "serde1", serde(borrow))]
21 pub target: &'a BStr,
22 /// The name of the tag, e.g. "v1.0".
23 pub name: &'a BStr,
24 /// The kind of object that `target` points to.
25 pub target_kind: crate::Kind,
26 /// The message describing this release.
27 pub message: &'a BStr,
28 /// The author of the tag.
29 pub signature: Option<Signature<'a>>,
30 /// A cryptographic signature over the entire content of the serialized tag object thus far.
31 pub pgp_signature: Option<&'a BStr>,
32}
33
34impl<'a> Tag<'a> {
35 /// Deserialize a tag from `data`.
36 pub fn from_bytes(data: &'a [u8]) -> Result<Tag<'a>, Error> {
37 parse(data).map(|(_, t)| t).map_err(Error::from)
38 }
39 /// The object this tag points to as `Id`.
40 pub fn target(&self) -> git_hash::ObjectId {
41 git_hash::ObjectId::from_hex(self.target).expect("prior validation")
42 }
43}
44
45fn parse(i: &[u8]) -> IResult<&[u8], Tag<'_>, Error> {
46 let (i, target) =
47 parse::header_field(i, b"object", parse::hex_sha1).map_err(Error::context("object <40 lowercase hex char>"))?;
48
49 let (i, kind) =
50 parse::header_field(i, b"type", take_while1(is_alphabetic)).map_err(Error::context("type <object kind>"))?;
51 let kind = crate::Kind::from_bytes(kind).map_err(|e| nom::Err::Error(Error::ParseKindError(e)))?;
52
53 let (i, tag_version) =
54 parse::header_field(i, b"tag", take_while1(|b| b != NL[0])).map_err(Error::context("tag <version>"))?;
55
56 let (i, signature) = opt(|i| parse::header_field(i, b"tagger", parse::signature))(i)
57 .map_err(Error::context("tagger <signature>"))?;
58 let (i, (message, pgp_signature)) = all_consuming(parse_message)(i)?;
59 Ok((
60 i,
61 Tag {
62 target,
63 name: tag_version.as_bstr(),
64 target_kind: kind,
65 message,
66 signature,
67 pgp_signature,
68 },
69 ))
70}
71
72fn parse_message(i: &[u8]) -> IResult<&[u8], (&BStr, Option<&BStr>), Error> {
73 const PGP_SIGNATURE_BEGIN: &[u8] = b"\n-----BEGIN PGP SIGNATURE-----";
74 const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----";
75
76 if i.is_empty() {
77 return Ok((i, (i.as_bstr(), None)));
78 }
79 let (i, _) = tag(NL)(i)?;
80 fn all_to_end(i: &[u8]) -> IResult<&[u8], (&[u8], &[u8]), Error> {
81 if i.is_empty() {
82 return Err(nom::Err::Error(Error::NomDetail(i.into(), "tag message is missing")));
83 }
84 // an empty signature message signals that there is none - the function signature is needed
85 // to work with 'alt(…)'. PGP signatures are never empty
86 Ok((&[], (&i, &[])))
87 }
88 let (i, (message, signature)) = alt((
89 tuple((
90 take_until(PGP_SIGNATURE_BEGIN),
91 preceded(
92 tag(NL),
93 recognize(tuple((
94 tag(&PGP_SIGNATURE_BEGIN[1..]),
95 take_until(PGP_SIGNATURE_END),
96 tag(PGP_SIGNATURE_END),
97 take_while(|_| true),
98 ))),
99 ),
100 )),
101 all_to_end,
102 ))(i)?;
103 let (i, _) = opt(tag(NL))(i)?;
104 Ok((
105 i,
106 (
107 message.as_bstr(),
108 if signature.is_empty() {
109 None
110 } else {
111 Some(signature.as_bstr())
112 },
113 ),
114 ))
115}
git-object/src/immutable/tree.rs+79−0
@@ -0,0 +1,79 @@
1use crate::{immutable::parse::SPACE, immutable::Error, tree};
2use bstr::{BStr, ByteSlice};
3use nom::{
4 bytes::complete::{tag, take, take_while1, take_while_m_n},
5 character::is_digit,
6 combinator::all_consuming,
7 multi::many1,
8 sequence::terminated,
9 IResult,
10};
11use std::convert::TryFrom;
12
13/// A directory snapshot containing files (blobs), directories (trees) and submodules (commits).
14#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
15#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
16pub struct Tree<'a> {
17 /// The directories and files contained in this tree.
18 #[cfg_attr(feature = "serde1", serde(borrow))]
19 pub entries: Vec<Entry<'a>>,
20}
21
22/// An element of a [`Tree`][Tree::entries].
23#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
24#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
25pub struct Entry<'a> {
26 /// The kind of object to which `oid` is pointing.
27 pub mode: tree::Mode,
28 /// The name of the file in the parent tree.
29 pub filename: &'a BStr,
30 /// The id of the object representing the entry.
31 #[cfg_attr(feature = "serde1", serde(borrow))]
32 pub oid: &'a git_hash::oid,
33}
34
35impl<'a> Tree<'a> {
36 /// Deserialize a Tree from `data`.
37 pub fn from_bytes(data: &'a [u8]) -> Result<Tree<'a>, Error> {
38 parse(data).map(|(_, t)| t).map_err(Error::from)
39 }
40}
41
42impl TryFrom<&[u8]> for tree::Mode {
43 type Error = Error;
44
45 fn try_from(mode: &[u8]) -> Result<Self, Self::Error> {
46 Ok(match mode {
47 b"40000" => tree::Mode::Tree,
48 b"100644" => tree::Mode::Blob,
49 b"100664" => tree::Mode::Blob, // rare and found in the linux kernel
50 b"100640" => tree::Mode::Blob, // rare and found in the Rust repo
51 b"100755" => tree::Mode::BlobExecutable,
52 b"120000" => tree::Mode::Link,
53 b"160000" => tree::Mode::Commit,
54 _ => return Err(Error::NomDetail(mode.into(), "unknown tree mode")),
55 })
56 }
57}
58
59const NULL: &[u8] = b"\0";
60fn parse_entry(i: &[u8]) -> IResult<&[u8], Entry<'_>, Error> {
61 let (i, mode) = terminated(take_while_m_n(5, 6, is_digit), tag(SPACE))(i)?;
62 let mode = tree::Mode::try_from(mode).map_err(nom::Err::Error)?;
63 let (i, filename) = terminated(take_while1(|b| b != NULL[0]), tag(NULL))(i)?;
64 let (i, oid) = take(20u8)(i)?;
65
66 Ok((
67 i,
68 Entry {
69 mode,
70 filename: filename.as_bstr(),
71 oid: git_hash::oid::try_from(oid).expect("we counted exactly 20 bytes"),
72 },
73 ))
74}
75
76fn parse(i: &[u8]) -> IResult<&[u8], Tree<'_>, Error> {
77 let (i, entries) = all_consuming(many1(parse_entry))(i)?;
78 Ok((i, Tree { entries }))
79}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t