Skip to content

Commit c423fbe

Browse files
committed
fix!: make signature access fallible and preserve raw actor headers
1 parent ddf21a1 commit c423fbe

File tree

17 files changed

+118
-132
lines changed

17 files changed

+118
-132
lines changed

examples/log.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ 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();
145+
let author = commit_ref.author()?;
146146
Ok(LogEntryInfo {
147147
commit_id: commit.id().to_hex().to_string(),
148148
parents: info.parent_ids().map(|id| id.shorten_or_id().to_string()).collect(),

gix-merge/src/commit/virtual_merge_base.rs

Lines changed: 6 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
)]
@@ -28,6 +30,8 @@ pub enum Error {
2830
}
2931

3032
pub(super) mod function {
33+
use std::convert::TryFrom;
34+
3135
use gix_object::FindExt;
3236

3337
use super::Error;
@@ -128,7 +132,8 @@ pub(super) mod function {
128132
tree_id: gix_hash::ObjectId,
129133
) -> Result<gix_hash::ObjectId, Error> {
130134
let mut buf = Vec::new();
131-
let mut commit: gix_object::Commit = objects.find_commit(&parent_a, &mut buf)?.into();
135+
let commit_ref = objects.find_commit(&parent_a, &mut buf)?;
136+
let mut commit = gix_object::Commit::try_from(commit_ref)?;
132137
commit.parents = vec![parent_a, parent_b].into();
133138
commit.tree = tree_id;
134139
objects.write(&commit).map_err(Error::WriteObject)

gix-object/src/commit/mod.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ impl<'a> CommitRef<'a> {
7474

7575
/// Access
7676
impl<'a> CommitRef<'a> {
77-
fn parse_signature(raw: &'a BStr) -> gix_actor::SignatureRef<'a> {
78-
gix_actor::SignatureRef::from_bytes::<()>(raw.as_ref()).expect("signatures were validated during parsing")
77+
fn parse_signature(raw: &'a BStr) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
78+
gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(raw.as_ref())
79+
.map_err(|err| crate::decode::Error::with_err(err, raw.as_ref()))
7980
}
8081

8182
/// Return the `tree` fields hash digest.
@@ -98,15 +99,15 @@ impl<'a> CommitRef<'a> {
9899
/// Return the author, with whitespace trimmed.
99100
///
100101
/// This is different from the `author` field which may contain whitespace.
101-
pub fn author(&self) -> gix_actor::SignatureRef<'a> {
102-
Self::parse_signature(self.author).trim()
102+
pub fn author(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
103+
Self::parse_signature(self.author).map(|signature| signature.trim())
103104
}
104105

105106
/// Return the committer, with whitespace trimmed.
106107
///
107108
/// This is different from the `committer` field which may contain whitespace.
108-
pub fn committer(&self) -> gix_actor::SignatureRef<'a> {
109-
Self::parse_signature(self.committer).trim()
109+
pub fn committer(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
110+
Self::parse_signature(self.committer).map(|signature| signature.trim())
110111
}
111112

112113
/// Returns a partially parsed message from which more information can be derived.
@@ -115,21 +116,21 @@ impl<'a> CommitRef<'a> {
115116
}
116117

117118
/// Returns the time at which this commit was created, or a default time if it could not be parsed.
118-
pub fn time(&self) -> gix_date::Time {
119-
Self::parse_signature(self.committer).time().unwrap_or_default()
119+
pub fn time(&self) -> Result<gix_date::Time, crate::decode::Error> {
120+
Self::parse_signature(self.committer).map(|signature| signature.time().unwrap_or_default())
120121
}
121122
}
122123

123124
/// Conversion
124125
impl CommitRef<'_> {
125126
/// Copy all fields of this instance into a fully owned commit, consuming this instance.
126-
pub fn into_owned(self) -> Commit {
127-
self.into()
127+
pub fn into_owned(self) -> Result<Commit, crate::decode::Error> {
128+
Commit::try_from(self)
128129
}
129130

130131
/// Copy all fields of this instance into a fully owned commit, internally cloning this instance.
131-
pub fn to_owned(self) -> Commit {
132-
self.clone().into()
132+
pub fn to_owned(self) -> Result<Commit, crate::decode::Error> {
133+
Commit::try_from(self.clone())
133134
}
134135
}
135136

gix-object/src/commit/write.rs

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,6 @@ use bstr::ByteSlice;
44

55
use crate::{encode, encode::NL, Commit, CommitRef, Kind};
66

7-
fn parse_signature(raw: &bstr::BStr) -> gix_actor::SignatureRef<'_> {
8-
gix_actor::SignatureRef::from_bytes::<()>(raw.as_ref()).expect("signatures were validated during parsing")
9-
}
10-
11-
fn signature_requires_raw(raw: &bstr::BStr) -> bool {
12-
let signature = parse_signature(raw);
13-
signature.name.find_byteset(b"<>\n").is_some() || signature.email.find_byteset(b"<>\n").is_some()
14-
}
15-
16-
fn signature_len(raw: &bstr::BStr) -> usize {
17-
if signature_requires_raw(raw) {
18-
raw.len()
19-
} else {
20-
parse_signature(raw).size()
21-
}
22-
}
23-
24-
fn write_signature(mut out: &mut dyn io::Write, field: &[u8], raw: &bstr::BStr) -> io::Result<()> {
25-
if signature_requires_raw(raw) {
26-
encode::trusted_header_field(field, raw.as_ref(), &mut out)
27-
} else {
28-
let signature = parse_signature(raw);
29-
encode::trusted_header_signature(field, &signature, &mut out)
30-
}
31-
}
32-
337
impl crate::WriteTo for Commit {
348
/// Serializes this instance to `out` in the git serialization format.
359
fn write_to(&self, mut out: &mut dyn io::Write) -> io::Result<()> {
@@ -84,8 +58,8 @@ impl crate::WriteTo for CommitRef<'_> {
8458
for parent in self.parents() {
8559
encode::trusted_header_id(b"parent", &parent, &mut out)?;
8660
}
87-
write_signature(&mut out, b"author", self.author)?;
88-
write_signature(&mut out, b"committer", self.committer)?;
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)?;
8963
if let Some(encoding) = self.encoding.as_ref() {
9064
encode::header_field(b"encoding", encoding, &mut out)?;
9165
}
@@ -104,8 +78,8 @@ impl crate::WriteTo for CommitRef<'_> {
10478
let hash_in_hex = self.tree().kind().len_in_hex();
10579
(b"tree".len() + 1 /* space */ + hash_in_hex + 1 /* nl */
10680
+ self.parents.iter().count() * (b"parent".len() + 1 /* space */ + hash_in_hex + 1 /* nl */)
107-
+ b"author".len() + 1 /* space */ + signature_len(self.author) + 1 /* nl */
108-
+ b"committer".len() + 1 /* space */ + signature_len(self.committer) + 1 /* nl */
81+
+ b"author".len() + 1 /* space */ + self.author.len() + 1 /* nl */
82+
+ b"committer".len() + 1 /* space */ + self.committer.len() + 1 /* nl */
10983
+ self
11084
.encoding
11185
.as_ref()

gix-object/src/object/convert.rs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
use std::convert::TryFrom;
2+
13
use crate::{tree, Blob, BlobRef, Commit, CommitRef, Object, ObjectRef, Tag, TagRef, Tree, TreeRef};
24

3-
impl From<TagRef<'_>> for Tag {
4-
fn from(other: TagRef<'_>) -> Tag {
5+
impl TryFrom<TagRef<'_>> for Tag {
6+
type Error = crate::decode::Error;
7+
8+
fn try_from(other: TagRef<'_>) -> Result<Tag, Self::Error> {
59
let TagRef {
610
target,
711
name,
@@ -10,38 +14,42 @@ impl From<TagRef<'_>> for Tag {
1014
tagger,
1115
pgp_signature,
1216
} = other;
13-
let tagger = tagger.map(|raw| {
14-
gix_actor::SignatureRef::from_bytes::<()>(raw.as_ref())
15-
.expect("signatures were validated during parsing")
16-
.into()
17-
});
18-
Tag {
17+
let tagger = tagger
18+
.map(|raw| {
19+
gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(raw.as_ref())
20+
.map_err(|err| crate::decode::Error::with_err(err, raw.as_ref()))
21+
})
22+
.transpose()?
23+
.map(Into::into);
24+
Ok(Tag {
1925
target: gix_hash::ObjectId::from_hex(target).expect("prior parser validation"),
2026
name: name.to_owned(),
2127
target_kind,
2228
message: message.to_owned(),
2329
tagger,
2430
pgp_signature: pgp_signature.map(ToOwned::to_owned),
25-
}
31+
})
2632
}
2733
}
2834

29-
impl From<CommitRef<'_>> for Commit {
30-
fn from(other: CommitRef<'_>) -> Commit {
35+
impl TryFrom<CommitRef<'_>> for Commit {
36+
type Error = crate::decode::Error;
37+
38+
fn try_from(other: CommitRef<'_>) -> Result<Commit, Self::Error> {
3139
let CommitRef {
3240
tree,
3341
parents,
34-
author,
35-
committer,
42+
author: author_raw,
43+
committer: committer_raw,
3644
encoding,
3745
message,
3846
extra_headers,
3947
} = other;
40-
let author = gix_actor::SignatureRef::from_bytes::<()>(author.as_ref())
41-
.expect("signatures were validated during parsing");
42-
let committer = gix_actor::SignatureRef::from_bytes::<()>(committer.as_ref())
43-
.expect("signatures were validated during parsing");
44-
Commit {
48+
let author = gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(author_raw.as_ref())
49+
.map_err(|err| crate::decode::Error::with_err(err, author_raw.as_ref()))?;
50+
let committer = gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(committer_raw.as_ref())
51+
.map_err(|err| crate::decode::Error::with_err(err, committer_raw.as_ref()))?;
52+
Ok(Commit {
4553
tree: gix_hash::ObjectId::from_hex(tree).expect("prior parser validation"),
4654
parents: parents
4755
.iter()
@@ -55,7 +63,7 @@ impl From<CommitRef<'_>> for Commit {
5563
.into_iter()
5664
.map(|(k, v)| (k.into(), v.into_owned()))
5765
.collect(),
58-
}
66+
})
5967
}
6068
}
6169

@@ -98,14 +106,16 @@ impl<'a> From<&'a tree::Entry> for tree::EntryRef<'a> {
98106
}
99107
}
100108

101-
impl From<ObjectRef<'_>> for Object {
102-
fn from(v: ObjectRef<'_>) -> Self {
103-
match v {
109+
impl TryFrom<ObjectRef<'_>> for Object {
110+
type Error = crate::decode::Error;
111+
112+
fn try_from(v: ObjectRef<'_>) -> Result<Self, Self::Error> {
113+
Ok(match v {
104114
ObjectRef::Tree(v) => Object::Tree(v.into()),
105115
ObjectRef::Blob(v) => Object::Blob(v.into()),
106-
ObjectRef::Commit(v) => Object::Commit(v.into()),
107-
ObjectRef::Tag(v) => Object::Tag(v.into()),
108-
}
116+
ObjectRef::Commit(v) => Object::Commit(Commit::try_from(v)?),
117+
ObjectRef::Tag(v) => Object::Tag(Tag::try_from(v)?),
118+
})
109119
}
110120
}
111121

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+
Object::try_from(self)
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+
Object::try_from(self.clone())
228228
}
229229
}
230230

gix-object/src/tag/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@ impl<'a> TagRef<'a> {
2525
}
2626

2727
/// Return the tagger, if present.
28-
pub fn tagger(&self) -> Option<gix_actor::SignatureRef<'a>> {
29-
self.tagger.map(|raw| {
30-
gix_actor::SignatureRef::from_bytes::<()>(raw.as_ref()).expect("tagger was validated during parsing")
31-
})
28+
pub fn tagger(&self) -> Result<Option<gix_actor::SignatureRef<'a>>, crate::decode::Error> {
29+
self.tagger
30+
.map(|raw| {
31+
gix_actor::SignatureRef::from_bytes::<crate::decode::ParseError>(raw.as_ref())
32+
.map_err(|err| crate::decode::Error::with_err(err, raw.as_ref()))
33+
.map(|signature| signature.trim())
34+
})
35+
.transpose()
3236
}
3337

3438
/// Copy all data into a fully-owned instance.
35-
pub fn into_owned(self) -> crate::Tag {
36-
self.into()
39+
pub fn into_owned(self) -> Result<crate::Tag, crate::decode::Error> {
40+
crate::Tag::try_from(self)
3741
}
3842
}

gix-object/src/tag/write.rs

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
use std::io;
22

3-
use bstr::{BStr, ByteSlice};
3+
use bstr::BStr;
44
use gix_date::parse::TimeBuf;
55

66
use crate::{encode, encode::NL, Kind, Tag, TagRef};
77

8-
fn parse_signature(raw: &BStr) -> gix_actor::SignatureRef<'_> {
9-
gix_actor::SignatureRef::from_bytes::<()>(raw.as_ref()).expect("signatures were validated during parsing")
10-
}
11-
12-
fn signature_requires_raw(raw: &BStr) -> bool {
13-
let signature = parse_signature(raw);
14-
signature.name.find_byteset(b"<>\n").is_some() || signature.email.find_byteset(b"<>\n").is_some()
15-
}
16-
17-
fn signature_len(raw: &BStr) -> usize {
18-
if signature_requires_raw(raw) {
19-
raw.len()
20-
} else {
21-
parse_signature(raw).size()
22-
}
23-
}
24-
258
/// An Error used in [`Tag::write_to()`][crate::WriteTo::write_to()].
269
#[derive(Debug, thiserror::Error)]
2710
#[allow(missing_docs)]
@@ -82,12 +65,7 @@ impl crate::WriteTo for TagRef<'_> {
8265
encode::trusted_header_field(b"type", self.target_kind.as_bytes(), &mut out)?;
8366
encode::header_field(b"tag", validated_name(self.name)?, &mut out)?;
8467
if let Some(tagger) = self.tagger {
85-
if signature_requires_raw(tagger) {
86-
encode::trusted_header_field(b"tagger", tagger.as_ref(), &mut out)?;
87-
} else {
88-
let signature = parse_signature(tagger);
89-
encode::trusted_header_signature(b"tagger", &signature, &mut out)?;
90-
}
68+
encode::trusted_header_field(b"tagger", tagger.as_ref(), &mut out)?;
9169
}
9270

9371
if !self.message.iter().all(|b| *b == b'\n') {
@@ -111,7 +89,7 @@ impl crate::WriteTo for TagRef<'_> {
11189
+ b"tag".len() + 1 /* space */ + self.name.len() + 1 /* nl */
11290
+ self
11391
.tagger
114-
.map_or(0, |raw| b"tagger".len() + 1 /* space */ + signature_len(raw) + 1 /* nl */)
92+
.map_or(0, |raw| b"tagger".len() + 1 /* space */ + raw.len() + 1 /* nl */)
11593
+ if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
11694
+ self.pgp_signature.as_ref().map_or(0, |m| 1 /* nl */ + m.len())) as u64
11795
}

gix-object/tests/object/commit/from_bytes.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ fn invalid_email_of_committer() {
4646
extra_headers: vec![]
4747
}
4848
);
49-
assert_eq!(commit.author(), actor);
50-
assert_eq!(commit.committer(), actor);
49+
assert_eq!(commit.author().unwrap(), actor);
50+
assert_eq!(commit.committer().unwrap(), actor);
5151
}
5252

5353
#[test]
@@ -123,8 +123,8 @@ fn mergetag() -> crate::Result {
123123
assert_eq!(commit, expected);
124124
assert_eq!(commit.extra_headers().find_all("mergetag").count(), 1);
125125
assert_eq!(commit.extra_headers().mergetags().count(), 1);
126-
assert_eq!(commit.author(), linus_signature("1591996221 -0700"));
127-
assert_eq!(commit.committer(), linus_signature("1591996221 -0700"));
126+
assert_eq!(commit.author().unwrap(), linus_signature("1591996221 -0700"));
127+
assert_eq!(commit.committer().unwrap(), linus_signature("1591996221 -0700"));
128128
Ok(())
129129
}
130130

@@ -244,8 +244,8 @@ Signed-off-by: Kim Altintop <kim@eagain.st>"
244244
extra_headers: vec![(b"gpgsig".as_bstr(), b"-----BEGIN PGP SIGNATURE-----\n\niHUEABYIAB0WIQSuZwcGWSQItmusNgR5URpSUCnwXQUCYT7xpAAKCRB5URpSUCnw\nXWB3AP9q323HlxnI8MyqszNOeYDwa7Y3yEZaUM2y/IRjz+z4YQEAq0yr1Syt3mrK\nOSFCqL2vDm3uStP+vF31f6FnzayhNg0=\n=Mhpp\n-----END PGP SIGNATURE-----\n".as_bstr().into())]
245245
}
246246
);
247-
assert_eq!(commit.author(), kim);
248-
assert_eq!(commit.committer(), kim);
247+
assert_eq!(commit.author().unwrap(), kim);
248+
assert_eq!(commit.committer().unwrap(), kim);
249249
let message = commit.message();
250250
assert_eq!(message.title, "test: use gitoxide for link-git-protocol tests");
251251
assert_eq!(
@@ -364,7 +364,7 @@ fn bogus_multi_gpgsig_header() -> crate::Result {
364364
fn author_method_returns_trimmed_signature() -> crate::Result {
365365
let backing = fixture_name("commit", "unsigned.txt");
366366
let commit = CommitRef::from_bytes(&backing)?;
367-
assert_eq!(commit.author(), signature("1592437401 +0800"));
368-
assert_eq!(commit.committer(), signature("1592437401 +0800"));
367+
assert_eq!(commit.author().unwrap(), signature("1592437401 +0800"));
368+
assert_eq!(commit.committer().unwrap(), signature("1592437401 +0800"));
369369
Ok(())
370370
}

0 commit comments

Comments
 (0)