Skip to content

Commit 605ef20

Browse files
committed
Move all object related code into own crate…
…for minimal dependencies
1 parent 85958f1 commit 605ef20

File tree

11 files changed

+441
-0
lines changed

11 files changed

+441
-0
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ incremental = false
2323

2424
[workspace]
2525
members = [
26+
"git-object",
2627
"git-odb",
2728
"git-core",
2829
"demos"

git-object/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "git-object"
3+
version = "0.1.0"
4+
authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
5+
publish = false
6+
edition = "2018"
7+
8+
[lib]
9+
doctest = false
10+
11+
[dependencies]
12+
quick-error = "1.2.3"
13+
hex = "0.3.2"
14+
btoi = "0.4.2"
15+
bstr = { version = "0.2.13", default-features = false, features = ["std"] }
16+
nom = { version = "6.0.0-alpha1", default-features = false }
17+
18+
[dev-dependencies]
19+
pretty_assertions = "0.6.1"

git-object/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod parsed;
2+
mod types;
3+
4+
pub use types::*;

git-object/src/parsed/mod.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use quick_error::quick_error;
2+
use crate::{Time};
3+
use std::str;
4+
use bstr::BStr;
5+
6+
mod tag;
7+
mod util;
8+
9+
pub use tag::Tag;
10+
11+
#[cfg(test)]
12+
mod tests;
13+
14+
quick_error! {
15+
#[derive(Debug)]
16+
pub enum Error {
17+
ParseIntegerError(msg: &'static str, kind: Vec<u8>, err: btoi::ParseIntegerError) {
18+
display("{}: {:?}", msg, std::str::from_utf8(&kind))
19+
cause(err)
20+
}
21+
ParseError(msg: &'static str, kind: Vec<u8>) {
22+
display("{}: {:?}", msg, std::str::from_utf8(&kind))
23+
}
24+
ObjectKind(err: crate::Error) {
25+
from()
26+
cause(err)
27+
}
28+
}
29+
}
30+
31+
#[derive(PartialEq, Eq, Debug, Hash)]
32+
pub enum Object<'data> {
33+
Tag(Tag<'data>),
34+
}
35+
36+
impl<'data> Object<'data> {
37+
pub fn kind(&self) -> crate::Kind {
38+
match self {
39+
Object::Tag(_) => crate::Kind::Tag,
40+
}
41+
}
42+
}
43+
44+
#[derive(PartialEq, Eq, Debug, Hash)]
45+
pub struct Signature<'data> {
46+
pub name: &'data BStr,
47+
pub email: &'data BStr,
48+
pub time: Time,
49+
}

git-object/src/parsed/tag.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use super::{
2+
util::{parse_timezone_offset, split2_at_space},
3+
Error,
4+
};
5+
use crate::{parsed::Signature, Time};
6+
use bstr::{BStr, ByteSlice};
7+
use btoi::btoi;
8+
use hex::FromHex;
9+
10+
#[derive(PartialEq, Eq, Debug, Hash)]
11+
pub struct Tag<'data> {
12+
pub target: &'data BStr,
13+
pub name: &'data BStr,
14+
pub target_kind: crate::Kind,
15+
pub message: Option<&'data BStr>,
16+
pub signature: Signature<'data>,
17+
pub pgp_signature: Option<&'data BStr>,
18+
}
19+
20+
fn parse_signature(d: &[u8]) -> Result<Signature, Error> {
21+
const ONE_SPACE: usize = 1;
22+
let email_begin = d
23+
.iter()
24+
.position(|&b| b == b'<')
25+
.ok_or_else(|| {
26+
Error::ParseError(
27+
"Could not find beginning of email marked by '<'",
28+
d.to_owned(),
29+
)
30+
})
31+
.and_then(|pos| {
32+
if pos == 0 {
33+
Err(Error::ParseError(
34+
"Email found in place of author name",
35+
d.to_owned(),
36+
))
37+
} else {
38+
Ok(pos)
39+
}
40+
})?;
41+
let email_end = email_begin
42+
+ d.iter()
43+
.skip(email_begin)
44+
.position(|&b| b == b'>')
45+
.ok_or_else(|| {
46+
Error::ParseError("Could not find end of email marked by '>'", d.to_owned())
47+
})
48+
.and_then(|pos| {
49+
if pos >= d.len() - 1 - ONE_SPACE {
50+
Err(Error::ParseError(
51+
"There is no time after email",
52+
d.to_owned(),
53+
))
54+
} else {
55+
Ok(pos)
56+
}
57+
})?;
58+
let (time_in_seconds, tzofz) = split2_at_space(&d[email_end + ONE_SPACE + 1..], |_, _| true)?;
59+
let (offset, sign) = parse_timezone_offset(tzofz)?;
60+
61+
Ok(Signature {
62+
name: (&d[..email_begin - ONE_SPACE]).as_bstr(),
63+
email: (&d[email_begin + 1..email_end]).as_bstr(),
64+
time: Time {
65+
time: btoi::<u32>(time_in_seconds).map_err(|e| {
66+
Error::ParseIntegerError("Could parse to seconds", time_in_seconds.to_owned(), e)
67+
})?,
68+
offset,
69+
sign,
70+
},
71+
})
72+
}
73+
74+
fn parse_message<'data>(
75+
d: &'data [u8],
76+
mut lines: impl Iterator<Item = &'data [u8]>,
77+
) -> Result<(Option<&'data BStr>, Option<&'data BStr>), Error> {
78+
const PGP_SIGNATURE_BEGIN: &[u8] = b"-----BEGIN PGP SIGNATURE-----";
79+
const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----";
80+
81+
Ok(match lines.next() {
82+
Some(l) if l.is_empty() => {
83+
let msg_begin = 0; // TODO: use nom to parse this or do it without needing nightly
84+
if msg_begin >= d.len() {
85+
return Err(Error::ParseError(
86+
"Message separator was not followed by message",
87+
d.to_owned(),
88+
));
89+
}
90+
let mut msg_end = d.len();
91+
let mut pgp_signature = None;
92+
if let Some(_pgp_begin_line) = lines.find(|l| l.starts_with(PGP_SIGNATURE_BEGIN)) {
93+
match lines.find(|l| l.starts_with(PGP_SIGNATURE_END)) {
94+
None => {
95+
return Err(Error::ParseError(
96+
"Didn't find end of signature marker",
97+
d.to_owned(),
98+
))
99+
}
100+
Some(_) => {
101+
msg_end = d.len(); // TODO: use nom to parse this or do it without needing nightly
102+
pgp_signature = Some((&d[msg_end..]).as_bstr())
103+
}
104+
}
105+
}
106+
(Some((&d[msg_begin..msg_end]).as_bstr()), pgp_signature)
107+
}
108+
Some(l) => {
109+
return Err(Error::ParseError(
110+
"Expected empty newline to separate message",
111+
l.to_owned(),
112+
))
113+
}
114+
None => (None, None),
115+
})
116+
}
117+
118+
impl<'data> Tag<'data> {
119+
pub fn target(&self) -> crate::Id {
120+
<[u8; 20]>::from_hex(self.target).expect("prior validation")
121+
}
122+
pub fn from_bytes(d: &'data [u8]) -> Result<Tag<'data>, Error> {
123+
let mut lines = d.split(|&b| b == b'\n');
124+
let (target, target_kind, name, signature) =
125+
match (lines.next(), lines.next(), lines.next(), lines.next()) {
126+
(Some(target), Some(kind), Some(name), Some(tagger)) => {
127+
let (_, target) = split2_at_space(target, |f, v| {
128+
f == b"object" && v.len() == 40 && <[u8; 20]>::from_hex(v).is_ok()
129+
})?;
130+
let kind = split2_at_space(kind, |f, _v| f == b"type")
131+
.and_then(|(_, kind)| crate::Kind::from_bytes(kind).map_err(Into::into))?;
132+
let (_, name) = split2_at_space(name, |f, _v| f == b"tag")?;
133+
let (_, rest) = split2_at_space(tagger, |f, _v| f == b"tagger")?;
134+
(target.as_bstr(), kind, name.as_bstr(), parse_signature(rest)?)
135+
}
136+
_ => {
137+
return Err(Error::ParseError(
138+
"Expected four lines: target, type, tag and tagger",
139+
d.to_owned(),
140+
))
141+
}
142+
};
143+
144+
let (message, pgp_signature) = parse_message(d, &mut lines)?;
145+
146+
Ok(Tag {
147+
target,
148+
name,
149+
target_kind,
150+
message,
151+
signature,
152+
pgp_signature,
153+
})
154+
}
155+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
object ffa700b4aca13b80cb6b98a078e7c96804f8e0ec
2+
type commit
3+
tag 1.0.0
4+
tagger Sebastian Thiel <byronimo@gmail.com> 1528473343 +0230
5+
6+
for the signature
7+
-----BEGIN PGP SIGNATURE-----
8+
Comment: GPGTools - https://gpgtools.org
9+
10+
iQIzBAABCgAdFiEEw7xSvXbiwjusbsBqZl+Z+p2ZlmwFAlsapyYACgkQZl+Z+p2Z
11+
lmy6Ug/+KzvzqiNpzz1bMVVAzp8NCbiEO3QGYPyeQc521lBwpaTrRYR+oHJY15r3
12+
OdL5WDysTpjN8N5FNyfmvzkuPdTkK3JlYmO7VRjdA2xu/B6vIZLaOfAowFrhMvKo
13+
8eoqwGcAP3rC5TuWEgzq2qhbjS4JXFLd4NLjWEFqT2Y2UKm+g8TeGOsa/0pF4Nq5
14+
xeW4qCYR0WcQLFedbpkKHxag2GfaXKvzNNJdqYhVQssNa6BeSmsfDvlWYNe617wV
15+
NvsR/zJT0wHb5SSH+h6QmwA7LQIQF//83Vc3aF7kv9D54r3ibXW5TjZ3WoeTUZO7
16+
kefkzJ12EYDCFLPhHvXPog518nO8Ot46dX+okrF0/B4N3RFTvjKr7VAGTzv2D/Dg
17+
DrD531S2F71b+JIRh641eeP7bjWFQi3tWLtrEOtjjsKPJfYRMKpYFnAO4UUJ6Rck
18+
Z5fFXEUCO8d5WT56jzKDjmVoY01lA87O1YsP/J+zQAlc9v1k6jqeQ53LZNgTN+ue
19+
5fJuSPT3T43pSOD1VQSr3aZ2Anc4Qu7K8uX9lkpxF9Sc0tDbeCosFLZMWNVp6m+e
20+
cjHJZXWmV4CcRfmLsXzU8s2cR9A0DBvOxhPD1TlKC2JhBFXigjuL9U4Rbq9tdegB
21+
2n8f2douw6624Tn/6Lm4a7AoxmU+CMiYagDxDL3RuZ8CAfh3bn0=
22+
=aIns
23+
-----END PGP SIGNATURE-----

git-object/src/parsed/tests/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use super::*;
2+
use hex::FromHex;
3+
use std::path::PathBuf;
4+
5+
pub fn bin(hex: &str) -> [u8; 20] {
6+
<[u8; 20]>::from_hex(hex).unwrap()
7+
}
8+
9+
pub fn fixture(path: &str) -> PathBuf {
10+
PathBuf::from("src/parsed/tests/fixtures").join(path)
11+
}
12+
13+
fn fixture_bytes(path: &str) -> Vec<u8> {
14+
std::fs::read(fixture(path)).unwrap()
15+
}
16+
17+
mod tag;

git-object/src/parsed/tests/tag.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use super::fixture_bytes;
2+
use bstr::ByteSlice;
3+
use crate::{
4+
Time,
5+
Sign,
6+
parsed::{
7+
self,
8+
tests::bin
9+
},
10+
Kind,
11+
};
12+
use pretty_assertions::{assert_eq};
13+
14+
#[test]
15+
fn tag_parse() {
16+
let fixture = fixture_bytes("tag.txt");
17+
let actual = parsed::Tag::from_bytes(&fixture).unwrap();
18+
assert_eq!(actual, tag_fixture(9000));
19+
assert_eq!(
20+
actual.target(),
21+
bin("ffa700b4aca13b80cb6b98a078e7c96804f8e0ec")
22+
);
23+
}
24+
25+
fn tag_fixture(offset: i32) -> parsed::Tag<'static> {
26+
parsed::Tag {
27+
target: b"ffa700b4aca13b80cb6b98a078e7c96804f8e0ec".as_bstr(),
28+
name: b"1.0.0".as_bstr(),
29+
target_kind: Kind::Commit,
30+
message: Some(b"for the signature\n".as_bstr()),
31+
pgp_signature: Some(
32+
b"-----BEGIN PGP SIGNATURE-----
33+
Comment: GPGTools - https://gpgtools.org
34+
35+
iQIzBAABCgAdFiEEw7xSvXbiwjusbsBqZl+Z+p2ZlmwFAlsapyYACgkQZl+Z+p2Z
36+
lmy6Ug/+KzvzqiNpzz1bMVVAzp8NCbiEO3QGYPyeQc521lBwpaTrRYR+oHJY15r3
37+
OdL5WDysTpjN8N5FNyfmvzkuPdTkK3JlYmO7VRjdA2xu/B6vIZLaOfAowFrhMvKo
38+
8eoqwGcAP3rC5TuWEgzq2qhbjS4JXFLd4NLjWEFqT2Y2UKm+g8TeGOsa/0pF4Nq5
39+
xeW4qCYR0WcQLFedbpkKHxag2GfaXKvzNNJdqYhVQssNa6BeSmsfDvlWYNe617wV
40+
NvsR/zJT0wHb5SSH+h6QmwA7LQIQF//83Vc3aF7kv9D54r3ibXW5TjZ3WoeTUZO7
41+
kefkzJ12EYDCFLPhHvXPog518nO8Ot46dX+okrF0/B4N3RFTvjKr7VAGTzv2D/Dg
42+
DrD531S2F71b+JIRh641eeP7bjWFQi3tWLtrEOtjjsKPJfYRMKpYFnAO4UUJ6Rck
43+
Z5fFXEUCO8d5WT56jzKDjmVoY01lA87O1YsP/J+zQAlc9v1k6jqeQ53LZNgTN+ue
44+
5fJuSPT3T43pSOD1VQSr3aZ2Anc4Qu7K8uX9lkpxF9Sc0tDbeCosFLZMWNVp6m+e
45+
cjHJZXWmV4CcRfmLsXzU8s2cR9A0DBvOxhPD1TlKC2JhBFXigjuL9U4Rbq9tdegB
46+
2n8f2douw6624Tn/6Lm4a7AoxmU+CMiYagDxDL3RuZ8CAfh3bn0=
47+
=aIns
48+
-----END PGP SIGNATURE-----
49+
"
50+
.as_bstr(),
51+
),
52+
signature: parsed::Signature {
53+
name: b"Sebastian Thiel".as_bstr(),
54+
email: b"byronimo@gmail.com".as_bstr(),
55+
time: Time {
56+
time: 1528473343,
57+
offset,
58+
sign: Sign::Plus,
59+
},
60+
},
61+
}
62+
}

0 commit comments

Comments
 (0)