diff --git a/src/kind.rs b/src/kind.rs index 992c804..6b6df6e 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -1,3 +1,5 @@ +use std::fmt; + use anyhow::{anyhow, Result}; #[derive(Debug)] @@ -30,13 +32,16 @@ impl Kind { Kind::Symlink => "120000", } } +} - pub fn string(&self) -> &str { - match self { +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let kind = match self { Kind::Blob(_) => "blob", Kind::Commit => "commit", Kind::Tree => "tree", Kind::Symlink => "symlink", - } + }; + write!(f, "{}", kind) } } diff --git a/src/main.rs b/src/main.rs index b34d644..b4c759c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use anyhow::{Error, Result}; +use object::hash_object; use repository::default_init_path; use std::path::PathBuf; @@ -72,6 +73,11 @@ enum Command { /// The pack index file to dump pack_id: String, }, + /// Hash an object + HashObject { + /// The object to hash + file: PathBuf, + }, } fn main() -> Result<(), Error> { @@ -128,6 +134,10 @@ fn main() -> Result<(), Error> { Ok(_) => (), Err(e) => eprintln!("Failed to dump pack index file: {}", e), }, + Command::HashObject { file } => match hash_object(&file) { + Ok(hash) => println!("{}", hex::encode(hash)), + Err(e) => eprintln!("Failed to hash object: {}", e), + }, } Ok(()) diff --git a/src/object.rs b/src/object.rs index 548e81a..51f2936 100644 --- a/src/object.rs +++ b/src/object.rs @@ -2,6 +2,7 @@ use crate::repository::Repository; use crate::{error::RuntimeError, kind::Kind}; use anyhow::{anyhow, Context, Result}; use flate2::{write::ZlibEncoder, Compression}; + use sha1::{Digest, Sha1}; use std::io::Write; use std::{ @@ -82,7 +83,7 @@ impl Repository { pub fn write_object(&self, kind: Kind, content: &[u8]) -> Result<[u8; 20]> { let mut hasher = Sha1::new(); - hasher.update(format!("{} {}\0", kind.string(), content.len()).as_bytes()); + hasher.update(format!("{} {}\0", kind, content.len()).as_bytes()); hasher.update(content); let hash = hasher.finalize().into(); let hash_str = hex::encode(hash); @@ -100,8 +101,7 @@ impl Repository { 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, "{} {}\0", kind.string(), content.len()) - .context("could not write header")?; + write!(zlib_out, "{} {}\0", kind, content.len()).context("could not write header")?; zlib_out.write_all(content)?; zlib_out .finish() @@ -111,6 +111,18 @@ impl Repository { } } +pub fn hash_object(file: &Path) -> Result<[u8; 20]> { + let content = std::fs::read(file)?; + + let kind = Kind::Blob(false); + let mut hasher = Sha1::new(); + hasher.update(format!("{} {}\0", kind, content.len()).as_bytes()); + hasher.update(content); + let hash = hasher.finalize().into(); + + Ok(hash) +} + fn is_path_in_repo(repo_path: &Path, file_path: &Path) -> Result { // Convert both paths to absolute paths let repo_canonical = repo_path.canonicalize()?; @@ -178,7 +190,7 @@ impl Object { format!( "{:0>6} {} {} {:name_len$}", entry.mode, - entry.kind.string(), + entry.kind, hash, entry.name, name_len = max_name_len @@ -193,3 +205,31 @@ impl Object { Ok(res) } } + +#[cfg(test)] +mod tests { + use super::*; + use hex::FromHex; + use std::io::Cursor; + + #[test] + fn test_object_string() { + let data = b"hello"; + let _obj = Object { + kind: Kind::Blob(true), + _size: 5, + data: Cursor::new(data), + }; + + let temp_file = std::env::temp_dir().join("temp_file"); + let mut file = File::create(&temp_file).unwrap(); + file.write_all(data).unwrap(); + + let res = hash_object(&temp_file); + + assert_eq!( + res.unwrap(), + <[u8; 20]>::from_hex("b6fc4c620b67d95f953a5c1c1230aaab5db5a1b0").unwrap(), + ); + } +} diff --git a/src/pack.rs b/src/pack.rs index 509f890..fe914e0 100644 --- a/src/pack.rs +++ b/src/pack.rs @@ -378,10 +378,9 @@ impl Repository { let mut buf = [0u8; 256 * 4]; file.read_exact(&mut buf)?; - for idx in 0..256 { - let offset = idx * 4; - num_objects = u32::from_be_bytes(buf[offset..offset + 4].try_into().unwrap()); - fanout_table[idx] = num_objects; + for (idx, fanout_record) in fanout_table.iter_mut().enumerate() { + num_objects = u32::from_be_bytes(buf[idx * 4..idx * 4 + 4].try_into().unwrap()); + *fanout_record = num_objects; } let mut names = vec![0u8; 20 * num_objects as usize];