Skip to content

Commit 2435710

Browse files
Pierre Chevalier pierrec@meta.compierrechevalier83
Pierre Chevalier [email protected]
authored andcommitted
[gix-object] Support empty tags with or without trailing NL
When representing an annotated tag with an empty commit message, we used to only support a tag ending with two newlines (one after the tagger line + one after the empty commit message). This was due to a misconception that annotated tags ending with a single NL shouldn't exist in the wild since there isn't an obvious way to create them from with `git`. This misconception shows up in the discussion thread for [issue 603](#603) It turns out that both encodings of empty annotated tags appear in the wild. We must be able to parse either and roundtrip for either. Before [PR 604](#604), we used to special case the empty tag msg case and not add a NL. To be able to represent `b""`, `b"\n"`, `b"\n\n"`..., we special case any `message` that is a pure sequence of `b'\n'` and actually parse the `b'\n'` into the tag's message. This allows us to calculate the correct size that matches the number of bytes that git would produce, as well as round-trip to and from the commit encoding. The existing tests (in particular `round_trip` for `empty.txt` and `empty_missing_nl.txt`) convince me that the logic is sound. Also, the size being `139` bytes for `empty_missing_nl.txt` and `140` bytes for `empty.txt` matches the output of `cat file | wc -l` for each file.
1 parent 6d5f774 commit 2435710

File tree

5 files changed

+40
-13
lines changed

5 files changed

+40
-13
lines changed

gix-object/src/tag/decode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ pub fn message<'a, E: ParserError<&'a [u8]>>(i: &mut &'a [u8]) -> ModalResult<(&
4040
const PGP_SIGNATURE_BEGIN: &[u8] = b"\n-----BEGIN PGP SIGNATURE-----";
4141
const PGP_SIGNATURE_END: &[u8] = b"-----END PGP SIGNATURE-----";
4242

43-
if i.is_empty() {
44-
return Ok((b"".as_bstr(), None));
43+
if i.iter().all(|b| *b == b'\n') {
44+
return i.map(|message: &[u8]| (message.as_bstr(), None)).parse_next(i);
4545
}
4646
delimited(
4747
NL,

gix-object/src/tag/write.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ impl crate::WriteTo for Tag {
2929
encode::trusted_header_signature(b"tagger", &tagger.to_ref(), out)?;
3030
}
3131

32-
out.write_all(NL)?;
33-
if !self.message.is_empty() {
34-
out.write_all(self.message.as_ref())?;
32+
if !self.message.iter().all(|b| *b == b'\n') {
33+
out.write_all(NL)?;
3534
}
35+
out.write_all(self.message.as_ref())?;
3636
if let Some(message) = &self.pgp_signature {
3737
out.write_all(NL)?;
3838
out.write_all(message.as_ref())?;
@@ -52,7 +52,7 @@ impl crate::WriteTo for Tag {
5252
.tagger
5353
.as_ref()
5454
.map_or(0, |t| b"tagger".len() + 1 /* space */ + t.size() + 1 /* nl */)
55-
+ 1 /* nl */ + self.message.len()
55+
+ if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
5656
+ self.pgp_signature.as_ref().map_or(0, |m| 1 /* nl */ + m.len())) as u64
5757
}
5858
}
@@ -66,10 +66,10 @@ impl crate::WriteTo for TagRef<'_> {
6666
encode::trusted_header_signature(b"tagger", tagger, &mut out)?;
6767
}
6868

69-
out.write_all(NL)?;
70-
if !self.message.is_empty() {
71-
out.write_all(self.message)?;
69+
if !self.message.iter().all(|b| *b == b'\n') {
70+
out.write_all(NL)?;
7271
}
72+
out.write_all(self.message)?;
7373
if let Some(message) = self.pgp_signature {
7474
out.write_all(NL)?;
7575
out.write_all(message)?;
@@ -89,7 +89,7 @@ impl crate::WriteTo for TagRef<'_> {
8989
.tagger
9090
.as_ref()
9191
.map_or(0, |t| b"tagger".len() + 1 /* space */ + t.size() + 1 /* nl */)
92-
+ 1 /* nl */ + self.message.len()
92+
+ if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
9393
+ self.pgp_signature.as_ref().map_or(0, |m| 1 /* nl */ + m.len())) as u64
9494
}
9595
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object 01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc
2+
type commit
3+
tag empty
4+
tagger Sebastian Thiel <[email protected]> 1592381636 +0800

gix-object/tests/object/encode/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ mod tag {
7474
round_trip!(
7575
gix_object::Tag,
7676
gix_object::TagRef,
77+
"tag/empty_missing_nl.txt",
7778
"tag/empty.txt",
7879
"tag/no-tagger.txt",
7980
"tag/whitespace.txt",

gix-object/tests/object/tag/mod.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ mod iter {
5454
Token::Name(b"empty".as_bstr()),
5555
Token::Tagger(tagger),
5656
Token::Body {
57-
message: b"".as_bstr(),
57+
message: b"\n".as_bstr(),
5858
pgp_signature: None,
5959
}
6060
]
@@ -155,7 +155,7 @@ fn invalid() {
155155
mod from_bytes {
156156
use gix_actor::SignatureRef;
157157
use gix_date::Time;
158-
use gix_object::{bstr::ByteSlice, Kind, TagRef};
158+
use gix_object::{bstr::ByteSlice, Kind, TagRef, WriteTo};
159159

160160
use crate::{fixture_name, signature, tag::tag_fixture, Sign};
161161

@@ -170,8 +170,29 @@ mod from_bytes {
170170

171171
#[test]
172172
fn empty() -> crate::Result {
173+
let fixture = fixture_name("tag", "empty.txt");
174+
let tag_ref = TagRef::from_bytes(&fixture)?;
173175
assert_eq!(
174-
TagRef::from_bytes(&fixture_name("tag", "empty.txt"))?,
176+
tag_ref,
177+
TagRef {
178+
target: b"01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc".as_bstr(),
179+
name: b"empty".as_bstr(),
180+
target_kind: Kind::Commit,
181+
message: b"\n".as_bstr(),
182+
tagger: Some(signature(1592381636)),
183+
pgp_signature: None
184+
}
185+
);
186+
assert_eq!(tag_ref.size(), 140);
187+
Ok(())
188+
}
189+
190+
#[test]
191+
fn empty_missing_nl() -> crate::Result {
192+
let fixture = fixture_name("tag", "empty_missing_nl.txt");
193+
let tag_ref = TagRef::from_bytes(&fixture)?;
194+
assert_eq!(
195+
tag_ref,
175196
TagRef {
176197
target: b"01dd4e2a978a9f5bd773dae6da7aa4a5ac1cdbbc".as_bstr(),
177198
name: b"empty".as_bstr(),
@@ -181,6 +202,7 @@ mod from_bytes {
181202
pgp_signature: None
182203
}
183204
);
205+
assert_eq!(tag_ref.size(), 139);
184206
Ok(())
185207
}
186208

0 commit comments

Comments
 (0)