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)]
81pub struct Signature {
82 pub signature_type: SignatureType,
83 pub name: String,
84 pub email: String,
85 pub timestamp: usize,
86 pub timezone: String,
87}
88
89impl Display for Signature {
90 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
91 writeln!(f, "{} <{}>", self.name, self.email).unwrap();
92 let date =
94 chrono::DateTime::<chrono::Utc>::from_timestamp(self.timestamp as i64, 0).unwrap();
95 writeln!(f, "Date: {} {}", date, self.timezone)
96 }
97}
98
99impl Signature {
100 pub fn from_data(data: Vec<u8>) -> Result<Signature, GitError> {
101 let mut sign = data;
103
104 let name_start = sign.find_byte(0x20).unwrap();
106
107 let signature_type = SignatureType::from_data(sign[..name_start].to_vec())?;
110
111 let (name, email) = {
112 let email_start = sign.find_byte(0x3C).unwrap();
113 let email_end = sign.find_byte(0x3E).unwrap();
114
115 unsafe {
116 (
117 sign[name_start + 1..email_start - 1]
118 .to_str_unchecked()
119 .to_string(),
120 sign[email_start + 1..email_end]
121 .to_str_unchecked()
122 .to_string(),
123 )
124 }
125 };
126
127 sign = sign[sign.find_byte(0x3E).unwrap() + 2..].to_vec();
129
130 let timestamp_split = sign.find_byte(0x20).unwrap();
132
133 let timestamp = unsafe {
136 sign[0..timestamp_split]
137 .to_str_unchecked()
138 .parse::<usize>()
139 .unwrap()
140 };
141
142 let timezone = unsafe { sign[timestamp_split + 1..].to_str_unchecked().to_string() };
145
146 Ok(Signature {
148 signature_type,
149 name,
150 email,
151 timestamp,
152 timezone,
153 })
154 }
155
156 pub fn to_data(&self) -> Result<Vec<u8>, GitError> {
157 let mut sign = Vec::new();
159
160 sign.extend_from_slice(&self.signature_type.to_bytes());
162 sign.extend_from_slice(&[0x20]);
163
164 sign.extend_from_slice(self.name.as_bytes());
166 sign.extend_from_slice(&[0x20]);
167
168 sign.extend_from_slice(format!("<{}>", self.email).as_bytes());
170 sign.extend_from_slice(&[0x20]);
171
172 sign.extend_from_slice(self.timestamp.to_string().as_bytes());
174 sign.extend_from_slice(&[0x20]);
175
176 sign.extend_from_slice(self.timezone.as_bytes());
178
179 Ok(sign)
181 }
182
183 pub fn new(sign_type: SignatureType, author: String, email: String) -> Signature {
185 let local_time = chrono::Local::now();
187
188 let offset = local_time.offset().fix().local_minus_utc();
190
191 let hours = offset / 60 / 60;
193
194 let minutes = offset / 60 % 60;
196
197 let offset_str = format!("{hours:+03}{minutes:02}");
199
200 Signature {
202 signature_type: sign_type, name: author, email, timestamp: chrono::Utc::now().timestamp() as usize, timezone: offset_str, }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use std::str::FromStr;
214
215 use chrono::DateTime;
216
217 use crate::internal::object::signature::{Signature, SignatureType};
218
219 #[test]
220 fn test_signature_type_from_str() {
221 assert_eq!(
222 SignatureType::from_str("author").unwrap(),
223 SignatureType::Author
224 );
225
226 assert_eq!(
227 SignatureType::from_str("committer").unwrap(),
228 SignatureType::Committer
229 );
230 }
231
232 #[test]
233 fn test_signature_type_from_data() {
234 assert_eq!(
235 SignatureType::from_data("author".to_string().into_bytes()).unwrap(),
236 SignatureType::Author
237 );
238
239 assert_eq!(
240 SignatureType::from_data("committer".to_string().into_bytes()).unwrap(),
241 SignatureType::Committer
242 );
243 }
244
245 #[test]
246 fn test_signature_type_to_bytes() {
247 assert_eq!(
248 SignatureType::Author.to_bytes(),
249 "author".to_string().into_bytes()
250 );
251
252 assert_eq!(
253 SignatureType::Committer.to_bytes(),
254 "committer".to_string().into_bytes()
255 );
256 }
257
258 #[test]
259 fn test_signature_new_from_data() {
260 let sign = Signature::from_data(
261 "author Quanyi Ma <eli@patch.sh> 1678101573 +0800"
262 .to_string()
263 .into_bytes(),
264 )
265 .unwrap();
266
267 assert_eq!(sign.signature_type, SignatureType::Author);
268 assert_eq!(sign.name, "Quanyi Ma");
269 assert_eq!(sign.email, "eli@patch.sh");
270 assert_eq!(sign.timestamp, 1678101573);
271 assert_eq!(sign.timezone, "+0800");
272 }
273
274 #[test]
275 fn test_signature_to_data() {
276 let sign = Signature::from_data(
277 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
278 .to_string()
279 .into_bytes(),
280 )
281 .unwrap();
282
283 let dest = sign.to_data().unwrap();
284
285 assert_eq!(
286 dest,
287 "committer Quanyi Ma <eli@patch.sh> 1678101573 +0800"
288 .to_string()
289 .into_bytes()
290 );
291 }
292
293 #[test]
295 fn test_signature_with_time() {
296 let sign = Signature::new(
297 SignatureType::Author,
298 "MEGA".to_owned(),
299 "admin@mega.com".to_owned(),
300 );
301 assert_eq!(sign.signature_type, SignatureType::Author);
302 assert_eq!(sign.name, "MEGA");
303 assert_eq!(sign.email, "admin@mega.com");
304 let naive_datetime = DateTime::from_timestamp(sign.timestamp as i64, 0).unwrap();
307 println!("Formatted DateTime: {}", naive_datetime.naive_local());
308 }
309}