chore: split into modules
This commit is contained in:
parent
33623f9b46
commit
95d37159fc
5
src/error.rs
Normal file
5
src/error.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum RuntimeError {
|
||||||
|
#[error("Invalid character found")]
|
||||||
|
UnexpectedChar,
|
||||||
|
}
|
31
src/kind.rs
Normal file
31
src/kind.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Kind {
|
||||||
|
Blob, // 100644 or 100755
|
||||||
|
Commit, // 160000
|
||||||
|
Tree, // 040000
|
||||||
|
Symlink, // 120000
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
pub fn from_mode(mode: &str) -> Result<Self> {
|
||||||
|
match mode {
|
||||||
|
"100644" | "100755" => Ok(Kind::Blob),
|
||||||
|
"160000" => Ok(Kind::Commit),
|
||||||
|
"120000" => Ok(Kind::Symlink),
|
||||||
|
"040000" | "40000" => Ok(Kind::Tree),
|
||||||
|
|
||||||
|
_ => Err(anyhow!(format!("invalid mode: {}", mode))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Kind::Blob => "blob",
|
||||||
|
Kind::Commit => "commit",
|
||||||
|
Kind::Tree => "tree",
|
||||||
|
Kind::Symlink => "symlink",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
src/main.rs
173
src/main.rs
@ -1,12 +1,16 @@
|
|||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod kind;
|
||||||
|
mod object;
|
||||||
|
|
||||||
|
use crate::object::read_object;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "mg", about = "A simple git clone")]
|
#[command(name = "mg", about = "A simple git clone")]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
@ -29,57 +33,6 @@ enum Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum RuntimeError {
|
|
||||||
#[error("Invalid character found")]
|
|
||||||
UnexpectedChar,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Kind {
|
|
||||||
Blob, // 100644 or 100755
|
|
||||||
Commit, // 160000
|
|
||||||
Tree, // 040000
|
|
||||||
Symlink, // 120000
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Kind {
|
|
||||||
fn from_mode(mode: &str) -> Result<Self> {
|
|
||||||
match mode {
|
|
||||||
"100644" | "100755" => Ok(Kind::Blob),
|
|
||||||
"160000" => Ok(Kind::Commit),
|
|
||||||
"120000" => Ok(Kind::Symlink),
|
|
||||||
"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 {
|
fn default_init_path() -> PathBuf {
|
||||||
env::var("REPO_PATH")
|
env::var("REPO_PATH")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
@ -98,116 +51,6 @@ fn init_repository(path: PathBuf) -> Result<PathBuf> {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_object(path: &Path, 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
let path = default_init_path();
|
||||||
@ -218,7 +61,7 @@ fn main() -> Result<(), Error> {
|
|||||||
Err(e) => eprintln!("Failed to initialize repository: {}", e),
|
Err(e) => eprintln!("Failed to initialize repository: {}", e),
|
||||||
},
|
},
|
||||||
Command::CatFile { hash } => match read_object(&path, &hash) {
|
Command::CatFile { hash } => match read_object(&path, &hash) {
|
||||||
Ok(mut obj) => obj.print()?,
|
Ok(mut obj) => println!("{}", obj.to_string()?),
|
||||||
Err(e) => eprintln!("Failed to read object: {}", e),
|
Err(e) => eprintln!("Failed to read object: {}", e),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
133
src/object.rs
Normal file
133
src/object.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{fs::File, io::BufRead, path::Path};
|
||||||
|
|
||||||
|
use crate::{error::RuntimeError, kind::Kind};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Object<Reader> {
|
||||||
|
kind: Kind,
|
||||||
|
_size: usize,
|
||||||
|
data: Reader,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TreeObject {
|
||||||
|
mode: String,
|
||||||
|
kind: Kind,
|
||||||
|
name: String,
|
||||||
|
hash: [u8; 20],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_object(path: &Path, object: &str) -> Result<Object<impl BufRead>> {
|
||||||
|
let object_path = path
|
||||||
|
.join(".git")
|
||||||
|
.join("objects")
|
||||||
|
.join(&object[..2])
|
||||||
|
.join(&object[2..]);
|
||||||
|
|
||||||
|
let fd = 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> {
|
||||||
|
pub fn to_string(&mut self) -> Result<String> {
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
let mut buf_hash: [u8; 20] = [0; 20];
|
||||||
|
|
||||||
|
let res = match self.kind {
|
||||||
|
Kind::Blob | Kind::Commit => {
|
||||||
|
self.data.read_to_end(&mut buf)?;
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
|
let hash = hex::encode(entry.hash);
|
||||||
|
format!(
|
||||||
|
"{:0>6} {} {} {:name_len$}",
|
||||||
|
entry.mode,
|
||||||
|
entry.kind.string(),
|
||||||
|
hash,
|
||||||
|
entry.name,
|
||||||
|
name_len = max_name_len
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user