feat: adding cat-file
This commit is contained in:
parent
bba1db7e2f
commit
bb731239f4
69
Cargo.lock
generated
69
Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
@ -58,6 +64,12 @@ version = "1.0.95"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.27"
|
version = "4.5.27"
|
||||||
@ -104,12 +116,37 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
@ -122,6 +159,18 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"flate2",
|
||||||
|
"hex",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -165,6 +214,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -5,4 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.95"
|
||||||
clap = { version = "4.5.27", features = ["derive"] }
|
clap = { version = "4.5.27", features = ["derive", "string"] }
|
||||||
|
flate2 = "1.0.35"
|
||||||
|
hex = "0.4.3"
|
||||||
|
thiserror = "2.0.11"
|
||||||
|
183
src/main.rs
183
src/main.rs
@ -1,4 +1,6 @@
|
|||||||
use anyhow::{Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
|
use std::env;
|
||||||
|
use std::io::prelude::*;
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@ -16,14 +18,72 @@ enum Command {
|
|||||||
/// Initialize a new Git repository
|
/// Initialize a new Git repository
|
||||||
Init {
|
Init {
|
||||||
/// The path where to create the repository. Defaults to current directory
|
/// The path where to create the repository. Defaults to current directory
|
||||||
#[arg(default_value = ".")]
|
#[arg(default_value=default_init_path().into_os_string())]
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
|
/// Display a Git object
|
||||||
|
CatFile {
|
||||||
|
/// The object to display
|
||||||
|
hash: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_repository(path: PathBuf) -> Result<PathBuf> {
|
#[derive(thiserror::Error, Debug)]
|
||||||
println!("Creating in {:?}", path);
|
pub enum RuntimeError {
|
||||||
|
#[error("Invalid character found")]
|
||||||
|
UnexpectedChar,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Kind {
|
||||||
|
Blob, // 100644 or 100755
|
||||||
|
Commit, // 120000
|
||||||
|
Tree, // 040000
|
||||||
|
Symlink, // 120000
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
fn from_mode(mode: &str) -> Result<Self> {
|
||||||
|
match mode {
|
||||||
|
"100644" | "100755" => Ok(Kind::Blob),
|
||||||
|
"120000" => Ok(Kind::Commit),
|
||||||
|
"040000" | "40000" => Ok(Kind::Tree),
|
||||||
|
_ => Err(anyhow::anyhow!(format!("invalid mode: {}", mode))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Kind::Blob => "blob",
|
||||||
|
Kind::Commit => "commit",
|
||||||
|
Kind::Tree => "tree",
|
||||||
|
Kind::Symlink => "symlink",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Object<Reader> {
|
||||||
|
kind: Kind,
|
||||||
|
size: usize,
|
||||||
|
data: Reader,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TreeObject {
|
||||||
|
mode: String,
|
||||||
|
kind: Kind,
|
||||||
|
name: String,
|
||||||
|
hash: [u8; 20],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_init_path() -> PathBuf {
|
||||||
|
env::var("REPO_PATH")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| PathBuf::from("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_repository(path: PathBuf) -> Result<PathBuf> {
|
||||||
let git_dir = path.join(".git");
|
let git_dir = path.join(".git");
|
||||||
|
|
||||||
fs::create_dir(&git_dir)?;
|
fs::create_dir(&git_dir)?;
|
||||||
@ -35,14 +95,129 @@ fn init_repository(path: PathBuf) -> Result<PathBuf> {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_object(path: &PathBuf, object: &str) -> Result<Object<impl BufRead>> {
|
||||||
|
let object_path = path
|
||||||
|
.join(".git")
|
||||||
|
.join("objects")
|
||||||
|
.join(&object[..2])
|
||||||
|
.join(&object[2..]);
|
||||||
|
|
||||||
|
let fd = fs::File::open(&object_path).context("opening the object")?;
|
||||||
|
let zfd = flate2::read::ZlibDecoder::new(fd);
|
||||||
|
let mut buf_reader = std::io::BufReader::new(zfd);
|
||||||
|
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
buf_reader
|
||||||
|
.read_until(0, &mut buf)
|
||||||
|
.context("read the object")?;
|
||||||
|
|
||||||
|
match buf.pop() {
|
||||||
|
Some(0) => {}
|
||||||
|
Some(_) | None => return Err(RuntimeError::UnexpectedChar.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = String::from_utf8(buf.clone()).context("converting header to utf-8")?;
|
||||||
|
|
||||||
|
let Some((object_type, object_size)) = header.split_once(' ') else {
|
||||||
|
anyhow::bail!("could not parse object header correctly");
|
||||||
|
};
|
||||||
|
|
||||||
|
let object_type = match object_type {
|
||||||
|
"blob" => Kind::Blob,
|
||||||
|
"commit" => Kind::Commit,
|
||||||
|
"tree" => Kind::Tree,
|
||||||
|
_ => anyhow::bail!("invalid object type found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let object_size = object_size.parse::<usize>()?;
|
||||||
|
|
||||||
|
Ok(Object {
|
||||||
|
kind: object_type,
|
||||||
|
size: object_size,
|
||||||
|
data: buf_reader,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: BufRead> Object<R> {
|
||||||
|
fn print(&mut self) -> Result<()> {
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
let mut buf_hash: [u8; 20] = [0; 20];
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
Kind::Blob | Kind::Commit => {
|
||||||
|
self.data.read_to_end(&mut buf)?;
|
||||||
|
println!("{}", String::from_utf8(buf)?);
|
||||||
|
}
|
||||||
|
Kind::Tree => {
|
||||||
|
let mut max_name_len = 0;
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
loop {
|
||||||
|
let read_bytes_len = self.data.read_until(0, &mut buf)?;
|
||||||
|
if read_bytes_len <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode_name = buf.clone();
|
||||||
|
buf.clear();
|
||||||
|
let mut splits = mode_name.splitn(2, |&b| b == b' ');
|
||||||
|
|
||||||
|
let mode = splits
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("could not parse mode"))?;
|
||||||
|
let mode = std::str::from_utf8(mode)?;
|
||||||
|
let name = splits
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("could not parse name"))?;
|
||||||
|
let name = std::str::from_utf8(name)?;
|
||||||
|
|
||||||
|
self.data.read_exact(&mut buf_hash)?;
|
||||||
|
|
||||||
|
if name.len() > max_name_len {
|
||||||
|
max_name_len = name.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(TreeObject {
|
||||||
|
name: name.to_string(),
|
||||||
|
kind: Kind::from_mode(mode)?,
|
||||||
|
mode: mode.to_string(),
|
||||||
|
hash: buf_hash.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let hash = hex::encode(entry.hash);
|
||||||
|
println!(
|
||||||
|
"{:0>6} {} {} {:name_len$}",
|
||||||
|
entry.mode,
|
||||||
|
entry.kind.string(),
|
||||||
|
hash,
|
||||||
|
entry.name,
|
||||||
|
name_len = max_name_len
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Error> {
|
fn main() -> Result<(), Error> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
let 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) {
|
||||||
|
Ok(mut obj) => obj.print()?,
|
||||||
|
Err(e) => eprintln!("Failed to read object: {}", e),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user