diff --git a/src/git_fs/index.rs b/src/git_fs/index.rs index 05bfcef..8bf072c 100644 --- a/src/git_fs/index.rs +++ b/src/git_fs/index.rs @@ -1,23 +1,77 @@ use std::{ io::{ self, + prelude::*, Error, }, + fs::File, + fmt::Display, + path::Path, }; +use std::ffi::CStr; +use sha1::{Sha1, Digest}; use crate::GIT_DIR; +#[derive(Debug)] +pub enum ObjectType { + Regular, + SymLink, + GitLink, +} + +impl Display for ObjectType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::Regular => write!(f, "Regular"), + Self::SymLink => write!(f, "Symlink"), + Self::GitLink => write!(f, "Gitlink"), + } + } +} + #[derive(Debug)] pub struct IndexEntry { - file_size: u32, + entry_size: usize, + object_type: ObjectType, + permissions: u16, hash: [u8; 20], name: String, } impl IndexEntry { - fn from_bytes(bytes: Vec) -> io::Result { + fn from_bytes(bytes: &[u8]) -> io::Result { + let mode = &bytes[24..28]; + let ot_bin = mode[2] >> 4; + let object_type = match ot_bin { + 0b1000 => ObjectType::Regular, + 0b1010 => ObjectType::SymLink, + 0b1110 => ObjectType::GitLink, + _ => return Err(Error::other(format!("Invalid object type: {}", ot_bin))) + }; + let permissions = u16::from_be_bytes(mode[2..4].try_into().unwrap()) &!0xFE00; + match (permissions, &object_type) { + (0, ObjectType::GitLink) => (), + (0, ObjectType::SymLink) => (), + (0o755, ObjectType::Regular) => (), + (0o644, ObjectType::Regular) => (), + _ => return Err(Error::other(format!("Invalid permissions (0o{:o}) for type {}", permissions, object_type))) + }; - return Err(Error::other("Not implemented")) + let hash: [u8; 20] = bytes[40..60].try_into().unwrap(); + let cname = CStr::from_bytes_until_nul(&bytes[62..]).unwrap(); + let name = String::from(cname.to_str().unwrap()); + + let entry_size = usize::div_ceil(62 + cname.count_bytes(), 8) * 8; + + Ok(IndexEntry { entry_size, object_type, permissions, hash, name }) + } + + fn to_bytes(&self) -> io::Result> { + let bytes = Vec::new(); + + Ok(bytes) + // Err(Error::other("Not implemented")) } } @@ -28,29 +82,56 @@ pub struct Index { } 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, + pub fn load() -> io::Result { + let path = Path::new(GIT_DIR) + .join("index"); + let mut file = File::open(&path)?; + let mut content: Vec = Vec::new(); + file.read_to_end(&mut content)?; + + Index::from_bytes(content) + } + + fn from_bytes(bytes: Vec) -> io::Result { + match &bytes[0..4] { + b"DIRC" => (), + _ => return Err(Error::other("Invalid index signature")) }; - match str::from_utf8(&magic) { - Ok("DIRC") => (), - _ => return Err(Error::other("Invalid index")) + let version = u32::from_be_bytes(bytes[4..8].try_into().unwrap()); + let count = u32::from_be_bytes(bytes[8..12].try_into().unwrap()); + + let mut entries: Vec = Vec::with_capacity(usize::try_from(count).unwrap()); + + let mut offset = 12; + + for _i in 0..count { + let entry = IndexEntry::from_bytes(&bytes[offset..])?; + offset += entry.entry_size; + entries.push(entry); }; - 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 { - - }; + let mut hasher = Sha1::new(); + hasher.update(&bytes[..offset]); + let hash = hasher.finalize(); + if bytes[offset..] != *hash { + return Err(Error::other("Invalid index")); + } return Ok(Index { version, entries}); } + + pub fn to_bytes(&self) -> io::Result> { + let mut bytes: Vec = Vec::new(); + bytes.extend("DIRC".as_bytes()); + bytes.extend(self.version.to_be_bytes()); + let count: u32 = self.entries.len() as u32; + bytes.extend(count.to_be_bytes()); + + for entry in &self.entries { + bytes.extend(entry.to_bytes()?); + } + + Ok(bytes) + } } diff --git a/src/subcommands/test.rs b/src/subcommands/test.rs index c836eb6..b0f18bf 100644 --- a/src/subcommands/test.rs +++ b/src/subcommands/test.rs @@ -1,6 +1,11 @@ use clap::Parser; -use std::fs; +use std::{ + fs::{File}, + io::prelude::*, + path::Path +}; use crate::subcommands::Subcommand; +use crate::git_fs::index::Index; use super::CmdResult; @@ -11,18 +16,13 @@ pub struct TestSubcommand { impl Subcommand for TestSubcommand { fn run(&self) -> CmdResult { - let metadata = match fs::metadata(self.path.clone()) { - Ok(metadata) => metadata, - _ => return Err(String::from("")), - }; + let index = Index::load().unwrap(); - let mtime = match metadata.modified() { - Ok(mtime) => mtime, - _ => return Err(String::from("")), - }; + println!("{index:?}"); - // mtime - println!("{mtime:?}"); + let path = Path::new(&self.path); + let mut file = File::create(path).unwrap(); + let _ = file.write_all(&index.to_bytes().unwrap()); Ok(String::from("")) }