Skip to content

Commit f471ac5

Browse files
authored
Merge pull request #2253 from Pingasmaster/raw-email-attempt-fix
fix!: store raw commit/tag actor headers and parse lazily (see #2177)
2 parents 93dd630 + 6f7b23a commit f471ac5

File tree

24 files changed

+243
-181
lines changed

24 files changed

+243
-181
lines changed

examples/log.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,16 @@ fn run(args: Args) -> anyhow::Result<()> {
142142
let info = info?;
143143
let commit = info.object()?;
144144
let commit_ref = commit.decode()?;
145+
let author = commit_ref.author()?;
145146
Ok(LogEntryInfo {
146147
commit_id: commit.id().to_hex().to_string(),
147148
parents: info.parent_ids().map(|id| id.shorten_or_id().to_string()).collect(),
148149
author: {
149150
let mut buf = Vec::new();
150-
commit_ref.author.actor().write_to(&mut buf)?;
151+
author.actor().write_to(&mut buf)?;
151152
buf.into()
152153
},
153-
time: commit_ref.author.time()?.format_or_unix(format::DEFAULT),
154+
time: author.time()?.format_or_unix(format::DEFAULT),
154155
message: commit_ref.message.to_owned(),
155156
})
156157
}),

gix-merge/src/commit/virtual_merge_base.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub enum Error {
1919
MergeTree(#[from] crate::tree::Error),
2020
#[error("Failed to write tree for merged merge-base or virtual commit")]
2121
WriteObject(gix_object::write::Error),
22+
#[error("Failed to decode a commit needed to build a virtual merge-base")]
23+
DecodeCommit(#[from] gix_object::decode::Error),
2224
#[error(
2325
"Conflicts occurred when trying to resolve multiple merge-bases by merging them. This is most certainly a bug."
2426
)]
@@ -128,7 +130,8 @@ pub(super) mod function {
128130
tree_id: gix_hash::ObjectId,
129131
) -> Result<gix_hash::ObjectId, Error> {
130132
let mut buf = Vec::new();
131-
let mut commit: gix_object::Commit = objects.find_commit(&parent_a, &mut buf)?.into();
133+
let commit_ref = objects.find_commit(&parent_a, &mut buf)?;
134+
let mut commit = commit_ref.to_owned()?;
132135
commit.parents = vec![parent_a, parent_b].into();
133136
commit.tree = tree_id;
134137
objects.write(&commit).map_err(Error::WriteObject)

gix-object/src/commit/decode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ pub fn commit<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
4040
.context(StrContext::Expected(
4141
"zero or more 'parent <40 lowercase hex char>'".into(),
4242
)),
43-
(|i: &mut _| parse::header_field(i, b"author", parse::signature))
43+
(|i: &mut _| parse::header_field(i, b"author", parse::signature_and_consumed).map(|(_signature, raw)| raw))
4444
.context(StrContext::Expected("author <signature>".into())),
45-
(|i: &mut _| parse::header_field(i, b"committer", parse::signature))
45+
(|i: &mut _| parse::header_field(i, b"committer", parse::signature_and_consumed).map(|(_signature, raw)| raw))
4646
.context(StrContext::Expected("committer <signature>".into())),
4747
opt(|i: &mut _| parse::header_field(i, b"encoding", take_till(0.., NL)))
4848
.context(StrContext::Expected("encoding <encoding>".into())),

gix-object/src/commit/mod.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::ops::Range;
33
use bstr::{BStr, BString, ByteSlice};
44
use winnow::prelude::*;
55

6+
use crate::parse::parse_signature;
67
use crate::{Commit, CommitRef, TagRef};
78

89
/// The well-known field name for gpg signatures.
@@ -94,15 +95,15 @@ impl<'a> CommitRef<'a> {
9495
/// Return the author, with whitespace trimmed.
9596
///
9697
/// This is different from the `author` field which may contain whitespace.
97-
pub fn author(&self) -> gix_actor::SignatureRef<'a> {
98-
self.author.trim()
98+
pub fn author(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
99+
parse_signature(self.author).map(|signature| signature.trim())
99100
}
100101

101102
/// Return the committer, with whitespace trimmed.
102103
///
103104
/// This is different from the `committer` field which may contain whitespace.
104-
pub fn committer(&self) -> gix_actor::SignatureRef<'a> {
105-
self.committer.trim()
105+
pub fn committer(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
106+
parse_signature(self.committer).map(|signature| signature.trim())
106107
}
107108

108109
/// Returns a partially parsed message from which more information can be derived.
@@ -111,21 +112,21 @@ impl<'a> CommitRef<'a> {
111112
}
112113

113114
/// Returns the time at which this commit was created, or a default time if it could not be parsed.
114-
pub fn time(&self) -> gix_date::Time {
115-
self.committer.time.parse().unwrap_or_default()
115+
pub fn time(&self) -> Result<gix_date::Time, crate::decode::Error> {
116+
parse_signature(self.committer).map(|signature| signature.time().unwrap_or_default())
116117
}
117118
}
118119

119120
/// Conversion
120121
impl CommitRef<'_> {
121122
/// Copy all fields of this instance into a fully owned commit, consuming this instance.
122-
pub fn into_owned(self) -> Commit {
123-
self.into()
123+
pub fn into_owned(self) -> Result<Commit, crate::decode::Error> {
124+
self.try_into()
124125
}
125126

126127
/// Copy all fields of this instance into a fully owned commit, internally cloning this instance.
127-
pub fn to_owned(self) -> Commit {
128-
self.clone().into()
128+
pub fn to_owned(self) -> Result<Commit, crate::decode::Error> {
129+
self.try_into()
129130
}
130131
}
131132

gix-object/src/commit/write.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ impl crate::WriteTo for CommitRef<'_> {
5858
for parent in self.parents() {
5959
encode::trusted_header_id(b"parent", &parent, &mut out)?;
6060
}
61-
encode::trusted_header_signature(b"author", &self.author, &mut out)?;
62-
encode::trusted_header_signature(b"committer", &self.committer, &mut out)?;
61+
encode::trusted_header_field(b"author", self.author.as_ref(), &mut out)?;
62+
encode::trusted_header_field(b"committer", self.committer.as_ref(), &mut out)?;
6363
if let Some(encoding) = self.encoding.as_ref() {
6464
encode::header_field(b"encoding", encoding, &mut out)?;
6565
}
@@ -78,8 +78,8 @@ impl crate::WriteTo for CommitRef<'_> {
7878
let hash_in_hex = self.tree().kind().len_in_hex();
7979
(b"tree".len() + 1 /* space */ + hash_in_hex + 1 /* nl */
8080
+ self.parents.iter().count() * (b"parent".len() + 1 /* space */ + hash_in_hex + 1 /* nl */)
81-
+ b"author".len() + 1 /* space */ + self.author.size() + 1 /* nl */
82-
+ b"committer".len() + 1 /* space */ + self.committer.size() + 1 /* nl */
81+
+ b"author".len() + 1 /* space */ + self.author.len() + 1 /* nl */
82+
+ b"committer".len() + 1 /* space */ + self.committer.len() + 1 /* nl */
8383
+ self
8484
.encoding
8585
.as_ref()

gix-object/src/lib.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,16 @@ pub struct CommitRef<'a> {
8888
pub tree: &'a BStr,
8989
/// HEX hash of each parent commit. Empty for first commit in repository.
9090
pub parents: SmallVec<[&'a BStr; 1]>,
91-
/// Who wrote this commit. Name and email might contain whitespace and are not trimmed to ensure round-tripping.
91+
/// The raw author header value as encountered during parsing.
9292
///
93-
/// Use the [`author()`](CommitRef::author()) method to received a trimmed version of it.
94-
pub author: gix_actor::SignatureRef<'a>,
95-
/// Who committed this commit. Name and email might contain whitespace and are not trimmed to ensure round-tripping.
96-
///
97-
/// Use the [`committer()`](CommitRef::committer()) method to received a trimmed version of it.
93+
/// Use the [`author()`](CommitRef::author()) method to obtain a parsed version of it.
94+
#[cfg_attr(feature = "serde", serde(borrow))]
95+
pub author: &'a BStr,
96+
/// The raw committer header value as encountered during parsing.
9897
///
99-
/// This may be different from the `author` in case the author couldn't write to the repository themselves and
100-
/// is commonly encountered with contributed commits.
101-
pub committer: gix_actor::SignatureRef<'a>,
98+
/// Use the [`committer()`](CommitRef::committer()) method to obtain a parsed version of it.
99+
#[cfg_attr(feature = "serde", serde(borrow))]
100+
pub committer: &'a BStr,
102101
/// The name of the message encoding, otherwise [UTF-8 should be assumed](https://github.com/git/git/blob/e67fbf927dfdf13d0b21dc6ea15dc3c7ef448ea0/commit.c#L1493:L1493).
103102
pub encoding: Option<&'a BStr>,
104103
/// The commit message documenting the change.
@@ -150,8 +149,11 @@ pub struct TagRef<'a> {
150149
pub target_kind: Kind,
151150
/// The name of the tag, e.g. "v1.0".
152151
pub name: &'a BStr,
153-
/// The author of the tag.
154-
pub tagger: Option<gix_actor::SignatureRef<'a>>,
152+
/// The raw tagger header value as encountered during parsing.
153+
///
154+
/// Use the [`tagger()`](TagRef::tagger()) method to obtain a parsed version of it.
155+
#[cfg_attr(feature = "serde", serde(borrow))]
156+
pub tagger: Option<&'a BStr>,
155157
/// The message describing this release.
156158
pub message: &'a BStr,
157159
/// A cryptographic signature over the entire content of the serialized tag object thus far.

gix-object/src/object/convert.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
1+
use std::convert::TryFrom;
2+
3+
use crate::parse::parse_signature;
14
use crate::{tree, Blob, BlobRef, Commit, CommitRef, Object, ObjectRef, Tag, TagRef, Tree, TreeRef};
25

3-
impl From<TagRef<'_>> for Tag {
4-
fn from(other: TagRef<'_>) -> Tag {
6+
impl TryFrom<TagRef<'_>> for Tag {
7+
type Error = crate::decode::Error;
8+
9+
fn try_from(other: TagRef<'_>) -> Result<Tag, Self::Error> {
510
let TagRef {
611
target,
712
name,
813
target_kind,
914
message,
10-
tagger: signature,
15+
tagger,
1116
pgp_signature,
1217
} = other;
13-
Tag {
18+
let untrimmed_tagger = tagger.map(parse_signature).transpose()?.map(Into::into);
19+
Ok(Tag {
1420
target: gix_hash::ObjectId::from_hex(target).expect("prior parser validation"),
1521
name: name.to_owned(),
1622
target_kind,
1723
message: message.to_owned(),
18-
tagger: signature.map(Into::into),
24+
tagger: untrimmed_tagger,
1925
pgp_signature: pgp_signature.map(ToOwned::to_owned),
20-
}
26+
})
2127
}
2228
}
2329

24-
impl From<CommitRef<'_>> for Commit {
25-
fn from(other: CommitRef<'_>) -> Commit {
30+
impl TryFrom<CommitRef<'_>> for Commit {
31+
type Error = crate::decode::Error;
32+
33+
fn try_from(other: CommitRef<'_>) -> Result<Commit, Self::Error> {
2634
let CommitRef {
2735
tree,
2836
parents,
@@ -32,21 +40,24 @@ impl From<CommitRef<'_>> for Commit {
3240
message,
3341
extra_headers,
3442
} = other;
35-
Commit {
43+
44+
let untrimmed_author = parse_signature(author)?;
45+
let untrimmed_committer = parse_signature(committer)?;
46+
Ok(Commit {
3647
tree: gix_hash::ObjectId::from_hex(tree).expect("prior parser validation"),
3748
parents: parents
3849
.iter()
3950
.map(|parent| gix_hash::ObjectId::from_hex(parent).expect("prior parser validation"))
4051
.collect(),
41-
author: author.into(),
42-
committer: committer.into(),
52+
author: untrimmed_author.into(),
53+
committer: untrimmed_committer.into(),
4354
encoding: encoding.map(ToOwned::to_owned),
4455
message: message.to_owned(),
4556
extra_headers: extra_headers
4657
.into_iter()
4758
.map(|(k, v)| (k.into(), v.into_owned()))
4859
.collect(),
49-
}
60+
})
5061
}
5162
}
5263

@@ -89,14 +100,16 @@ impl<'a> From<&'a tree::Entry> for tree::EntryRef<'a> {
89100
}
90101
}
91102

92-
impl From<ObjectRef<'_>> for Object {
93-
fn from(v: ObjectRef<'_>) -> Self {
94-
match v {
103+
impl TryFrom<ObjectRef<'_>> for Object {
104+
type Error = crate::decode::Error;
105+
106+
fn try_from(v: ObjectRef<'_>) -> Result<Self, Self::Error> {
107+
Ok(match v {
95108
ObjectRef::Tree(v) => Object::Tree(v.into()),
96109
ObjectRef::Blob(v) => Object::Blob(v.into()),
97-
ObjectRef::Commit(v) => Object::Commit(v.into()),
98-
ObjectRef::Tag(v) => Object::Tag(v.into()),
99-
}
110+
ObjectRef::Commit(v) => Object::Commit(v.try_into()?),
111+
ObjectRef::Tag(v) => Object::Tag(v.try_into()?),
112+
})
100113
}
101114
}
102115

gix-object/src/object/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,15 @@ impl<'a> ObjectRef<'a> {
216216
/// Convert the immutable object into a mutable version, consuming the source in the process.
217217
///
218218
/// Note that this is an expensive operation.
219-
pub fn into_owned(self) -> Object {
220-
self.into()
219+
pub fn into_owned(self) -> Result<Object, crate::decode::Error> {
220+
self.try_into()
221221
}
222222

223223
/// Convert this immutable object into its mutable counterpart.
224224
///
225225
/// Note that this is an expensive operation.
226-
pub fn to_owned(&self) -> Object {
227-
self.clone().into()
226+
pub fn to_owned(&self) -> Result<Object, crate::decode::Error> {
227+
self.clone().try_into()
228228
}
229229
}
230230

gix-object/src/parse.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,18 @@ pub(crate) fn signature<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrC
7171
) -> ModalResult<gix_actor::SignatureRef<'a>, E> {
7272
gix_actor::signature::decode(i)
7373
}
74+
75+
pub(crate) fn signature_and_consumed<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
76+
i: &mut &'a [u8],
77+
) -> ModalResult<(gix_actor::SignatureRef<'a>, &'a BStr), E> {
78+
let original = *i;
79+
gix_actor::signature::decode(i).map(|signature| {
80+
let consumed = original.len() - i.len();
81+
(signature, original[..consumed].as_bstr())
82+
})
83+
}
84+
85+
pub(crate) fn parse_signature(raw: &BStr) -> Result<gix_actor::SignatureRef<'_>, crate::decode::Error> {
86+
gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(raw.as_ref())
87+
.map_err(|err| crate::decode::Error::with_err(err, raw.as_ref()))
88+
}

gix-object/src/tag/decode.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,18 @@ pub fn git_tag<'a, E: ParserError<&'a [u8]> + AddContext<&'a [u8], StrContext>>(
1919
.context(StrContext::Expected("type <object kind>".into())),
2020
(|i: &mut _| parse::header_field(i, b"tag", take_while(1.., |b| b != NL[0])))
2121
.context(StrContext::Expected("tag <version>".into())),
22-
opt(|i: &mut _| parse::header_field(i, b"tagger", parse::signature))
22+
opt(|i: &mut _| parse::header_field(i, b"tagger", parse::signature_and_consumed).map(|(_signature, raw)| raw))
2323
.context(StrContext::Expected("tagger <signature>".into())),
2424
terminated(message, eof),
2525
)
26-
.map(
27-
|(target, kind, tag_version, signature, (message, pgp_signature))| TagRef {
28-
target,
29-
name: tag_version.as_bstr(),
30-
target_kind: kind,
31-
message,
32-
tagger: signature,
33-
pgp_signature,
34-
},
35-
)
26+
.map(|(target, kind, tag_version, tagger, (message, pgp_signature))| TagRef {
27+
target,
28+
name: tag_version.as_bstr(),
29+
target_kind: kind,
30+
message,
31+
tagger,
32+
pgp_signature,
33+
})
3634
.parse_next(i)
3735
}
3836

0 commit comments

Comments
 (0)