From 4b0902c6482cbb2f9c8b2d03f90bf47da19d1e4e Mon Sep 17 00:00:00 2001 From: Patrick Marie Date: Wed, 5 Feb 2025 09:08:09 +0100 Subject: [PATCH] feat: write-tree --- src/kind.rs | 2 +- src/main.rs | 4 ++-- src/object.rs | 46 +++++++++++++++++++++------------------------- src/tree.rs | 20 ++++++++------------ 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/kind.rs b/src/kind.rs index b5c044a..992c804 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -26,7 +26,7 @@ impl Kind { Kind::Blob(false) => "100644", Kind::Blob(true) => "100755", Kind::Commit => "160000", - Kind::Tree => "040000", + Kind::Tree => "40000", Kind::Symlink => "120000", } } diff --git a/src/main.rs b/src/main.rs index ab76d9f..fedca65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ mod kind; mod object; mod tree; -use crate::object::{read_object, write_object}; +use crate::object::{read_object, write_blob}; use crate::tree::write_tree; #[derive(Parser)] @@ -76,7 +76,7 @@ fn main() -> Result<(), Error> { Ok(mut obj) => print!("{}", obj.string()?), Err(e) => eprintln!("Failed to read object: {}", e), }, - Command::WriteBlob { file } => match write_object(&repo_path, &file) { + Command::WriteBlob { file } => match write_blob(&repo_path, &file) { Ok(hash) => println!("{}", hex::encode(hash)), Err(e) => eprintln!("Failed to write object: {}", e), }, diff --git a/src/object.rs b/src/object.rs index eb8eb9f..d43bf0d 100644 --- a/src/object.rs +++ b/src/object.rs @@ -5,8 +5,7 @@ use sha1::{Digest, Sha1}; use std::io::Write; use std::{ fs::{create_dir, File}, - io::{copy, BufRead}, - os::unix::fs::MetadataExt, + io::BufRead, path::Path, }; @@ -80,44 +79,41 @@ fn is_path_in_repo(repo_path: &Path, file_path: &Path) -> Result { Ok(file_canonical.starts_with(repo_canonical)) } -pub fn hash_file(file: &Path) -> Result<[u8; 20]> { - let fd = File::open(file).context("opening file for sha1")?; - let mut buf_reader = std::io::BufReader::new(fd); - - let stat = file.metadata().context("stat on file")?; - - let header = format!("blob {}\0", stat.size()); - - let mut hasher = Sha1::new(); - hasher.update(header.as_bytes()); - copy(&mut buf_reader, &mut hasher)?; - - Ok(hasher.finalize().into()) -} - -pub fn write_object(repo_path: &Path, file: &Path) -> Result<[u8; 20]> { +pub fn write_blob(repo_path: &Path, file: &Path) -> Result<[u8; 20]> { if !file.exists() || !is_path_in_repo(repo_path, file)? { return Err(anyhow!("path does not exist")); } - let hash = hash_file(file)?; + let content = std::fs::read(file)?; + + Ok(write_object(repo_path, Kind::Blob(false), &content)?) +} + +pub fn write_object(repo_path: &Path, kind: Kind, content: &[u8]) -> Result<[u8; 20]> { + let mut hasher = Sha1::new(); + hasher.update(format!("{} {}\0", kind.string(), content.len()).as_bytes()); + hasher.update(content); + let hash = hasher.finalize().into(); let hash_str = hex::encode(hash); - let bytes_num = file.metadata()?.len(); - let mut buf_reader = std::io::BufReader::new(File::open(file)?); let target_dir = repo_path.join(".git").join("objects").join(&hash_str[..2]); - if !target_dir.exists() { create_dir(&target_dir).context("could not create directory in .git/objects")?; } let target_file = target_dir.join(&hash_str[2..]); + if target_file.exists() { + return Ok(hash); + } + let file_out_fd = File::create(target_file).context("could not open target file")?; let mut zlib_out = ZlibEncoder::new(file_out_fd, Compression::default()); - write!(zlib_out, "blob {}\0", bytes_num).context("could not write header")?; - - std::io::copy(&mut buf_reader, &mut zlib_out).context("could not compress or write file")?; + write!(zlib_out, "{} {}\0", kind.string(), content.len()).context("could not write header")?; + zlib_out.write(content)?; + zlib_out + .finish() + .context("could not compress or write file")?; Ok(hash) } diff --git a/src/tree.rs b/src/tree.rs index a9a498e..5be5906 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,12 +1,11 @@ use anyhow::{Context, Result}; -use sha1::{Digest, Sha1}; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use crate::kind::Kind; -use crate::object::{hash_file, TreeObject}; +use crate::object::{write_blob, write_object, TreeObject}; -pub fn write_tree(_repo_path: &PathBuf, path: &PathBuf) -> Result<[u8; 20]> { +pub fn write_tree(repo_path: &PathBuf, path: &PathBuf) -> Result<[u8; 20]> { let mut entries = Vec::new(); let files = std::fs::read_dir(path)?; @@ -20,10 +19,13 @@ pub fn write_tree(_repo_path: &PathBuf, path: &PathBuf) -> Result<[u8; 20]> { let kind; if file_type.is_dir() { - hash = write_tree(_repo_path, &file_path).context("could not write_tree of subtree")?; + hash = write_tree(repo_path, &file_path).context("could not write_tree of subtree")?; kind = Kind::Tree; } else { - hash = hash_file(&file_path)?; + hash = write_blob(repo_path, &file_path).context(format!( + "could not write object {:?}", + file_path.file_name() + ))?; kind = Kind::Blob(file_path.metadata()?.mode() & 0o111 != 0); } @@ -46,11 +48,5 @@ pub fn write_tree(_repo_path: &PathBuf, path: &PathBuf) -> Result<[u8; 20]> { out.extend_from_slice(&entry.hash); } - let header = format!("tree {}\0", out.len()); - - let mut hasher = Sha1::new(); - hasher.update(header); - hasher.update(out); - - Ok(hasher.finalize().into()) + write_object(repo_path, Kind::Tree, &out).context("Write") }