From 62af81b28f0819e16a5f3a438e0ba70ef229a3cd Mon Sep 17 00:00:00 2001 From: Patrick Marie Date: Wed, 5 Feb 2025 21:55:05 +0100 Subject: [PATCH] feat: adding commit, show commands --- src/commit.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 25 +++++++++++ src/object.rs | 4 +- src/repository.rs | 6 +-- 4 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 src/commit.rs diff --git a/src/commit.rs b/src/commit.rs new file mode 100644 index 0000000..7ddde5e --- /dev/null +++ b/src/commit.rs @@ -0,0 +1,106 @@ +use std::fs::{read_to_string, File}; + +use anyhow::{Context, Result}; +use hex::FromHex; + +use crate::{kind::Kind, repository::Repository}; + +impl Repository { + pub fn read_head(&self) -> Result { + let head_path = self.path.join(".git").join("HEAD"); + read_to_string(head_path).context("reading head") + } + + pub fn current_branch(&self) -> Result { + let head = self.read_head()?; + Ok(head + .trim_start_matches("ref: refs/heads/") + .trim_end() + .to_string()) + } + + pub fn current_commit(&self) -> Result<[u8; 20]> { + let current_branch = self.current_branch()?; + let branch_path = self + .path + .join(".git") + .join("refs") + .join("heads") + .join(¤t_branch); + + let r = read_to_string(branch_path).context("could not read current branch")?; + let r = r.trim(); + + Ok(<[u8; 20]>::from_hex(r)?) + } + + pub fn has_current_commit(&self) -> bool { + self.current_commit().is_ok() + } + + pub fn set_current_commit(&self, hash: &[u8; 20]) -> Result<()> { + let current_branch = self + .current_branch() + .context("could not find current branch")?; + + let branch_path = self.path.join(".git").join("refs").join("heads"); + + if !branch_path.exists() { + std::fs::create_dir_all(&branch_path)?; + } + + let branch_path = branch_path.join(¤t_branch); + + // file does not exist + if !branch_path.exists() { + File::create(&branch_path)?; + } + + std::fs::write(branch_path, hex::encode(hash))?; + + Ok(()) + } + + pub fn commit(&self, message: &str) -> Result<[u8; 20]> { + let has_current_commit = self.has_current_commit(); + let mut out: Vec = Vec::new(); + + let tree_hash = self + .write_tree(&self.path) + .context("could not write_tree")?; + out.extend_from_slice(b"tree "); + out.extend_from_slice(hex::encode(tree_hash).as_bytes()); + out.push(b'\n'); + + if has_current_commit { + let current_commit_id = self.current_commit()?; + out.extend_from_slice(b"parent "); + out.extend_from_slice(hex::encode(current_commit_id).as_bytes()); + out.push(b'\n'); + } + + out.push(b'\n'); + out.extend_from_slice(message.as_bytes()); + out.push(b'\n'); + + let hash = self.write_object(Kind::Commit, &out).context("Write")?; + + // update current branch's commit id + self.set_current_commit(&hash)?; + + Ok(hash) + } + + pub fn show(&self, hash: Option) -> Result<()> { + let mut commit = if let Some(hash) = hash { + self.read_object(&hash)? + } else { + let current_commit = self.current_commit()?; + self.read_object(&hex::encode(current_commit))? + }; + + println!("{}", commit.string()?); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 098c747..5fed1b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use clap::Parser; use clap::Subcommand; +mod commit; mod error; mod kind; mod object; @@ -43,6 +44,18 @@ enum Command { /// The path to write path: PathBuf, }, + /// Commit current changes + Commit { + /// The commit message + message: String, + }, + /// Get the current branch + Branch, + /// Get the latest commit + Show { + /// The commit to show + hash: Option, + }, } fn main() -> Result<(), Error> { @@ -67,6 +80,18 @@ fn main() -> Result<(), Error> { Ok(hash) => println!("{}", hex::encode(hash)), Err(e) => eprintln!("Failed to write tree: {}", e), }, + Command::Commit { message } => match repo.commit(&message) { + Ok(hash) => println!("{}", hex::encode(hash)), + Err(e) => eprintln!("Failed to commit: {}", e), + }, + Command::Branch => match repo.current_branch() { + Ok(branch) => println!("{}", branch), + Err(e) => eprintln!("Failed to get branch: {}", e), + }, + Command::Show { hash } => match repo.show(hash) { + Ok(_) => (), + Err(e) => eprintln!("Failed to show: {}", e), + }, } Ok(()) diff --git a/src/object.rs b/src/object.rs index b6e5d0f..548e81a 100644 --- a/src/object.rs +++ b/src/object.rs @@ -77,7 +77,7 @@ impl Repository { let content = std::fs::read(file)?; - Ok(self.write_object(Kind::Blob(false), &content)?) + self.write_object(Kind::Blob(false), &content) } pub fn write_object(&self, kind: Kind, content: &[u8]) -> Result<[u8; 20]> { @@ -102,7 +102,7 @@ impl Repository { let mut zlib_out = ZlibEncoder::new(file_out_fd, Compression::default()); write!(zlib_out, "{} {}\0", kind.string(), content.len()) .context("could not write header")?; - zlib_out.write(content)?; + zlib_out.write_all(content)?; zlib_out .finish() .context("could not compress or write file")?; diff --git a/src/repository.rs b/src/repository.rs index 6d7e226..ca830b8 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -2,7 +2,7 @@ use anyhow::Result; use std::{ env, fs::{create_dir, read_to_string}, - path::PathBuf, + path::{Path, PathBuf}, }; pub struct Repository { @@ -42,8 +42,8 @@ impl Repository { Ok(true) } - pub fn init_repository(&mut self, path: &PathBuf) -> Result { - self.path = path.clone(); + pub fn init_repository(&mut self, path: &Path) -> Result { + self.path = path.to_path_buf(); let git_dir = self.path.join(".git"); create_dir(&git_dir)?;