Skip to content

Commit 70097c0

Browse files
Pierre ChevalierByron
Pierre Chevalier
authored andcommitted
Apply feedback from discussion
We now store a gix-date::Time in the owned `Signature` and a `&BStr` in `SignatureRef`. This means that `SignatureRef` round-trips to and from Git, but `Signature` doesn't necessarily round-trip with `SignatureRef`. It also means that we need a buffer of bytes when creating a `SignatureRef`.
1 parent b07f907 commit 70097c0

File tree

20 files changed

+177
-96
lines changed

20 files changed

+177
-96
lines changed

gitoxide-core/src/hours/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ where
8282
};
8383
let name = string_ref(author.name.as_ref());
8484
let email = string_ref(author.email.as_ref());
85-
let time = string_ref(author.time.as_ref());
85+
let mut buf = Vec::with_capacity(64);
86+
let time = string_ref(author.time.to_ref(&mut buf));
8687

8788
out.push((commit_idx, actor::SignatureRef { name, email, time }));
8889
}

gix-actor/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ pub struct Signature {
6767
/// Use [SignatureRef::trim()] or trim manually to be able to clean it up.
6868
pub email: BString,
6969
/// The time stamp at which the signature is performed.
70-
pub time: BString,
70+
pub time: date::Time,
7171
}
7272

7373
impl Signature {
7474
/// Seconds since unix epoch from the time
7575
pub fn seconds(&self) -> gix_date::SecondsSinceUnixEpoch {
76-
SignatureRef::from(self).seconds()
76+
self.time.seconds
7777
}
7878
}
7979
/// A immutable signature is created by an actor at a certain time.

gix-actor/src/signature/mod.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod _ref {
22
use bstr::ByteSlice;
3+
use gix_date::Time;
34
use winnow::{error::StrContext, prelude::*};
45

56
use crate::{signature::decode, IdentityRef, Signature, SignatureRef};
@@ -18,7 +19,7 @@ mod _ref {
1819
Signature {
1920
name: self.name.to_owned(),
2021
email: self.email.to_owned(),
21-
time: self.time.to_owned(),
22+
time: Time::from_bytes(self.time).expect("Time must be valid"),
2223
}
2324
}
2425

@@ -43,14 +44,15 @@ mod _ref {
4344

4445
mod convert {
4546
use crate::{Signature, SignatureRef};
47+
use gix_date::Time;
4648

4749
impl Signature {
4850
/// Borrow this instance as immutable
49-
pub fn to_ref(&self) -> SignatureRef<'_> {
51+
pub fn to_ref<'a>(&'a self, buf: &'a mut Vec<u8>) -> SignatureRef<'a> {
5052
SignatureRef {
5153
name: self.name.as_ref(),
5254
email: self.email.as_ref(),
53-
time: self.time.as_ref(),
55+
time: self.time.to_ref(buf),
5456
}
5557
}
5658
}
@@ -61,16 +63,10 @@ mod convert {
6163
Signature {
6264
name: name.to_owned(),
6365
email: email.to_owned(),
64-
time: time.to_owned(),
66+
time: Time::from_bytes(time).expect("Time must be valid"),
6567
}
6668
}
6769
}
68-
69-
impl<'a> From<&'a Signature> for SignatureRef<'a> {
70-
fn from(other: &'a Signature) -> SignatureRef<'a> {
71-
other.to_ref()
72-
}
73-
}
7470
}
7571

7672
pub(crate) mod write {
@@ -96,11 +92,12 @@ pub(crate) mod write {
9692
impl Signature {
9793
/// Serialize this instance to `out` in the git serialization format for actors.
9894
pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
99-
self.to_ref().write_to(out)
95+
let mut buf = Vec::<u8>::new();
96+
self.to_ref(&mut buf).write_to(out)
10097
}
10198
/// Computes the number of bytes necessary to serialize this signature
10299
pub fn size(&self) -> usize {
103-
self.to_ref().size()
100+
self.name.len() + 2 /* space <*/ + self.email.len() + 2 /* > space */ + self.time.size()
104101
}
105102
}
106103

gix-actor/tests/signature/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
mod write_to {
22
mod invalid {
33
use gix_actor::Signature;
4+
use gix_date::Time;
45

56
#[test]
67
fn name() {
78
let signature = Signature {
89
name: "invalid < middlename".into(),
910
email: "ok".into(),
10-
time: "0 0000".into(),
11+
time: Time::default(),
1112
};
1213
assert_eq!(
1314
format!("{:?}", signature.write_to(&mut Vec::new())),
@@ -20,7 +21,7 @@ mod write_to {
2021
let signature = Signature {
2122
name: "ok".into(),
2223
email: "server>.example.com".into(),
23-
time: "0 0000".into(),
24+
time: Time::default(),
2425
};
2526
assert_eq!(
2627
format!("{:?}", signature.write_to(&mut Vec::new())),
@@ -33,7 +34,7 @@ mod write_to {
3334
let signature = Signature {
3435
name: "hello\nnewline".into(),
3536
email: "[email protected]".into(),
36-
time: "0 0000".into(),
37+
time: Time::default(),
3738
};
3839
assert_eq!(
3940
format!("{:?}", signature.write_to(&mut Vec::new())),
@@ -59,7 +60,6 @@ fn round_trip() -> Result<(), Box<dyn std::error::Error>> {
5960
static DEFAULTS: &[&[u8]] = &[
6061
b"Sebastian Thiel <[email protected]> 1 -0030",
6162
b"Sebastian Thiel <[email protected]> -1500 -0030",
62-
b"Sebastian Thiel <[email protected]> 1313584730 +051800", // Seen in the wild
6363
".. ☺️Sebastian 王知明 Thiel🙌 .. <[email protected]> 1528473343 +0230".as_bytes(),
6464
b".. whitespace \t is explicitly allowed - unicode aware trimming must be done elsewhere <[email protected]> 1528473343 +0230"
6565
];
@@ -73,6 +73,16 @@ fn round_trip() -> Result<(), Box<dyn std::error::Error>> {
7373
Ok(())
7474
}
7575

76+
#[test]
77+
fn signature_ref_round_trips_with_seconds_in_offset() -> Result<(), Box<dyn std::error::Error>> {
78+
let input = b"Sebastian Thiel <[email protected]> 1313584730 +051800"; // Seen in the wild
79+
let signature: SignatureRef = gix_actor::SignatureRef::from_bytes::<()>(input).unwrap();
80+
let mut output = Vec::new();
81+
signature.write_to(&mut output)?;
82+
assert_eq!(output.as_bstr(), input.as_bstr());
83+
Ok(())
84+
}
85+
7686
#[test]
7787
fn parse_timestamp_with_trailing_digits() {
7888
let signature = gix_actor::SignatureRef::from_bytes::<()>(b"first last <[email protected]> 1312735823 +051800")

gix-date/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub mod time;
1717
pub mod parse;
1818
use bstr::{BStr, ByteSlice};
1919
pub use parse::function::parse;
20-
use parse::function::parse_raw;
20+
pub use parse::function::parse_raw;
2121
use parse::Error;
2222
use std::time::SystemTime;
2323

@@ -44,6 +44,11 @@ impl Time {
4444
let s = i.as_bstr().to_str().expect("Input must be ascii");
4545
parse_raw(s).ok_or(Error::InvalidDateString { input: s.into() })
4646
}
47+
/// Write time into buffer
48+
pub fn to_ref<'a>(&self, buf: &'a mut Vec<u8>) -> &'a BStr {
49+
self.write_to(buf).expect("write to memory cannot fail");
50+
buf.as_bstr()
51+
}
4752
}
4853

4954
/// The amount of seconds since unix epoch.

gix-date/src/parse.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ pub(crate) mod function {
6060
})
6161
}
6262

63-
pub(crate) fn parse_raw(input: &str) -> Option<Time> {
63+
#[allow(missing_docs)]
64+
pub fn parse_raw(input: &str) -> Option<Time> {
6465
let mut split = input.split_whitespace();
6566
let seconds: SecondsSinceUnixEpoch = split.next()?.parse().ok()?;
6667
let offset = split.next()?;

gix-mailmap/src/snapshot/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,17 @@ fn enriched_signature<'a>(
157157
(Some(new_email), Some(new_name)) => Signature {
158158
email: new_email.to_owned().into(),
159159
name: new_name.to_owned().into(),
160-
time: time.into(),
160+
time: gix_date::Time::from_bytes(time).expect("Time must be valid"),
161161
},
162162
(Some(new_email), None) => Signature {
163163
email: new_email.to_owned().into(),
164164
name: name.into(),
165-
time: time.into(),
165+
time: gix_date::Time::from_bytes(time).expect("Time must be valid"),
166166
},
167167
(None, Some(new_name)) => Signature {
168168
email: email.into(),
169169
name: new_name.to_owned().into(),
170-
time: time.into(),
170+
time: gix_date::Time::from_bytes(time).expect("Time must be valid"),
171171
},
172172
(None, None) => unreachable!("BUG: ResolvedSignatures don't exist here when nothing is set"),
173173
}

gix-mailmap/src/snapshot/signature.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ pub struct Signature<'a> {
1111
/// The possibly mapped email.
1212
pub email: Cow<'a, BStr>,
1313
/// The time stamp at which the signature is performed.
14-
pub time: Cow<'a, BStr>,
14+
pub time: gix_date::Time,
1515
}
1616

1717
impl<'a> From<Signature<'a>> for gix_actor::Signature {
1818
fn from(s: Signature<'a>) -> Self {
1919
gix_actor::Signature {
2020
name: s.name.into_owned(),
2121
email: s.email.into_owned(),
22-
time: s.time.into_owned(),
22+
time: s.time,
2323
}
2424
}
2525
}
@@ -29,7 +29,7 @@ impl<'a> From<gix_actor::SignatureRef<'a>> for Signature<'a> {
2929
Signature {
3030
name: s.name.into(),
3131
email: s.email.into(),
32-
time: s.time.into(),
32+
time: gix_date::Time::from_bytes(s.time).expect("Time must be valid"),
3333
}
3434
}
3535
}

gix-mailmap/tests/snapshot/mod.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,65 @@ use gix_testtools::fixture_bytes;
44
#[test]
55
fn try_resolve() {
66
let snapshot = Snapshot::from_bytes(&fixture_bytes("typical.txt"));
7+
let mut buf = Vec::with_capacity(64);
78
assert_eq!(
8-
snapshot.try_resolve(signature("Foo", "[email protected]").to_ref()),
9+
snapshot.try_resolve(signature("Foo", "[email protected]").to_ref(&mut buf)),
910
Some(signature("Joe R. Developer", "[email protected]")),
1011
"resolved signatures contain all original fields, and normalize the email as well to match the one that it was looked up with"
1112
);
13+
buf.clear();
1214
assert_eq!(
13-
snapshot.try_resolve(signature("Joe", "[email protected]").to_ref()),
15+
snapshot.try_resolve(signature("Joe", "[email protected]").to_ref(&mut buf)),
1416
Some(signature("Joe R. Developer", "[email protected]")),
1517
"name and email can be mapped specifically"
1618
);
1719

20+
buf.clear();
1821
assert_eq!(
19-
snapshot.try_resolve(signature("Jane", "jane@laptop.(none)").to_ref()),
22+
snapshot.try_resolve(signature("Jane", "jane@laptop.(none)").to_ref(&mut buf)),
2023
Some(signature("Jane Doe", "[email protected]")),
2124
"fix name and email by email"
2225
);
26+
buf.clear();
2327
assert_eq!(
24-
snapshot.try_resolve(signature("Jane", "jane@desktop.(none)").to_ref()),
28+
snapshot.try_resolve(signature("Jane", "jane@desktop.(none)").to_ref(&mut buf)),
2529
Some(signature("Jane Doe", "[email protected]")),
2630
"fix name and email by other email"
2731
);
2832

33+
buf.clear();
2934
assert_eq!(
30-
snapshot.try_resolve(signature("janE", "[email protected]").to_ref()),
35+
snapshot.try_resolve(signature("janE", "[email protected]").to_ref(&mut buf)),
3136
Some(signature("Jane Doe", "[email protected]")),
3237
"name and email can be mapped specifically, case insensitive matching of name"
3338
);
39+
buf.clear();
3440
assert_eq!(
35-
snapshot.resolve(signature("janE", "jane@ipad.(none)").to_ref()),
41+
snapshot.resolve(signature("janE", "jane@ipad.(none)").to_ref(&mut buf)),
3642
signature("janE", "[email protected]"),
3743
"an email can be mapped by name and email specifically, both match case-insensitively"
3844
);
3945

4046
let sig = signature("Jane", "[email protected]");
41-
assert_eq!(snapshot.try_resolve(sig.to_ref()), None, "unmatched email");
47+
buf.clear();
48+
assert_eq!(snapshot.try_resolve(sig.to_ref(&mut buf)), None, "unmatched email");
4249

50+
buf.clear();
4351
assert_eq!(
44-
snapshot.resolve(sig.to_ref()),
52+
snapshot.resolve(sig.to_ref(&mut buf)),
4553
sig,
4654
"resolution always works here, returning a copy of the original"
4755
);
4856

4957
let sig = signature("Jean", "[email protected]");
58+
buf.clear();
5059
assert_eq!(
51-
snapshot.try_resolve(sig.to_ref()),
60+
snapshot.try_resolve(sig.to_ref(&mut buf)),
5261
None,
5362
"matched email, unmatched name"
5463
);
55-
assert_eq!(snapshot.resolve(sig.to_ref()), sig);
64+
buf.clear();
65+
assert_eq!(snapshot.resolve(sig.to_ref(&mut buf)), sig);
5666

5767
assert_eq!(
5868
snapshot.entries(),
@@ -85,16 +95,19 @@ fn non_name_and_name_mappings_will_not_clash() {
8595
"old-email",
8696
),
8797
];
98+
let mut buf = Vec::with_capacity(64);
8899
for entries in [entries.clone().into_iter().rev().collect::<Vec<_>>(), entries] {
89100
let snapshot = Snapshot::new(entries);
90101

102+
buf.clear();
91103
assert_eq!(
92-
snapshot.try_resolve(signature("replace-by-email", "Old-Email").to_ref()),
104+
snapshot.try_resolve(signature("replace-by-email", "Old-Email").to_ref(&mut buf)),
93105
Some(signature("new-name", "old-email")),
94106
"it can match by email only, and the email is normalized"
95107
);
108+
buf.clear();
96109
assert_eq!(
97-
snapshot.try_resolve(signature("old-name", "Old-Email").to_ref()),
110+
snapshot.try_resolve(signature("old-name", "Old-Email").to_ref(&mut buf)),
98111
Some(signature("other-new-name", "other-new-email")),
99112
"it can match by email and name as well"
100113
);
@@ -117,26 +130,30 @@ fn non_name_and_name_mappings_will_not_clash() {
117130
#[test]
118131
fn overwrite_entries() {
119132
let snapshot = Snapshot::from_bytes(&fixture_bytes("overwrite.txt"));
133+
let mut buf = Vec::with_capacity(64);
120134
assert_eq!(
121-
snapshot.try_resolve(signature("does not matter", "old-a-email").to_ref()),
135+
snapshot.try_resolve(signature("does not matter", "old-a-email").to_ref(&mut buf)),
122136
Some(signature("A-overwritten", "old-a-email")),
123137
"email only by email"
124138
);
125139

140+
buf.clear();
126141
assert_eq!(
127-
snapshot.try_resolve(signature("to be replaced", "old-b-EMAIL").to_ref()),
142+
snapshot.try_resolve(signature("to be replaced", "old-b-EMAIL").to_ref(&mut buf)),
128143
Some(signature("B-overwritten", "new-b-email-overwritten")),
129144
"name and email by email"
130145
);
131146

147+
buf.clear();
132148
assert_eq!(
133-
snapshot.try_resolve(signature("old-c", "old-C-email").to_ref()),
149+
snapshot.try_resolve(signature("old-c", "old-C-email").to_ref(&mut buf)),
134150
Some(signature("C-overwritten", "new-c-email-overwritten")),
135151
"name and email by name and email"
136152
);
137153

154+
buf.clear();
138155
assert_eq!(
139-
snapshot.try_resolve(signature("unchanged", "old-d-email").to_ref()),
156+
snapshot.try_resolve(signature("unchanged", "old-d-email").to_ref(&mut buf)),
140157
Some(signature("unchanged", "new-d-email-overwritten")),
141158
"email by email"
142159
);
@@ -161,6 +178,6 @@ fn signature(name: &str, email: &str) -> gix_actor::Signature {
161178
gix_actor::Signature {
162179
name: name.into(),
163180
email: email.into(),
164-
time: b"42 +0800".into(),
181+
time: gix_date::parse_raw("42 +0800").unwrap(),
165182
}
166183
}

gix-object/src/commit/write.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ impl crate::WriteTo for Commit {
1111
for parent in &self.parents {
1212
encode::trusted_header_id(b"parent", parent, &mut out)?;
1313
}
14-
encode::trusted_header_signature(b"author", &self.author.to_ref(), &mut out)?;
15-
encode::trusted_header_signature(b"committer", &self.committer.to_ref(), &mut out)?;
14+
let mut buf = Vec::with_capacity(64);
15+
encode::trusted_header_signature(b"author", &self.author.to_ref(&mut buf), &mut out)?;
16+
buf.clear();
17+
encode::trusted_header_signature(b"committer", &self.committer.to_ref(&mut buf), &mut out)?;
1618
if let Some(encoding) = self.encoding.as_ref() {
1719
encode::header_field(b"encoding", encoding, &mut out)?;
1820
}

gix-object/src/tag/write.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ impl crate::WriteTo for Tag {
2626
encode::trusted_header_field(b"type", self.target_kind.as_bytes(), out)?;
2727
encode::header_field(b"tag", validated_name(self.name.as_ref())?, out)?;
2828
if let Some(tagger) = &self.tagger {
29-
encode::trusted_header_signature(b"tagger", &tagger.to_ref(), out)?;
29+
let mut buf = Vec::with_capacity(64);
30+
encode::trusted_header_signature(b"tagger", &tagger.to_ref(&mut buf), out)?;
3031
}
3132

3233
if !self.message.iter().all(|b| *b == b'\n') {

0 commit comments

Comments
 (0)