Skip to content

Commit df5ef8a

Browse files
committed
[pack-gen] the first green test for Tag iterators
1 parent 1898319 commit df5ef8a

File tree

7 files changed

+230
-30
lines changed

7 files changed

+230
-30
lines changed

git-object/src/immutable/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ pub use commit::{iter::Iter as CommitIter, Commit};
1515
pub mod object;
1616
pub use object::{Object, Signature};
1717

18-
mod tag;
19-
pub use tag::Tag;
18+
///
19+
pub mod tag;
20+
pub use tag::{iter::Iter as TagIter, Tag};
2021

2122
///
2223
pub mod tree;

git-object/src/immutable/tag.rs

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub struct Tag<'a> {
2727
/// The message describing this release.
2828
pub message: &'a BStr,
2929
/// The author of the tag.
30-
pub signature: Option<Signature<'a>>,
30+
pub tagger: Option<Signature<'a>>,
3131
/// A cryptographic signature over the entire content of the serialized tag object thus far.
3232
pub pgp_signature: Option<&'a BStr>,
3333
}
@@ -64,7 +64,7 @@ fn parse(i: &[u8]) -> IResult<&[u8], Tag<'_>, decode::Error> {
6464
name: tag_version.as_bstr(),
6565
target_kind: kind,
6666
message,
67-
signature,
67+
tagger: signature,
6868
pgp_signature,
6969
},
7070
))
@@ -117,3 +117,156 @@ fn parse_message(i: &[u8]) -> IResult<&[u8], (&BStr, Option<&BStr>), decode::Err
117117
),
118118
))
119119
}
120+
121+
///
122+
pub mod iter {
123+
use crate::{
124+
bstr::ByteSlice,
125+
immutable::{
126+
object::decode,
127+
parse::NL,
128+
tag::{parse, parse_message},
129+
Signature,
130+
},
131+
Kind,
132+
};
133+
use bstr::BStr;
134+
use git_hash::{oid, ObjectId};
135+
use nom::{
136+
bytes::complete::take_while1,
137+
character::is_alphabetic,
138+
combinator::{all_consuming, opt},
139+
};
140+
141+
enum State {
142+
Target,
143+
TargetKind,
144+
Name,
145+
Tagger,
146+
Message,
147+
}
148+
149+
impl Default for State {
150+
fn default() -> Self {
151+
State::Target
152+
}
153+
}
154+
155+
/// Like [`immutable::Tag`][super::Tag], but as `Iterator` to support entirely allocation free parsing.
156+
/// It's particularly useful to dereference only the target chain.
157+
pub struct Iter<'a> {
158+
data: &'a [u8],
159+
state: State,
160+
}
161+
162+
impl<'a> Iter<'a> {
163+
/// Create a tag iterator from data.
164+
pub fn from_bytes(data: &'a [u8]) -> Iter<'a> {
165+
Iter {
166+
data,
167+
state: State::default(),
168+
}
169+
}
170+
}
171+
172+
impl<'a> Iter<'a> {
173+
fn next_inner(i: &'a [u8], state: &mut State) -> Result<(&'a [u8], Token<'a>), decode::Error> {
174+
use State::*;
175+
Ok(match state {
176+
Target => {
177+
let (i, target) = parse::header_field(i, b"object", parse::hex_sha1)
178+
.map_err(decode::Error::context("object <40 lowercase hex char>"))?;
179+
*state = State::TargetKind;
180+
(
181+
i,
182+
Token::Target {
183+
id: ObjectId::from_hex(target).expect("parsing validation"),
184+
},
185+
)
186+
}
187+
TargetKind => {
188+
let (i, kind) = parse::header_field(i, b"type", take_while1(is_alphabetic))
189+
.map_err(decode::Error::context("type <object kind>"))?;
190+
let kind =
191+
crate::Kind::from_bytes(kind).map_err(|e| nom::Err::Error(decode::Error::ParseKindError(e)))?;
192+
*state = State::Name;
193+
(i, Token::TargetKind(kind))
194+
}
195+
Name => {
196+
let (i, tag_version) = parse::header_field(i, b"tag", take_while1(|b| b != NL[0]))
197+
.map_err(decode::Error::context("tag <version>"))?;
198+
*state = State::Tagger;
199+
(i, Token::Name(tag_version.as_bstr()))
200+
}
201+
Tagger => {
202+
let (i, signature) = opt(|i| parse::header_field(i, b"tagger", parse::signature))(i)
203+
.map_err(decode::Error::context("tagger <signature>"))?;
204+
*state = State::Message;
205+
(i, Token::Tagger(signature))
206+
}
207+
Message => {
208+
let (i, (message, pgp_signature)) = all_consuming(parse_message)(i)?;
209+
debug_assert!(
210+
i.is_empty(),
211+
"we should have consumed all data - otherwise iter may go forever"
212+
);
213+
return Ok((i, Token::Body { message, pgp_signature }));
214+
}
215+
})
216+
}
217+
}
218+
219+
impl<'a> Iterator for Iter<'a> {
220+
type Item = Result<Token<'a>, decode::Error>;
221+
222+
fn next(&mut self) -> Option<Self::Item> {
223+
if self.data.is_empty() {
224+
return None;
225+
}
226+
match Self::next_inner(self.data, &mut self.state) {
227+
Ok((data, token)) => {
228+
self.data = data;
229+
Some(Ok(token))
230+
}
231+
Err(err) => {
232+
self.data = &[];
233+
Some(Err(err))
234+
}
235+
}
236+
}
237+
}
238+
239+
/// A token returned by the [commit iterator][Iter].
240+
#[allow(missing_docs)]
241+
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
242+
pub enum Token<'a> {
243+
Target {
244+
id: ObjectId,
245+
},
246+
TargetKind(Kind),
247+
Name(&'a BStr),
248+
Tagger(Option<Signature<'a>>),
249+
Body {
250+
message: &'a BStr,
251+
pgp_signature: Option<&'a BStr>,
252+
},
253+
}
254+
255+
impl<'a> Token<'a> {
256+
/// Return the object id of this token if its a [Target][Token::Target].
257+
pub fn id(&self) -> Option<&oid> {
258+
match self {
259+
Token::Target { id } => Some(id.as_ref()),
260+
_ => None,
261+
}
262+
}
263+
264+
/// Return the owned object id of this token if its a [Target][Token::Target].
265+
pub fn into_id(self) -> Option<ObjectId> {
266+
match self {
267+
Token::Target { id } => Some(id),
268+
_ => None,
269+
}
270+
}
271+
}
272+
}

git-object/src/mutable/convert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl From<immutable::Tag<'_>> for mutable::Tag {
1818
name,
1919
target_kind,
2020
message,
21-
signature,
21+
tagger: signature,
2222
pgp_signature,
2323
} = other;
2424
mutable::Tag {

git-object/tests/immutable/commit.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ mod method {
137137
use pretty_assertions::assert_eq;
138138

139139
#[test]
140-
fn tree() -> Result<(), Box<dyn std::error::Error>> {
140+
fn tree() -> crate::Result {
141141
let fixture = fixture_bytes("commit", "unsigned.txt");
142142
let commit = Commit::from_bytes(&fixture)?;
143143
assert_eq!(commit.tree(), hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d"));
@@ -157,7 +157,7 @@ mod iter {
157157
use git_object::{bstr::ByteSlice, immutable::commit::iter::Token, immutable::CommitIter};
158158

159159
#[test]
160-
fn newline_right_after_signature_multiline_header() -> Result<(), Box<dyn std::error::Error>> {
160+
fn newline_right_after_signature_multiline_header() -> crate::Result {
161161
let data = fixture_bytes("commit", "signed-whitespace.txt");
162162
let tokens = CommitIter::from_bytes(&data).collect::<Result<Vec<_>, _>>()?;
163163
assert_eq!(tokens.len(), 7, "mainly a parsing exercise");
@@ -171,7 +171,7 @@ mod iter {
171171
}
172172

173173
#[test]
174-
fn signed_with_encoding() -> Result<(), Box<dyn std::error::Error>> {
174+
fn signed_with_encoding() -> crate::Result {
175175
assert_eq!(
176176
CommitIter::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt"))
177177
.collect::<Result<Vec<_>, _>>()?,
@@ -197,7 +197,7 @@ mod iter {
197197
}
198198

199199
#[test]
200-
fn whitespace() -> Result<(), Box<dyn std::error::Error>> {
200+
fn whitespace() -> crate::Result {
201201
assert_eq!(
202202
CommitIter::from_bytes(&fixture_bytes("commit", "whitespace.txt")).collect::<Result<Vec<_>, _>>()?,
203203
vec![
@@ -220,7 +220,7 @@ mod iter {
220220
}
221221

222222
#[test]
223-
fn unsigned() -> Result<(), Box<dyn std::error::Error>> {
223+
fn unsigned() -> crate::Result {
224224
assert_eq!(
225225
CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")).collect::<Result<Vec<_>, _>>()?,
226226
vec![
@@ -240,7 +240,7 @@ mod iter {
240240
}
241241

242242
#[test]
243-
fn signed_singleline() -> Result<(), Box<dyn std::error::Error>> {
243+
fn signed_singleline() -> crate::Result {
244244
assert_eq!(
245245
CommitIter::from_bytes(&fixture_bytes("commit", "signed-singleline.txt")).collect::<Result<Vec<_>, _>>()?,
246246
vec![
@@ -264,7 +264,7 @@ mod iter {
264264
}
265265

266266
#[test]
267-
fn error_handling() -> Result<(), Box<dyn std::error::Error>> {
267+
fn error_handling() -> crate::Result {
268268
let data = fixture_bytes("commit", "unsigned.txt");
269269
let iter = CommitIter::from_bytes(&data[..data.len() / 2]);
270270
let tokens = iter.collect::<Vec<_>>();
@@ -276,7 +276,7 @@ mod iter {
276276
}
277277

278278
#[test]
279-
fn mergetag() -> Result<(), Box<dyn std::error::Error>> {
279+
fn mergetag() -> crate::Result {
280280
assert_eq!(
281281
CommitIter::from_bytes(&fixture_bytes("commit", "mergetag.txt")).collect::<Result<Vec<_>, _>>()?,
282282
vec![
@@ -310,7 +310,7 @@ mod iter {
310310
use git_object::immutable::CommitIter;
311311

312312
#[test]
313-
fn tree_id() -> Result<(), Box<dyn std::error::Error>> {
313+
fn tree_id() -> crate::Result {
314314
assert_eq!(
315315
CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt")).tree_id(),
316316
Some(hex_to_id("1b2dfb4ac5e42080b682fc676e9738c94ce6d54d"))
@@ -325,7 +325,7 @@ mod iter {
325325
}
326326

327327
#[test]
328-
fn signatures() -> Result<(), Box<dyn std::error::Error>> {
328+
fn signatures() -> crate::Result {
329329
assert_eq!(
330330
CommitIter::from_bytes(&fixture_bytes("commit", "unsigned.txt"))
331331
.signatures()
@@ -346,7 +346,7 @@ mod from_bytes {
346346
use smallvec::SmallVec;
347347

348348
#[test]
349-
fn unsigned() -> Result<(), Box<dyn std::error::Error>> {
349+
fn unsigned() -> crate::Result {
350350
assert_eq!(
351351
Commit::from_bytes(&fixture_bytes("commit", "unsigned.txt"))?,
352352
Commit {
@@ -363,7 +363,7 @@ mod from_bytes {
363363
}
364364

365365
#[test]
366-
fn whitespace() -> Result<(), Box<dyn std::error::Error>> {
366+
fn whitespace() -> crate::Result {
367367
assert_eq!(
368368
Commit::from_bytes(&fixture_bytes("commit", "whitespace.txt"))?,
369369
Commit {
@@ -380,7 +380,7 @@ mod from_bytes {
380380
}
381381

382382
#[test]
383-
fn signed_singleline() -> Result<(), Box<dyn std::error::Error>> {
383+
fn signed_singleline() -> crate::Result {
384384
assert_eq!(
385385
Commit::from_bytes(&fixture_bytes("commit", "signed-singleline.txt"))?,
386386
Commit {
@@ -397,7 +397,7 @@ mod from_bytes {
397397
}
398398

399399
#[test]
400-
fn mergetag() -> Result<(), Box<dyn std::error::Error>> {
400+
fn mergetag() -> crate::Result {
401401
let fixture = fixture_bytes("commit", "mergetag.txt");
402402
let commit = Commit {
403403
tree: b"1c61918031bf2c7fab9e17dde3c52a6a9884fcb5".as_bstr(),
@@ -421,7 +421,7 @@ mod from_bytes {
421421
}
422422

423423
#[test]
424-
fn signed() -> Result<(), Box<dyn std::error::Error>> {
424+
fn signed() -> crate::Result {
425425
assert_eq!(
426426
Commit::from_bytes(&fixture_bytes("commit", "signed.txt"))?,
427427
Commit {
@@ -438,7 +438,7 @@ mod from_bytes {
438438
}
439439

440440
#[test]
441-
fn signed_with_encoding() -> Result<(), Box<dyn std::error::Error>> {
441+
fn signed_with_encoding() -> crate::Result {
442442
assert_eq!(
443443
Commit::from_bytes(&fixture_bytes("commit", "signed-with-encoding.txt"))?,
444444
Commit {
@@ -455,7 +455,7 @@ mod from_bytes {
455455
}
456456

457457
#[test]
458-
fn with_encoding() -> Result<(), Box<dyn std::error::Error>> {
458+
fn with_encoding() -> crate::Result {
459459
assert_eq!(
460460
Commit::from_bytes(&fixture_bytes("commit", "with-encoding.txt"))?,
461461
Commit {
@@ -472,7 +472,7 @@ mod from_bytes {
472472
}
473473

474474
#[test]
475-
fn merge() -> Result<(), Box<dyn std::error::Error>> {
475+
fn merge() -> crate::Result {
476476
assert_eq!(
477477
Commit::from_bytes(&fixture_bytes("commit", "merge.txt"))?,
478478
Commit {
@@ -504,7 +504,7 @@ iyBBl69jASy41Ug/BlFJbw4+ItkShpXwkJKuBBV/JExChmvbxYWaS7QnyYC9UO0=
504504
";
505505

506506
#[test]
507-
fn newline_right_after_signature_multiline_header() -> Result<(), Box<dyn std::error::Error>> {
507+
fn newline_right_after_signature_multiline_header() -> crate::Result {
508508
let fixture = fixture_bytes("commit", "signed-whitespace.txt");
509509
let commit = Commit::from_bytes(&fixture)?;
510510
let pgp_sig = OTHER_SIGNATURE.as_bstr();

0 commit comments

Comments
 (0)