diff --git a/Cargo.lock b/Cargo.lock index 9b9b6ce..c6e865d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,6 +207,12 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "mg" version = "0.1.0" @@ -215,6 +221,7 @@ dependencies = [ "clap", "flate2", "hex", + "nom", "sha1", "thiserror", ] @@ -228,6 +235,15 @@ dependencies = [ "adler2", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.2" diff --git a/Cargo.toml b/Cargo.toml index d77c58d..dd98aa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,6 @@ anyhow = "1.0.95" clap = { version = "4.5.27", features = ["derive", "string"] } flate2 = "1.0.35" hex = "0.4.3" +nom = "8.0.0" sha1 = "0.10.6" thiserror = "2.0.11" diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..a28a4f5 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,150 @@ +use std::path::Path; + +use nom::{ + bytes::complete::take, + number::complete::{be_u16, be_u32}, + IResult, Parser, +}; + +use anyhow::{anyhow, Error, Result}; + +use crate::repository::Repository; + +#[derive(Debug)] +#[allow(dead_code)] +struct IndexHeader { + signature: [u8; 4], // "DIRC" + version: u32, // 2, 3, or 4 + entries_count: u32, +} + +#[derive(Debug)] +#[allow(dead_code)] +struct IndexEntry { + ctime_s: u32, + ctime_n: u32, + mtime_s: u32, + mtime_n: u32, + dev: u32, + ino: u32, + mode: u32, + uid: u32, + gid: u32, + size: u32, + sha1: [u8; 20], + flags: u16, + file_path: String, +} + +#[derive(Debug)] +#[allow(dead_code)] +struct Index { + header: IndexHeader, + entries: Vec, +} + +fn parse_index(input: &[u8]) -> IResult<&[u8], Index> { + let (mut input, header) = parse_header(input)?; + + let mut entries = Vec::with_capacity(header.entries_count as usize); + + for _ in 0..header.entries_count { + let (remaining, entry) = parse_entry(input)?; + entries.push(entry); + input = remaining; + } + + Ok((input, Index { header, entries })) +} + +fn parse_header(input: &[u8]) -> IResult<&[u8], IndexHeader> { + let (input, (signature, version, entries_count)) = + (take(4usize), be_u32, be_u32).parse(input)?; + + let mut sig = [0u8; 4]; + sig.copy_from_slice(signature); + + Ok(( + input, + IndexHeader { + signature: sig, + version, + entries_count, + }, + )) +} + +fn parse_entry(input: &[u8]) -> IResult<&[u8], IndexEntry> { + let start_input_len = input.len(); + let ( + input, + (ctime_s, ctime_n, mtime_s, mtime_n, dev, ino, mode, uid, gid, size, sha1_bytes, flags), + ) = ( + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + be_u32, + take(20usize), + be_u16, + ) + .parse(input)?; + let current_input_len = input.len(); + + let path_len = flags & 0xFFF; + let (input, path_bytes) = take(path_len as usize)(input)?; + let file_path = String::from_utf8_lossy(path_bytes).into_owned(); + + // between 1 and 8 NUL bytes to pad the entry. + let padding_len = 8 - ((start_input_len - current_input_len) + path_len as usize) % 8; + let (input, _) = take(padding_len)(input)?; + + let mut sha1 = [0u8; 20]; + sha1.copy_from_slice(sha1_bytes); + + Ok(( + input, + IndexEntry { + ctime_s, + ctime_n, + mtime_s, + mtime_n, + dev, + ino, + mode, + uid, + gid, + size, + sha1, + flags, + file_path, + }, + )) +} + +impl Index { + pub fn read_from_file(path: &Path) -> Result { + let content = std::fs::read(path)?; + let (_remaining, index) = + parse_index(&content).map_err(|e| anyhow!("Failed to parse index: {}", e))?; + Ok(index) + } +} + +impl Repository { + pub fn read_index(&self) -> Result<()> { + let index_path = self.path.join(".git").join("index"); + let index = Index::read_from_file(&index_path)?; + + for entry in index.entries { + println!("{} {}", hex::encode(entry.sha1), entry.file_path); + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 65b20b4..c0ac80b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use clap::Subcommand; mod commit; mod error; +mod index; mod kind; mod log; mod object; @@ -59,6 +60,8 @@ enum Command { }, /// Show the commit log Log, + /// List the index entries + LsIndex, } fn main() -> Result<(), Error> { @@ -99,6 +102,10 @@ fn main() -> Result<(), Error> { Ok(_) => (), Err(e) => eprintln!("Failed to show log: {}", e), }, + Command::LsIndex => match repo.read_index() { + Ok(_) => (), + Err(e) => eprintln!("Failed to list index: {}", e), + }, } Ok(())