use std::collections::HashMap; use std::fs; use crate::{create_path_or_die, die}; use flate2::read::{ZlibDecoder, ZlibEncoder}; use std::fs::File; use std::io::{BufReader, Read, Write}; use std::path::PathBuf; use flate2::Compression; use sha1::{Digest, Sha1}; pub enum GitObjectType { Blob, Commit, Tag, Tree, Undefined, } pub struct GitObject { object_type: GitObjectType, size: usize, data: Vec, } impl GitObject { fn new() -> Self { Self { object_type: GitObjectType::Undefined, size: 0, data: Vec::new(), } } pub fn from_file(path: PathBuf) -> Self { if !path.is_file() { return Self::new(); } if let Ok(compressed_content) = File::open(path.clone()) { let sha = path.file_name().unwrap().to_str().unwrap(); let mut decoder = ZlibDecoder::new(compressed_content); let mut decompressed = Vec::new(); decoder.read_to_end(&mut decompressed).unwrap(); let mut iter = decompressed.iter().peekable(); let type_buffer: Vec = iter.by_ref() .take_while(|&&byte| byte != b' ') .cloned() .collect(); let size_buffer: Vec = iter.by_ref() .take_while(|&&byte| byte != 0) .cloned() .collect(); let data: Vec = iter.cloned().collect(); let object_type = match String::from_utf8_lossy(&type_buffer).to_string().as_str() { "blob" => { GitObjectType::Blob } "commit" => { GitObjectType::Commit } "tag" => { GitObjectType::Tag } "tree" => { GitObjectType::Tree } _ => { die!("Unknown type in object {}", sha); } }; let Ok(size) = String::from_utf8_lossy(&size_buffer).to_string().parse::() else { die!("Malformed object {}, unknown size", sha); }; if data.len() != size { die!("Malformed object {}, size doesn't match", sha); } Self { object_type, size, data, } } else { Self::new() } } pub fn write_to_file(&mut self, git_object_dir: PathBuf) { let object_type = match self.object_type { GitObjectType::Blob => { "blob " } GitObjectType::Commit => { "commit " } GitObjectType::Tag => { "tag " } GitObjectType::Tree => { "tree " } _ => { die!("Cannot write to object file, type unknown") } }; let mut bytes_to_write = Vec::from(object_type); let mut size_in_bytes = Vec::from(self.size.to_string()); bytes_to_write.append(&mut size_in_bytes); bytes_to_write.push(0); bytes_to_write.append(&mut self.data); let mut hasher = Sha1::new(); hasher.update(&bytes_to_write); let hash = hasher.finalize(); let sha = hash.iter() .map(|byte| format!("{:02x}", byte)) .collect::(); let buff = BufReader::new(bytes_to_write.as_slice()); let mut encoder = ZlibEncoder::new(buff, Compression::fast()); let mut compressed = Vec::new(); encoder.read_to_end(&mut compressed).unwrap(); let object_dir = git_object_dir.join(&sha[..2]); create_path_or_die!(dir: object_dir.clone(), "Failed to create object file, could not create directory"); let mut file = fs::OpenOptions::new() .create(true) .write(true) .open(object_dir.join(&sha[2..])).unwrap(); file.write_all(compressed.as_slice()).unwrap(); } } pub struct GitCommit { header: HashMap, message: String, } impl GitCommit { pub fn new() -> Self { Self { header: HashMap::new(), message: String::new(), } } fn deserialize(data: &Vec) -> Self { let string_rep = String::from_utf8(data.clone()); if string_rep.is_err() { die!("Failed to parse commit data"); } let string_rep = string_rep.unwrap(); let mut is_message = false; let mut header: HashMap = HashMap::new(); let mut message = String::new(); let mut last_key: Option = None; for line in string_rep.lines() { if is_message { message.push_str(format!("\n{}", line).as_str()); continue; } if line == "" { is_message = true; continue; } if line.starts_with(" ") && last_key.is_some() { let key = last_key.clone().unwrap(); match header.get(&key) { Some(val) => { let mut new_val = val.clone(); new_val.push_str(format!("\n{}", line).as_str()); header.insert(key, new_val); } None => { header.insert(key, line.to_string()); } } } else { let mut line_iter = line.splitn(2, ' '); let key = line_iter.next().unwrap(); let val = line_iter.next().unwrap(); header.insert(key.to_string(), val.to_string()); last_key = Some(key.to_string()); } } Self { header, message, } } }