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 error;
|
||||||
mod kind;
|
mod kind;
|
||||||
mod object;
|
mod object;
|
||||||
|
mod tree;
|
||||||
|
|
||||||
use crate::object::read_object;
|
use crate::object::{read_object, write_object};
|
||||||
use crate::object::write_object;
|
use crate::tree::write_tree;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "mg", about = "A simple git clone")]
|
#[command(name = "mg", about = "A simple git clone")]
|
||||||
@ -37,6 +38,11 @@ enum Command {
|
|||||||
/// The file to write
|
/// The file to write
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
},
|
},
|
||||||
|
/// Write a tree object
|
||||||
|
WriteTree {
|
||||||
|
/// The path to write
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_init_path() -> PathBuf {
|
fn default_init_path() -> PathBuf {
|
||||||
@ -59,21 +65,25 @@ fn init_repository(path: PathBuf) -> Result<PathBuf> {
|
|||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
let path = default_init_path();
|
let repo_path = default_init_path();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Init { path } => match init_repository(path) {
|
Command::Init { path } => match init_repository(path) {
|
||||||
Ok(path) => println!("Initialized empty Git repository in {:?}", path),
|
Ok(path) => println!("Initialized empty Git repository in {:?}", path),
|
||||||
Err(e) => eprintln!("Failed to initialize repository: {}", e),
|
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()?),
|
Ok(mut obj) => print!("{}", obj.string()?),
|
||||||
Err(e) => eprintln!("Failed to read object: {}", e),
|
Err(e) => eprintln!("Failed to read object: {}", e),
|
||||||
},
|
},
|
||||||
Command::WriteBlob { file } => match write_object(&path, &file) {
|
Command::WriteBlob { file } => match write_object(&repo_path, &file) {
|
||||||
Ok(hash) => println!("{}", hash),
|
Ok(hash) => println!("{}", hex::encode(hash)),
|
||||||
Err(e) => eprintln!("Failed to write object: {}", e),
|
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(())
|
Ok(())
|
||||||
|
@ -18,11 +18,11 @@ pub struct Object<Reader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TreeObject {
|
pub struct TreeObject {
|
||||||
mode: String,
|
pub mode: String,
|
||||||
kind: Kind,
|
pub kind: Kind,
|
||||||
name: String,
|
pub name: String,
|
||||||
hash: [u8; 20],
|
pub hash: [u8; 20],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_object(path: &Path, object: &str) -> Result<Object<impl BufRead>> {
|
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))
|
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 fd = File::open(file).context("opening file for sha1")?;
|
||||||
let mut buf_reader = std::io::BufReader::new(fd);
|
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();
|
let mut hasher = Sha1::new();
|
||||||
hasher.update(header.as_bytes());
|
hasher.update(header.as_bytes());
|
||||||
copy(&mut buf_reader, &mut hasher)?;
|
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)? {
|
if !file.exists() || !is_path_in_repo(repo_path, file)? {
|
||||||
return Err(anyhow!("path does not exist"));
|
return Err(anyhow!("path does not exist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = hash_file(file)?;
|
let hash = hash_file(file)?;
|
||||||
|
let hash_str = hex::encode(hash);
|
||||||
let bytes_num = file.metadata()?.len();
|
let bytes_num = file.metadata()?.len();
|
||||||
let mut buf_reader = std::io::BufReader::new(File::open(file)?);
|
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() {
|
if !target_dir.exists() {
|
||||||
create_dir(&target_dir).context("could not create directory in .git/objects")?;
|
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 file_out_fd = File::create(target_file).context("could not open target file")?;
|
||||||
|
|
||||||
let mut zlib_out = ZlibEncoder::new(file_out_fd, Compression::default());
|
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