commit f600a89f5b2dcac86a5fb94c8c0bdfb65544cd1e Author: Antonin Ruan Date: Thu Feb 19 14:46:58 2026 +0100 Init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..08b95b9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,284 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf74d17c3aec5495110c34cc3f78644bfa89af6c8993ed4de2790e49b6499" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370daa45065b80218950227371916a1633217ae42b2715b2287b606dcd618e24" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rgit" +version = "0.1.0" +dependencies = [ + "clap", + "hex", + "sha1", + "zlib-rs", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zlib-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a33bbf307b25a1774cee0687694ec72fa7814b3ab5c1c12a9d2fc6a36fc439c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5e77de2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rgit" +version = "0.1.0" +description = "Git implementation in Rust" +edition = "2024" + +[toolchain] +channel = "nightly" + +[dependencies] +clap = { version = "4.5.59", features = ["derive"] } +hex = "0.4.3" +sha1 = "0.10.6" +zlib-rs = "0.6.1" diff --git a/src/git_fs/head.rs b/src/git_fs/head.rs new file mode 100644 index 0000000..201f8c9 --- /dev/null +++ b/src/git_fs/head.rs @@ -0,0 +1,51 @@ +use std::{ + fs::File, + io::prelude::*, + path::Path, +}; + +use crate::GIT_DIR; +use crate::git_fs::read_lines; + +#[derive(Debug)] +pub struct Head { + pub ref_to: String, +} + +impl Head { + pub fn load() -> Result { + let path = Path::new(GIT_DIR).join("HEAD"); + + let mut lines = match read_lines(path) { + Err(_) => return Err(()), + Ok(lines) => lines, + }; + + let content = match lines.next() { + Some(Ok(line)) => line, + _ => return Err(()), + }; + + match content.split_once(": ") { + None => Err(()), + Some((_, r)) => Ok(Head { ref_to: String::from(r) }) + } + } + + pub fn save(&self) -> Result<(), ()> { + let path = Path::new(GIT_DIR).join("HEAD"); + let mut content = String::from("ref: "); + content.push_str(&self.ref_to); + content.push_str("\n"); + + let mut file = match File::create(&path) { + Err(_) => return Err(()), + Ok(file) => file, + }; + + match file.write(content.as_bytes()) { + Err(_) => Err(()), + Ok(_) => Ok(()), + } + } +} diff --git a/src/git_fs/index.rs b/src/git_fs/index.rs new file mode 100644 index 0000000..05bfcef --- /dev/null +++ b/src/git_fs/index.rs @@ -0,0 +1,56 @@ +use std::{ + io::{ + self, + Error, + }, +}; + +use crate::GIT_DIR; + +#[derive(Debug)] +pub struct IndexEntry { + file_size: u32, + hash: [u8; 20], + name: String, +} + +impl IndexEntry { + fn from_bytes(bytes: Vec) -> io::Result { + + return Err(Error::other("Not implemented")) + } +} + +#[derive(Debug)] +pub struct Index { + version: u32, + entries: Vec, +} + +impl Index { + pub fn from_bytes(bytes: Vec) -> io::Result { + let magic: [u8; 4] = match bytes.first_chunk() { + None => return Err(Error::other("Error parsing index")), + Some(magic) => *magic, + }; + + match str::from_utf8(&magic) { + Ok("DIRC") => (), + _ => return Err(Error::other("Invalid index")) + }; + + let version_bytes = <[u8; 4]>::try_from(&bytes.as_slice()[4..8]).unwrap(); + let version = u32::from_be_bytes(version_bytes); + + let count_bytes = <[u8; 4]>::try_from(&bytes.as_slice()[8..12]).unwrap(); + let count = u32::from_be_bytes(count_bytes); + + let entries: Vec = Vec::with_capacity(usize::try_from(count).unwrap()); + + for i in 0..=count { + + }; + + return Ok(Index { version, entries}); + } +} diff --git a/src/git_fs/mod.rs b/src/git_fs/mod.rs new file mode 100644 index 0000000..48cd265 --- /dev/null +++ b/src/git_fs/mod.rs @@ -0,0 +1,62 @@ +use std::{ + fs::File, + io::{ + self, prelude::*, BufRead + }, path::Path, result +}; + +use crate::GIT_DIR; + +pub mod head; +pub mod index; +pub mod object; + +fn read_lines

(filename: P) -> io::Result>> +where P: AsRef { + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} + +pub struct Ref { + ref_name: String, + commit_hash: String, +} + +impl Ref { + pub fn load(name: String) -> result::Result { + let path = Path::new(GIT_DIR).join(&name); + + let mut lines = match read_lines(path) { + Ok(lines) => lines, + Err(_) => return Err(()), + }; + + let content = match lines.next() { + Some(Ok(line)) => line, + _ => return Err(()), + }; + + Ok( + Ref { + ref_name: name, + commit_hash: content + } + ) + } + + pub fn save(&self) -> result::Result<(), ()> { + let path = Path::new(GIT_DIR) + .join("refs") + .join(&self.ref_name); + + let mut file = match File::create(&path) { + Err(_) => return Err(()), + Ok(file) => file, + }; + + match file.write(self.commit_hash.as_bytes()) { + Err(_) => Err(()), + Ok(_) => Ok(()), + } + } +} diff --git a/src/git_fs/object.rs b/src/git_fs/object.rs new file mode 100644 index 0000000..65a4f70 --- /dev/null +++ b/src/git_fs/object.rs @@ -0,0 +1,306 @@ +use hex::encode; +use sha1::{Sha1, Digest}; +use std::{ + fs::{create_dir_all, File}, + io::{ + self, + prelude::*, + Error, + }, + path::Path, +}; +use zlib_rs::{ + compress_bound, compress_slice, decompress_slice, DeflateConfig, InflateConfig, ReturnCode +}; + +use crate::GIT_DIR; + +#[derive(Debug)] +pub enum GitObjectType { + Blob(Blob), + Tree(Tree), +} + +impl GitObjectType { + pub fn load(name: String) -> io::Result { + let prefix = &name[..2]; + let suffix = &name[2..]; + + let path = Path::new(GIT_DIR) + .join("objects") + .join(prefix) + .join(suffix); + + if !path.exists() { + return Err(Error::other("Object does not exists")) + } + + let mut file = File::open(path)?; + let mut content: Vec = Vec::new(); + file.read_to_end(&mut content)?; + + let mut header_buf = [0u8; 20]; + + match decompress_slice(&mut header_buf, &content, InflateConfig::default()) { + (_, ReturnCode::Ok) => (), + (_, ReturnCode::BufError) => (), + _ => return Err(Error::other("Error while decompressing")) + }; + + let header = match str::from_utf8(&header_buf) { + Ok(s) => match s.split_once("\0") { + None => return Err(Error::other("Invalid format")), + Some((header, _)) => header, + } + Err(_) => return Err(Error::other("Utf-8 error")) + }; + + let (t, size) = match header.split_once(" ") { + None => return Err(Error::other("Invalid format")), + Some((t, s_str)) => match s_str.parse::() { + Ok(s) => (t, s), + _ => return Err(Error::other("Invalid format")), + } + }; + let header_size = header.len() + 1; + let total_size = size + header_size; + + let mut deflated = vec![0u8; total_size]; + + match decompress_slice(&mut deflated, &content, InflateConfig::default()) { + (_, ReturnCode::Ok) => (), + _ => return Err(Error::other("Error while decompressing")) + } + + let obj_bytes = deflated[header_size..].to_vec(); + + match t { + "blob" => Ok(GitObjectType::Blob(Blob{ + size, + content: obj_bytes, + })), + "tree" => Ok(GitObjectType::Tree(Tree::from_bytes(obj_bytes)?)), + _ => Err(Error::other("Invalid type")) + } + } +} + +pub trait GitObject { + fn raw(&self) -> Vec; + fn hash(&self, write: bool) -> io::Result>; + fn save(&self, hash: &String, bytes: Vec) -> io::Result<()> { + let mut compressed_buf = vec![0u8; compress_bound(bytes.len())]; + let compressed = match compress_slice(&mut compressed_buf, &bytes, DeflateConfig::default()) { + (compressed, ReturnCode::Ok) => compressed, + (_, _) => return Err(Error::other("Error while compressing objects")), + }; + + let prefix = &hash[..2]; + let suffix = &hash[2..]; + let path = Path::new(GIT_DIR) + .join("objects") + .join(prefix); + + create_dir_all(&path)?; + + let mut file = File::create(&path.join(suffix))?; + file.write_all(&compressed)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Blob { + size: usize, + content: Vec, +} + +impl Blob { + pub fn create(filename: String) -> io::Result { + let path = Path::new(&filename); + + let mut file = File::open(path)?; + + let mut content: Vec = Vec::new(); + let read = file.read_to_end(&mut content)?; + + Ok(Self { size: read, content }) + } +} + +impl GitObject for Blob { + fn raw(&self) -> Vec { + let mut header = String::from("blob "); + header.push_str(&self.size.to_string()); + header.push_str("\0"); + + let mut to_compress: Vec = Vec::from(header.as_bytes()); + to_compress.extend(&self.content); + + return to_compress + } + + fn hash(&self, write: bool) -> io::Result> { + let bytes = self.raw(); + + let mut hasher = Sha1::new(); + hasher.update(&bytes); + + let hash = hasher.finalize(); + + if write { + self.save(&encode(hash), bytes)?; + } + + Ok(Vec::from(hash.as_slice())) + } +} + +#[derive(Debug)] +pub enum FileType { + Directory, + RegNonXFile, + RegExeFile, + SymLink, + GitLink, +} + +#[derive(Debug)] +pub struct TreeEntry { + pub ftype: FileType, + pub name: String, + pub hash: [u8; 20], +} + +impl TreeEntry { + fn from_bytes(bytes: Vec) -> io::Result { + let split = match bytes.as_slice().iter().position(|x| *x == 0) { + None => return Err(Error::other("Error parsing tree object")), + Some(s) => s, + }; + + let (header, hash_v) = bytes.split_at(split+1); + + if hash_v.len() != 20 { + return Err(Error::other("Digest length not valid")); + } + + let hash: [u8; 20] = match hash_v.first_chunk() { + None => return Err(Error::other("Error parsing tree object")), + Some(h) => *h, + }; + + + let header_str = match str::from_utf8(&header[..header.len()-1]) { + Ok(s) => s, + _ => return Err(Error::other("Error parsing tree object")) + }; + + let (ftype, name) = match header_str.split_once(" ") { + None => return Err(Error::other("Error parsing tree object")), + Some((l, name)) => { + let ftype = match l { + "0040000" => FileType::Directory, + "0100644" => FileType::RegNonXFile, + "0100755" => FileType::RegExeFile, + "0120000" => FileType::SymLink, + "0160000" => FileType::GitLink, + _ => return Err(Error::other("Invalid file type")) + }; + (ftype, String::from(name)) + } + }; + + return Ok(TreeEntry { + ftype, + name, + hash, + }); + } + + fn as_bytes(&self) -> Vec { + let mut header = String::from(match self.ftype { + FileType::Directory => "0040000", + FileType::RegNonXFile => "0100644", + FileType::RegExeFile => "0100755", + FileType::SymLink => "0120000", + FileType::GitLink => "0160000", + }); + header.push_str(" "); + header.push_str(&self.name); + header.push_str("\0"); + + let mut bytes: Vec = Vec::from(header.as_bytes()); + bytes.extend(self.hash); + + return bytes; + } +} + +#[derive(Debug)] +pub struct Tree { + pub entries: Vec +} + +impl Tree { + pub fn from_bytes(bytes: Vec) -> io::Result { + let mut entries: Vec = Vec::new(); + let splits = bytes.iter() + .enumerate() + .filter_map(|(idx, b)| (*b == 0).then(|| idx + 21)) + .collect::>(); + + let bytes_slice = bytes.as_slice(); + let mut left_i = 0; + + for split in splits { + let right_i = split; + let entry_r = &bytes_slice[left_i..right_i]; + + let entry = TreeEntry::from_bytes(Vec::from(entry_r))?; + entries.push(entry); + left_i = right_i; + } + + return Ok(Tree { entries }); + } + + fn as_bytes(&self) -> Vec { + let mut result = Vec::new(); + for entry in &self.entries { + result.extend(entry.as_bytes()); + } + + return result; + } +} + +impl GitObject for Tree { + fn raw(&self) -> Vec { + let bytes = self.as_bytes(); + let mut header = String::from("tree "); + header.push_str(&bytes.len().to_string()); + header.push_str("\0"); + + let mut raw: Vec = Vec::from(header.as_bytes()); + raw.extend(self.as_bytes()); + + return raw; + } + + fn hash(&self, write: bool) -> io::Result> { + let bytes = self.raw(); + + let mut hasher = Sha1::new(); + hasher.update(&bytes); + + let hash = hasher.finalize(); + + if write { + self.save(&encode(hash), bytes)?; + } + + return Ok(Vec::from(hash.as_slice())); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8d0f5f7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,65 @@ +use clap::Parser; +// use std::{ +// fs::File, +// io::prelude::*, +// path::Path, +// }; + +// use zlib_rs::{ +// ReturnCode, +// DeflateConfig, compress_bound, compress_slice, +// }; + +use subcommands::{Subcommand, SubcommandType}; + +mod subcommands; +mod git_fs; + +const GIT_DIR: &str = ".rgit"; + +#[derive(Parser, Debug)] +#[command(version, about, long_about)] +struct CmdArgs{ + #[command(subcommand)] + cmd: SubcommandType, +} + +fn main() { + let args = CmdArgs::parse(); + + match args.cmd.run() { + Err(str) => println!("Some error occured: {str}"), + Ok(str) => println!("{str}"), + } + + // let path = Path::new("sample/file.txt"); + // let display = path.display(); + + // let mut file = match File::open(&path) { + // Err(err) => panic!("couldn't open {}: {}", display, err), + // Ok(file) => file, + // }; + + // let mut s = String::new(); + // match file.read_to_string(&mut s) { + // Err(err) => panic!("couldn't read {}: {}", display, err), + // Ok(_) => print!("content: \n{}", s), + // } + + // let mut compressed_buf = vec![0u8; compress_bound(s.len())]; + // let compressed = match compress_slice(&mut compressed_buf, s.as_bytes(), DeflateConfig::default()) { + // (compressed, ReturnCode::Ok) => compressed, + // (_, _) => panic!("Error while compressing"), + // }; + + // let path_w = Path::new("sample/write"); + // file = match File::create(&path_w) { + // Err(_) => panic!("error while opening file"), + // Ok(file) => file, + // }; + + // match file.write_all(compressed) { + // Err(_) => panic!("error while writing"), + // Ok(_) => println!("File written"), + // } +} diff --git a/src/subcommands/hash_object.rs b/src/subcommands/hash_object.rs new file mode 100644 index 0000000..b696329 --- /dev/null +++ b/src/subcommands/hash_object.rs @@ -0,0 +1,32 @@ +use clap::Parser; +use hex::encode; + +use crate::{ + git_fs::object::{Blob, GitObject}, + subcommands::Subcommand +}; + +use super::CmdResult; + +#[derive(Parser, Debug)] +pub struct HashObjectSubcommand { + #[arg(short)] + /// Save object in database + pub write: bool, + + pub path: String, +} + +impl Subcommand for HashObjectSubcommand { + fn run (&self) -> CmdResult { + let object = match Blob::create(self.path.clone()) { + Ok(o) => o, + _ => return Err("".to_owned()) + }; + + match object.hash(self.write) { + Ok(hash) => Ok(encode(hash)), + _ => return Err("".to_owned()) + } + } +} diff --git a/src/subcommands/init.rs b/src/subcommands/init.rs new file mode 100644 index 0000000..ba470ea --- /dev/null +++ b/src/subcommands/init.rs @@ -0,0 +1,60 @@ +use clap::Parser; +use std::{ + fs, + path::Path +}; + +use crate::git_fs::head::Head; +use crate::subcommands::Subcommand; +use crate::GIT_DIR; + +use super::CmdResult; + +#[derive(Parser, Debug)] +pub struct InitSubcommand { + directory: Option, +} + +impl Subcommand for InitSubcommand { + fn run(&self) -> CmdResult { + let path = match &self.directory { + None => Path::new("."), + Some(path) => Path::new(path), + }.join(GIT_DIR); + + let new_repo = path.exists(); + + match fs::create_dir_all(&path) { + Err(_) => return Err("Error while creating dir".to_owned()), + Ok(()) => (), + }; + + let folders = [ + "objects/info", + "objects/pack", + "refs/heads", + "refs/tags", + ]; + + for folder in folders { + match fs::create_dir_all(&path.join(folder)) { + Err(_) => return Err("".to_owned()), + Ok(()) => (), + }; + } + + let head = Head { ref_to: String::from("refs/heads/master") }; + match head.save() { + Err(_) => return Err("".to_owned()), + Ok(()) => (), + }; + + let canonical_path = path.canonicalize(); + + if new_repo { + Ok(format!("Reinitialized exisiting Git repo in {}", canonical_path.unwrap().display())) + } else { + Ok(format!("Initialized empty Git repo in {}", canonical_path.unwrap().display())) + } + } +} diff --git a/src/subcommands/mod.rs b/src/subcommands/mod.rs new file mode 100644 index 0000000..c7979e7 --- /dev/null +++ b/src/subcommands/mod.rs @@ -0,0 +1,33 @@ +use crate::subcommands::{ + init::InitSubcommand, + test::TestSubcommand, + hash_object::HashObjectSubcommand, +}; + +mod hash_object; +mod init; +mod test; + +pub type CmdResult = Result; + +#[derive(clap::Parser, Debug)] +pub enum SubcommandType { + /// Init a Git repository + Init(InitSubcommand), + HashObject(HashObjectSubcommand), + Test(TestSubcommand), +} + +pub trait Subcommand { + fn run(&self) -> CmdResult; +} + +impl Subcommand for SubcommandType { + fn run(&self) -> CmdResult { + match self { + Self::Init(cmd) => cmd.run(), + Self::HashObject(cmd) => cmd.run(), + Self::Test(cmd) => cmd.run(), + } + } +} diff --git a/src/subcommands/test.rs b/src/subcommands/test.rs new file mode 100644 index 0000000..c836eb6 --- /dev/null +++ b/src/subcommands/test.rs @@ -0,0 +1,29 @@ +use clap::Parser; +use std::fs; +use crate::subcommands::Subcommand; + +use super::CmdResult; + +#[derive(Parser, Debug)] +pub struct TestSubcommand { + path: String +} + +impl Subcommand for TestSubcommand { + fn run(&self) -> CmdResult { + let metadata = match fs::metadata(self.path.clone()) { + Ok(metadata) => metadata, + _ => return Err(String::from("")), + }; + + let mtime = match metadata.modified() { + Ok(mtime) => mtime, + _ => return Err(String::from("")), + }; + + // mtime + println!("{mtime:?}"); + + Ok(String::from("")) + } +}