git_internal/internal/object/
signature.rs1use std::{fmt::Display, str::FromStr};
13
14use bstr::ByteSlice;
15use chrono::Offset;
16
17use crate::errors::GitError;
18
19#[derive(
33 PartialEq,
34 Eq,
35 Debug,
36 Clone,
37 serde::Serialize,
38 serde::Deserialize,
39 rkyv::Archive,
40 rkyv::Serialize,
41 rkyv::Deserialize,
42)]
43pub enum SignatureType {
44 Author,
45 Committer,
46 Tagger,
47}
48
49impl Display for SignatureType {
50 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
51 match self {
52 SignatureType::Author => write!(f, "author"),
53 SignatureType::Committer => write!(f, "committer"),
54 SignatureType::Tagger => write!(f, "tagger"),
55 }
56 }
57}
58impl FromStr for SignatureType {
59 type Err = GitError;
60 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 match s {
63 "author" => Ok(SignatureType::Author),
64 "committer" => Ok(SignatureType::Committer),
65 "tagger" => Ok(SignatureType::Tagger),
66 _ => Err(GitError::InvalidSignatureType(s.to_string())),
67 }
68 }
69}
70impl SignatureType {
71 pub fn from_data(data: Vec<u8>) -> Result<Self, GitError> {
73 let s = String::from_utf8(data.to_vec())
74 .map_err(|e| GitError::ConversionError(e.to_string()))?;
75 SignatureType::from_str(s.as_str())
76 }
77
78 pub fn to_bytes(&self) -> Vec<u8> {
80 match self {
81 SignatureType::Author => "author".to_string().into_bytes(),
82 SignatureType::Committer => "committer".to_string().into_bytes(),
83 SignatureType::Tagger => "tagger".to_string().into_bytes(),
84 }
85 }
86}
87
88#[derive(
90 PartialEq,
91 Eq,
92 Debug,
93 Clone,
94 serde::Serialize,
95 serde::Deserialize,
96 rkyv::Archive,
97 rkyv::Serialize,
98 rkyv::Deserialize,
99)]
100pub struct Signature {
101 pub signature_type: SignatureType,
102 pub name: String,
103 pub email: String,
104 pub timestamp: usize,
105 pub timezone: String,
106}
107
108impl Display for Signature {
109 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110 writeln!(f, "{} <{}>", self.name, self.email).unwrap();
111 let date =
113 chrono::DateTime::<chrono::Utc>::from_timestamp(self.timestamp as i64, 0).unwrap();
114 writeln!(f, "Date: {} {}", date, self.timezone)
115 }
116}
117
118impl Signature {
119 pub fn from_data(data: Vec<u8>) -> Result<Signature, GitError> {
121 let mut sign = data;
123
124 let name_start = sign.find_byte(0x20).unwrap();
126
127 let signature_type = SignatureType::from_data(sign[..name_start].to_vec())?;
130
131 let (name, email) = {
132 let email_start = sign.find_byte(0x3C).unwrap();
133 let email_end = sign.find_byte(0x3E).unwrap();
134
135 unsafe {
136 (
137 sign[name_start + 1..email_start - 1]
138 .to_str_unchecked()
139 .to_string(),
140 sign[email_start + 1..email_end]
141 .to_str_unchecked()
142 .to_string(),
143 )
144 }
145 };
146
147 sign = sign[sign.find_byte(0x3E).unwrap() + 2..].to_vec();
149
150 let timestamp_split = sign.find_byte(0x20).unwrap();
152
153 let timestamp = unsafe {
156 sign[0..timestamp_split]
157 .to_str_unchecked()
158 .parse::<usize>()
159 .unwrap()
160 };
161
162 let timezone = unsafe { sign[timestamp_split + 1..].to_str_unchecked().to_string() };
165
166 Ok(Signature {
168 signature_type,
169 name,
170 email,
171 timestamp,
172 timezone,
173 })
174 }
175
176 pub fn to_data(&self) -> Result<Vec<u8>, GitError> {
178 let mut sign = Vec::new();
180
181 sign.extend_from_slice(&self.signature_type.to_bytes());
183 sign.extend_from_slice(&[0x20]);
184
185 sign.extend_from_slice(self.name.as_bytes());
187 sign.extend_from_slice(&[0x20]);
188
189 sign.extend_from_slice(format!("<{}>", self.email).as_bytes());
191 sign.extend_from_slice(&[0x20]);
192
193 sign.extend_from_slice(self.timestamp.to_string().as_bytes());
195 sign.extend_from_slice(&[0x20]);
196
197 sign.extend_from_slice(self.timezone.as_bytes());
199
200 Ok(sign)
202 }
203
204 pub fn new(sign_type: SignatureType, author: String, email: String) -> Signature {
206 let local_time = chrono::Local::now();
208
209 let offset = local_time.offset().fix().local_minus_utc();
211
212 let hours = offset / 60 / 60;
214
215 let minutes = offset / 60 % 60;
217
218 let offset_str = format!("{hours:+03}{minutes:02}");
220
221 Signature {
223 signature_type: sign_type, name: author, email, timestamp: chrono::Utc::now().timestamp() as usize, timezone: offset_str, }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use std::str::FromStr;
235
236 use chrono::DateTime;
237
238 use crate::internal::object::signature::{Signature, SignatureType};
239
240 #[test]
242 fn test_signature_type_from_str() {
243 assert_eq!(
244 SignatureType::from_str("author").unwrap(),
245 SignatureType::Author
246 );
247
248 assert_eq!(
249 SignatureType::from_str("committer").unwrap(),
250 SignatureType::Committer
251 );
252 }
253
254 #[test]
256 fn test_signature_type_from_data() {
257 assert_eq!(
258 SignatureType::from_data("author".to_string().into_bytes()).unwrap(),
259 SignatureType::Author
260 );
261
262 assert_eq!(
263 SignatureType::from_data("committer".to_string().into_bytes()).unwrap(),
264 SignatureType::Committer
265 );
266 }
267
268 #[test]
270 fn test_signature_type_to_bytes() {
271 assert_eq!(
272 SignatureType::Author.to_bytes(),
273 "author".to_string().into_bytes()
274 );
275
276 assert_eq!(
277 SignatureType::Committer.to_bytes(),
278 "committer".to_string().into_bytes()
279 );
280 }
281
282 #[test]
284 fn test_signature_new_from_data() {
285 let sign = Signature::from_data(
286 "author Quanyi Ma <eli@patch.sh> 1678101573 +0800"
287 .to_string()
288 .into_bytes(),
289 )
290 .unwrap();
291
292 assert_eq!(sign.signature_type, SignatureType::Author);
293 assert_eq!(sign.name, "Quanyi Ma");
294 assert_eq!(sign.email, "eli@patch.sh");
295 assert_eq!(sign.timestamp, 1678101573);
296 assert_eq!(sign.timezone, "+0800");
297 }
298
299 #[test]
301 fn test_signature_to_data() {
302 let sign = Signature::from_data(
303 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
304 .to_string()
305 .into_bytes(),
306 )
307 .unwrap();
308
309 let dest = sign.to_data().unwrap();
310
311 assert_eq!(
312 dest,
313 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
314 .to_string()
315 .into_bytes()
316 );
317 }
318
319 #[test]
321 fn test_signature_with_time() {
322 let sign = Signature::new(
323 SignatureType::Author,
324 "MEGA".to_owned(),
325 "admin@mega.com".to_owned(),
326 );
327 assert_eq!(sign.signature_type, SignatureType::Author);
328 assert_eq!(sign.name, "MEGA");
329 assert_eq!(sign.email, "admin@mega.com");
330 let naive_datetime = DateTime::from_timestamp(sign.timestamp as i64, 0).unwrap();
333 println!("Formatted DateTime: {}", naive_datetime.naive_local());
334 }
335}