Skip to content

Commit fe12e9f

Browse files
git-object 0.22.1 + GitoxideLabs/gitoxide#604
0 parents  commit fe12e9f

26 files changed

+3109
-0
lines changed

Cargo.toml

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
2+
#
3+
# When uploading crates to the registry Cargo will automatically
4+
# "normalize" Cargo.toml files for maximal compatibility
5+
# with all versions of Cargo and also rewrite `path` dependencies
6+
# to registry (e.g., crates.io) dependencies.
7+
#
8+
# If you are reading this file be aware that the original Cargo.toml
9+
# will likely look very different (and much more reasonable).
10+
# See Cargo.toml.orig for the original contents.
11+
12+
[package]
13+
edition = "2018"
14+
name = "git-object"
15+
version = "0.22.1"
16+
authors = ["Sebastian Thiel <[email protected]>"]
17+
include = ["src/**/*"]
18+
description = "Immutable and mutable git objects with decoding and encoding support"
19+
license = "MIT/Apache-2.0"
20+
repository = "https://github.com/Byron/gitoxide"
21+
resolver = "2"
22+
23+
[package.metadata.docs.rs]
24+
all-features = true
25+
features = ["document-features"]
26+
rustdoc-args = [
27+
"--cfg",
28+
"docsrs",
29+
]
30+
31+
[lib]
32+
doctest = false
33+
34+
[dependencies.bstr]
35+
version = "1.0.1"
36+
features = [
37+
"std",
38+
"unicode",
39+
]
40+
default-features = false
41+
42+
[dependencies.btoi]
43+
version = "0.4.2"
44+
45+
[dependencies.document-features]
46+
version = "0.2.0"
47+
optional = true
48+
49+
[dependencies.git-actor]
50+
version = "^0.13.0"
51+
52+
[dependencies.git-features]
53+
version = "^0.23.1"
54+
features = ["rustsha1"]
55+
56+
[dependencies.git-hash]
57+
version = "^0.9.11"
58+
59+
[dependencies.git-validate]
60+
version = "^0.6.0"
61+
62+
[dependencies.hex]
63+
version = "0.4.2"
64+
65+
[dependencies.itoa]
66+
version = "1.0.1"
67+
68+
[dependencies.nom]
69+
version = "7"
70+
features = ["std"]
71+
default-features = false
72+
73+
[dependencies.serde]
74+
version = "1.0.114"
75+
features = ["derive"]
76+
optional = true
77+
default-features = false
78+
79+
[dependencies.smallvec]
80+
version = "1.4.0"
81+
features = ["write"]
82+
83+
[dependencies.thiserror]
84+
version = "1.0.34"
85+
86+
[dev-dependencies.pretty_assertions]
87+
version = "1.0.0"
88+
89+
[features]
90+
serde1 = [
91+
"serde",
92+
"bstr/serde",
93+
"smallvec/serde",
94+
"git-hash/serde1",
95+
"git-actor/serde1",
96+
]
97+
verbose-object-parsing-errors = ["nom/std"]

METADATA.bzl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
##
2+
## @generated by fbcode//common/rust/tools/reindeer/buckify
3+
## Do not edit by hand.
4+
##
5+
## See https://fburl.com/rust-third-party for instructions on how to update this.
6+
##
7+
8+
METADATA = {
9+
"licenses": [
10+
"MIT",
11+
"Apache-2.0",
12+
],
13+
"maintainers": [],
14+
"name": "git-object",
15+
"upstream_address": "",
16+
"upstream_hash": "",
17+
"upstream_type": "",
18+
"version": "0.22.1",
19+
}

src/blob.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::{convert::Infallible, io};
2+
3+
use crate::{Blob, BlobRef, Kind};
4+
5+
impl<'a> crate::WriteTo for BlobRef<'a> {
6+
/// Write the blobs data to `out` verbatim.
7+
fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
8+
out.write_all(self.data)
9+
}
10+
11+
fn size(&self) -> usize {
12+
self.data.len()
13+
}
14+
15+
fn kind(&self) -> Kind {
16+
Kind::Blob
17+
}
18+
}
19+
20+
impl crate::WriteTo for Blob {
21+
/// Write the blobs data to `out` verbatim.
22+
fn write_to(&self, out: impl io::Write) -> io::Result<()> {
23+
self.to_ref().write_to(out)
24+
}
25+
26+
fn size(&self) -> usize {
27+
self.to_ref().size()
28+
}
29+
30+
fn kind(&self) -> Kind {
31+
Kind::Blob
32+
}
33+
}
34+
35+
impl Blob {
36+
/// Provide a `BlobRef` to this owned blob
37+
pub fn to_ref(&self) -> BlobRef<'_> {
38+
BlobRef { data: &self.data }
39+
}
40+
}
41+
42+
impl<'a> BlobRef<'a> {
43+
/// Instantiate a `Blob` from the given `data`, which is used as-is.
44+
pub fn from_bytes(data: &[u8]) -> Result<BlobRef<'_>, Infallible> {
45+
Ok(BlobRef { data })
46+
}
47+
}

src/commit/decode.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::borrow::Cow;
2+
3+
use nom::{
4+
branch::alt,
5+
bytes::complete::{is_not, tag},
6+
combinator::{all_consuming, opt},
7+
error::{context, ContextError, ParseError},
8+
multi::many0,
9+
IResult, Parser,
10+
};
11+
use smallvec::SmallVec;
12+
13+
use crate::{parse, parse::NL, BStr, ByteSlice, CommitRef};
14+
15+
pub fn message<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], &'a BStr, E> {
16+
if i.is_empty() {
17+
// newline + [message]
18+
return Err(nom::Err::Error(E::add_context(
19+
i,
20+
"newline + <message>",
21+
E::from_error_kind(i, nom::error::ErrorKind::Eof),
22+
)));
23+
}
24+
let (i, _) = context("a newline separates headers from the message", tag(NL))(i)?;
25+
Ok((&[], i.as_bstr()))
26+
}
27+
28+
pub fn commit<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
29+
i: &'a [u8],
30+
) -> IResult<&'a [u8], CommitRef<'_>, E> {
31+
let (i, tree) = context("tree <40 lowercase hex char>", |i| {
32+
parse::header_field(i, b"tree", parse::hex_hash)
33+
})(i)?;
34+
let (i, parents) = context(
35+
"zero or more 'parent <40 lowercase hex char>'",
36+
many0(|i| parse::header_field(i, b"parent", parse::hex_hash)),
37+
)(i)?;
38+
let (i, author) = context("author <signature>", |i| {
39+
parse::header_field(i, b"author", parse::signature)
40+
})(i)?;
41+
let (i, committer) = context("committer <signature>", |i| {
42+
parse::header_field(i, b"committer", parse::signature)
43+
})(i)?;
44+
let (i, encoding) = context(
45+
"encoding <encoding>",
46+
opt(|i| parse::header_field(i, b"encoding", is_not(NL))),
47+
)(i)?;
48+
let (i, extra_headers) = context(
49+
"<field> <single-line|multi-line>",
50+
many0(alt((
51+
parse::any_header_field_multi_line.map(|(k, o)| (k.as_bstr(), Cow::Owned(o))),
52+
|i| {
53+
parse::any_header_field(i, is_not(NL)).map(|(i, (k, o))| (i, (k.as_bstr(), Cow::Borrowed(o.as_bstr()))))
54+
},
55+
))),
56+
)(i)?;
57+
let (i, message) = all_consuming(message)(i)?;
58+
59+
Ok((
60+
i,
61+
CommitRef {
62+
tree,
63+
parents: SmallVec::from(parents),
64+
author,
65+
committer,
66+
encoding: encoding.map(ByteSlice::as_bstr),
67+
message,
68+
extra_headers,
69+
},
70+
))
71+
}

src/commit/message/body.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::ops::Deref;
2+
3+
use nom::{
4+
bytes::complete::{tag, take_until1},
5+
combinator::all_consuming,
6+
error::{ErrorKind, ParseError},
7+
sequence::terminated,
8+
IResult,
9+
};
10+
11+
use crate::{
12+
bstr::{BStr, ByteSlice},
13+
commit::message::BodyRef,
14+
};
15+
16+
/// An iterator over trailers as parsed from a commit message body.
17+
///
18+
/// lines with parsing failures will be skipped
19+
pub struct Trailers<'a> {
20+
pub(crate) cursor: &'a [u8],
21+
}
22+
23+
/// A trailer as parsed from the commit message body.
24+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
25+
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
26+
pub struct TrailerRef<'a> {
27+
/// The name of the trailer, like "Signed-off-by", up to the separator ": "
28+
#[cfg_attr(feature = "serde1", serde(borrow))]
29+
pub token: &'a BStr,
30+
/// The value right after the separator ": ", with leading and trailing whitespace trimmed.
31+
/// Note that multi-line values aren't currently supported.
32+
pub value: &'a BStr,
33+
}
34+
35+
fn parse_single_line_trailer<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], (&'a BStr, &'a BStr), E> {
36+
let (value, token) = terminated(take_until1(b":".as_ref()), tag(b": "))(i.trim_end())?;
37+
if token.trim_end().len() != token.len() || value.trim_start().len() != value.len() {
38+
Err(nom::Err::Failure(E::from_error_kind(i, ErrorKind::Fail)))
39+
} else {
40+
Ok((&[], (token.as_bstr(), value.as_bstr())))
41+
}
42+
}
43+
44+
impl<'a> Iterator for Trailers<'a> {
45+
type Item = TrailerRef<'a>;
46+
47+
fn next(&mut self) -> Option<Self::Item> {
48+
if self.cursor.is_empty() {
49+
return None;
50+
}
51+
for line in self.cursor.lines_with_terminator() {
52+
self.cursor = &self.cursor[line.len()..];
53+
if let Some(trailer) =
54+
all_consuming(parse_single_line_trailer::<()>)(line)
55+
.ok()
56+
.map(|(_, (token, value))| TrailerRef {
57+
token: token.trim().as_bstr(),
58+
value: value.trim().as_bstr(),
59+
})
60+
{
61+
return Some(trailer);
62+
}
63+
}
64+
None
65+
}
66+
}
67+
68+
impl<'a> BodyRef<'a> {
69+
/// Parse `body` bytes into the trailer and the actual body.
70+
pub fn from_bytes(body: &'a [u8]) -> Self {
71+
body.rfind(b"\n\n")
72+
.map(|pos| (2, pos))
73+
.or_else(|| body.rfind(b"\r\n\r\n").map(|pos| (4, pos)))
74+
.and_then(|(sep_len, pos)| {
75+
let trailer = &body[pos + sep_len..];
76+
let body = &body[..pos];
77+
Trailers { cursor: trailer }.next().map(|_| BodyRef {
78+
body_without_trailer: body.as_bstr(),
79+
start_of_trailer: trailer,
80+
})
81+
})
82+
.unwrap_or_else(|| BodyRef {
83+
body_without_trailer: body.as_bstr(),
84+
start_of_trailer: &[],
85+
})
86+
}
87+
88+
/// Returns the body with the trailers stripped.
89+
///
90+
/// You can iterate trailers with the [`trailers()`][BodyRef::trailers()] method.
91+
pub fn without_trailer(&self) -> &'a BStr {
92+
self.body_without_trailer
93+
}
94+
95+
/// Return an iterator over the trailers parsed from the last paragraph of the body. May be empty.
96+
pub fn trailers(&self) -> Trailers<'a> {
97+
Trailers {
98+
cursor: self.start_of_trailer,
99+
}
100+
}
101+
}
102+
103+
impl<'a> AsRef<BStr> for BodyRef<'a> {
104+
fn as_ref(&self) -> &BStr {
105+
self.body_without_trailer
106+
}
107+
}
108+
109+
impl<'a> Deref for BodyRef<'a> {
110+
type Target = BStr;
111+
112+
fn deref(&self) -> &Self::Target {
113+
self.body_without_trailer
114+
}
115+
}
116+
#[cfg(test)]
117+
mod test_parse_trailer {
118+
use super::*;
119+
120+
fn parse(input: &str) -> (&BStr, &BStr) {
121+
parse_single_line_trailer::<()>(input.as_bytes()).unwrap().1
122+
}
123+
124+
#[test]
125+
fn simple_newline() {
126+
assert_eq!(parse("foo: bar\n"), ("foo".into(), "bar".into()));
127+
}
128+
129+
#[test]
130+
fn simple_non_ascii_no_newline() {
131+
assert_eq!(parse("🤗: 🎉"), ("🤗".into(), "🎉".into()));
132+
}
133+
134+
#[test]
135+
fn with_lots_of_whitespace_newline() {
136+
assert_eq!(
137+
parse("hello foo: bar there \n"),
138+
("hello foo".into(), "bar there".into())
139+
);
140+
}
141+
142+
#[test]
143+
fn extra_whitespace_before_token_or_value_is_error() {
144+
assert!(parse_single_line_trailer::<()>(b"foo : bar").is_err());
145+
assert!(parse_single_line_trailer::<()>(b"foo: bar").is_err())
146+
}
147+
148+
#[test]
149+
fn simple_newline_windows() {
150+
assert_eq!(parse("foo: bar\r\n"), ("foo".into(), "bar".into()));
151+
}
152+
}

0 commit comments

Comments
 (0)