feat: prepare tree objects
This commit is contained in:
parent
82f4381d20
commit
8db6c791fc
22
src/main.rs
22
src/main.rs
@ -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(())
|
||||
|
@ -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
55
src/tree.rs
Normal 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())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user