git_internal/internal/object/
signature.rs1use std::{fmt::Display, str::FromStr};
13
14use bincode::{Decode, Encode};
15use bstr::ByteSlice;
16use chrono::Offset;
17use serde::{Deserialize, Serialize};
18
19use crate::errors::GitError;
20
21#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Decode, Encode)]
35pub enum SignatureType {
36 Author,
37 Committer,
38 Tagger,
39}
40
41impl Display for SignatureType {
42 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43 match self {
44 SignatureType::Author => write!(f, "author"),
45 SignatureType::Committer => write!(f, "committer"),
46 SignatureType::Tagger => write!(f, "tagger"),
47 }
48 }
49}
50impl FromStr for SignatureType {
51 type Err = GitError;
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 match s {
55 "author" => Ok(SignatureType::Author),
56 "committer" => Ok(SignatureType::Committer),
57 "tagger" => Ok(SignatureType::Tagger),
58 _ => Err(GitError::InvalidSignatureType(s.to_string())),
59 }
60 }
61}
62impl SignatureType {
63 pub fn from_data(data: Vec<u8>) -> Result<Self, GitError> {
65 let s = String::from_utf8(data.to_vec())
66 .map_err(|e| GitError::ConversionError(e.to_string()))?;
67 SignatureType::from_str(s.as_str())
68 }
69
70 pub fn to_bytes(&self) -> Vec<u8> {
72 match self {
73 SignatureType::Author => "author".to_string().into_bytes(),
74 SignatureType::Committer => "committer".to_string().into_bytes(),
75 SignatureType::Tagger => "tagger".to_string().into_bytes(),
76 }
77 }
78}
79
80#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Decode, Encode)]
82pub struct Signature {
83 pub signature_type: SignatureType,
84 pub name: String,
85 pub email: String,
86 pub timestamp: usize,
87 pub timezone: String,
88}
89
90impl Display for Signature {
91 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
92 writeln!(f, "{} <{}>", self.name, self.email).unwrap();
93 let date =
95 chrono::DateTime::<chrono::Utc>::from_timestamp(self.timestamp as i64, 0).unwrap();
96 writeln!(f, "Date: {} {}", date, self.timezone)
97 }
98}
99
100impl Signature {
101 pub fn from_data(data: Vec<u8>) -> Result<Signature, GitError> {
103 let mut sign = data;
105
106 let name_start = sign.find_byte(0x20).unwrap();
108
109 let signature_type = SignatureType::from_data(sign[..name_start].to_vec())?;
112
113 let (name, email) = {
114 let email_start = sign.find_byte(0x3C).unwrap();
115 let email_end = sign.find_byte(0x3E).unwrap();
116
117 unsafe {
118 (
119 sign[name_start + 1..email_start - 1]
120 .to_str_unchecked()
121 .to_string(),
122 sign[email_start + 1..email_end]
123 .to_str_unchecked()
124 .to_string(),
125 )
126 }
127 };
128
129 sign = sign[sign.find_byte(0x3E).unwrap() + 2..].to_vec();
131
132 let timestamp_split = sign.find_byte(0x20).unwrap();
134
135 let timestamp = unsafe {
138 sign[0..timestamp_split]
139 .to_str_unchecked()
140 .parse::<usize>()
141 .unwrap()
142 };
143
144 let timezone = unsafe { sign[timestamp_split + 1..].to_str_unchecked().to_string() };
147
148 Ok(Signature {
150 signature_type,
151 name,
152 email,
153 timestamp,
154 timezone,
155 })
156 }
157
158 pub fn to_data(&self) -> Result<Vec<u8>, GitError> {
160 let mut sign = Vec::new();
162
163 sign.extend_from_slice(&self.signature_type.to_bytes());
165 sign.extend_from_slice(&[0x20]);
166
167 sign.extend_from_slice(self.name.as_bytes());
169 sign.extend_from_slice(&[0x20]);
170
171 sign.extend_from_slice(format!("<{}>", self.email).as_bytes());
173 sign.extend_from_slice(&[0x20]);
174
175 sign.extend_from_slice(self.timestamp.to_string().as_bytes());
177 sign.extend_from_slice(&[0x20]);
178
179 sign.extend_from_slice(self.timezone.as_bytes());
181
182 Ok(sign)
184 }
185
186 pub fn new(sign_type: SignatureType, author: String, email: String) -> Signature {
188 let local_time = chrono::Local::now();
190
191 let offset = local_time.offset().fix().local_minus_utc();
193
194 let hours = offset / 60 / 60;
196
197 let minutes = offset / 60 % 60;
199
200 let offset_str = format!("{hours:+03}{minutes:02}");
202
203 Signature {
205 signature_type: sign_type, name: author, email, timestamp: chrono::Utc::now().timestamp() as usize, timezone: offset_str, }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use std::str::FromStr;
217
218 use chrono::DateTime;
219
220 use crate::internal::object::signature::{Signature, SignatureType};
221
222 #[test]
224 fn test_signature_type_from_str() {
225 assert_eq!(
226 SignatureType::from_str("author").unwrap(),
227 SignatureType::Author
228 );
229
230 assert_eq!(
231 SignatureType::from_str("committer").unwrap(),
232 SignatureType::Committer
233 );
234 }
235
236 #[test]
238 fn test_signature_type_from_data() {
239 assert_eq!(
240 SignatureType::from_data("author".to_string().into_bytes()).unwrap(),
241 SignatureType::Author
242 );
243
244 assert_eq!(
245 SignatureType::from_data("committer".to_string().into_bytes()).unwrap(),
246 SignatureType::Committer
247 );
248 }
249
250 #[test]
252 fn test_signature_type_to_bytes() {
253 assert_eq!(
254 SignatureType::Author.to_bytes(),
255 "author".to_string().into_bytes()
256 );
257
258 assert_eq!(
259 SignatureType::Committer.to_bytes(),
260 "committer".to_string().into_bytes()
261 );
262 }
263
264 #[test]
266 fn test_signature_new_from_data() {
267 let sign = Signature::from_data(
268 "author Quanyi Ma <eli@patch.sh> 1678101573 +0800"
269 .to_string()
270 .into_bytes(),
271 )
272 .unwrap();
273
274 assert_eq!(sign.signature_type, SignatureType::Author);
275 assert_eq!(sign.name, "Quanyi Ma");
276 assert_eq!(sign.email, "eli@patch.sh");
277 assert_eq!(sign.timestamp, 1678101573);
278 assert_eq!(sign.timezone, "+0800");
279 }
280
281 #[test]
283 fn test_signature_to_data() {
284 let sign = Signature::from_data(
285 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
286 .to_string()
287 .into_bytes(),
288 )
289 .unwrap();
290
291 let dest = sign.to_data().unwrap();
292
293 assert_eq!(
294 dest,
295 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
296 .to_string()
297 .into_bytes()
298 );
299 }
300
301 #[test]
303 fn test_signature_with_time() {
304 let sign = Signature::new(
305 SignatureType::Author,
306 "MEGA".to_owned(),
307 "admin@mega.com".to_owned(),
308 );
309 assert_eq!(sign.signature_type, SignatureType::Author);
310 assert_eq!(sign.name, "MEGA");
311 assert_eq!(sign.email, "admin@mega.com");
312 let naive_datetime = DateTime::from_timestamp(sign.timestamp as i64, 0).unwrap();
315 println!("Formatted DateTime: {}", naive_datetime.naive_local());
316 }
317}