feat: create a blob object
All checks were successful
CI checks / Format (push) Successful in 42s
CI checks / Clippy (push) Successful in 39s

This commit is contained in:
2025-02-04 22:03:26 +01:00
parent bfc0e7f7b7
commit 82f4381d20
4 changed files with 154 additions and 4 deletions

View File

@ -10,6 +10,7 @@ mod kind;
mod object;
use crate::object::read_object;
use crate::object::write_object;
#[derive(Parser)]
#[command(name = "mg", about = "A simple git clone")]
@ -31,6 +32,11 @@ enum Command {
/// The object to display
hash: String,
},
/// Write a blob object
WriteBlob {
/// The file to write
file: PathBuf,
},
}
fn default_init_path() -> PathBuf {
@ -61,9 +67,13 @@ fn main() -> Result<(), Error> {
Err(e) => eprintln!("Failed to initialize repository: {}", e),
},
Command::CatFile { hash } => match read_object(&path, &hash) {
Ok(mut obj) => println!("{}", obj.string()?),
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),
Err(e) => eprintln!("Failed to write object: {}", e),
},
}
Ok(())

View File

@ -1,7 +1,14 @@
use anyhow::{Context, Result};
use std::{fs::File, io::BufRead, path::Path};
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::{
fs::{create_dir, File},
io::{copy, BufRead},
os::unix::fs::MetadataExt,
path::Path,
};
#[derive(Debug)]
pub struct Object<Reader> {
@ -61,6 +68,60 @@ pub fn read_object(path: &Path, object: &str) -> Result<Object<impl BufRead>> {
})
}
fn is_path_in_repo(repo_path: &Path, file_path: &Path) -> Result<bool> {
// Convert both paths to absolute paths
let repo_canonical = repo_path.canonicalize()?;
let file_canonical = match file_path.canonicalize() {
Ok(path) => path,
Err(_) => return Ok(false),
};
// Check if file_path starts with repo_path
Ok(file_canonical.starts_with(repo_canonical))
}
pub fn hash_file(file: &Path) -> Result<String> {
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)?;
let hash_bytes = hasher.finalize();
Ok(hex::encode(hash_bytes))
}
pub fn write_object(repo_path: &Path, file: &Path) -> Result<String> {
if !file.exists() || !is_path_in_repo(repo_path, file)? {
return Err(anyhow!("path does not exist"));
}
let hash = hash_file(file)?;
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]);
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 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")?;
Ok(hash)
}
impl<R: BufRead> Object<R> {
pub fn string(&mut self) -> Result<String> {
let mut buf: Vec<u8> = Vec::new();