feat: prepare tree objects
All checks were successful
CI checks / Clippy (push) Successful in 29s
CI checks / Format (push) Successful in 2m53s

This commit is contained in:
Patrick MARIE 2025-02-04 23:05:08 +01:00
parent 82f4381d20
commit 8db6c791fc
Signed by: mycroft
GPG Key ID: BB519E5CD8E7BFA7
3 changed files with 82 additions and 17 deletions

View File

@ -8,9 +8,10 @@ use clap::Subcommand;
mod error;
mod kind;
mod object;
mod tree;
use crate::object::read_object;
use crate::object::write_object;
use crate::object::{read_object, write_object};
use crate::tree::write_tree;
#[derive(Parser)]
#[command(name = "mg", about = "A simple git clone")]
@ -37,6 +38,11 @@ enum Command {
/// The file to write
file: PathBuf,
},
/// Write a tree object
WriteTree {
/// The path to write
path: PathBuf,
},
}
fn default_init_path() -> PathBuf {
@ -59,21 +65,25 @@ fn init_repository(path: PathBuf) -> Result<PathBuf> {
fn main() -> Result<(), Error> {
let cli = Cli::parse();
let path = default_init_path();
let repo_path = default_init_path();
match cli.command {
Command::Init { path } => match init_repository(path) {
Ok(path) => println!("Initialized empty Git repository in {:?}", path),
Err(e) => eprintln!("Failed to initialize repository: {}", e),
},
Command::CatFile { hash } => match read_object(&path, &hash) {
Command::CatFile { hash } => match read_object(&repo_path, &hash) {
Ok(mut obj) => print!("{}", obj.string()?),
Err(e) => eprintln!("Failed to read object: {}", e),
},
Command::WriteBlob { file } => match write_object(&path, &file) {
Ok(hash) => println!("{}", hash),
Command::WriteBlob { file } => match write_object(&repo_path, &file) {
Ok(hash) => println!("{}", hex::encode(hash)),
Err(e) => eprintln!("Failed to write object: {}", e),
},
Command::WriteTree { path } => match write_tree(&repo_path, &path) {
Ok(hash) => println!("{}", hex::encode(hash)),
Err(e) => eprintln!("Failed to write tree: {}", e),
},
}
Ok(())

View File

@ -18,11 +18,11 @@ pub struct Object<Reader> {
}
#[derive(Debug)]
struct TreeObject {
mode: String,
kind: Kind,
name: String,
hash: [u8; 20],
pub struct TreeObject {
pub mode: String,
pub kind: Kind,
pub name: String,
pub hash: [u8; 20],
}
pub fn read_object(path: &Path, object: &str) -> Result<Object<impl BufRead>> {
@ -80,7 +80,7 @@ fn is_path_in_repo(repo_path: &Path, file_path: &Path) -> Result<bool> {
Ok(file_canonical.starts_with(repo_canonical))
}
pub fn hash_file(file: &Path) -> Result<String> {
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);
@ -91,27 +91,27 @@ pub fn hash_file(file: &Path) -> Result<String> {
let mut hasher = Sha1::new();
hasher.update(header.as_bytes());
copy(&mut buf_reader, &mut hasher)?;
let hash_bytes = hasher.finalize();
Ok(hex::encode(hash_bytes))
Ok(hasher.finalize().into())
}
pub fn write_object(repo_path: &Path, file: &Path) -> Result<String> {
pub fn write_object(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 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[..2]);
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[2..]);
let target_file = target_dir.join(&hash_str[2..]);
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());

55
src/tree.rs Normal file
View File

@ -0,0 +1,55 @@
use anyhow::{Context, Result};
use sha1::{Digest, Sha1};
use std::path::PathBuf;
use crate::kind::Kind;
use crate::object::{hash_file, TreeObject};
pub fn write_tree(_repo_path: &PathBuf, path: &PathBuf) -> Result<[u8; 20]> {
let mut entries = Vec::new();
let files = std::fs::read_dir(path)?;
for file in files {
let file = file?;
let file_type = file.file_type()?;
let file_name = file.file_name();
let file_path = file.path();
let hash: [u8; 20];
let kind;
if file_type.is_dir() {
hash = write_tree(_repo_path, &file_path).context("could not write_tree of subtree")?;
kind = Kind::Tree;
} else {
hash = hash_file(&file_path)?;
kind = Kind::Blob;
}
entries.push(TreeObject {
mode: "100644".to_string(),
kind,
name: file_name.into_string().unwrap(),
hash,
})
}
entries.sort_by(|a, b| a.name.cmp(&b.name));
let mut out: Vec<u8> = Vec::new();
for entry in &entries {
out.extend_from_slice(entry.mode.as_bytes());
out.push(b' ');
out.extend_from_slice(entry.name.as_bytes());
out.push(0);
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())
}