use anyhow::Result;
use flate2::read::ZlibDecoder;
use std::fs::{read, rename, File};
use std::io::{prelude::*, Cursor};
use std::path::PathBuf;
use crate::database::{Blob, Commit, Tree};
use crate::refs::Refs;
use crate::utils::get_root_path;
use crate::{database::Storable, utils::compress_content, utils::hash_content};
use super::tree::FlatTree;
#[derive(Debug)]
pub enum FileMode {
Regular,
Executable,
Directory,
Unknown,
}
impl From<FileMode> for u32 {
fn from(mode: FileMode) -> u32 {
match mode {
FileMode::Regular => 0o100644,
FileMode::Executable => 0o100755,
FileMode::Directory => 0o040000,
FileMode::Unknown => 0,
}
}
}
impl FileMode {
pub fn from_str(mode: &str) -> Self {
match mode {
"100644" => FileMode::Regular,
"100755" => FileMode::Executable,
"040000" => FileMode::Directory,
_ => FileMode::Unknown,
}
}
}
#[derive(Debug)]
pub enum ObjectType {
Blob,
Tree,
Commit,
Unknown,
}
impl ObjectType {
pub fn from_str(object_type: &str) -> Self {
match object_type {
"blob" => ObjectType::Blob,
"tree" => ObjectType::Tree,
"commit" => ObjectType::Commit,
_ => ObjectType::Unknown,
}
}
}
pub struct Header {
pub object_type: ObjectType,
pub object_size: usize,
}
impl Header {
pub fn parse(oid: &str) -> Self {
let object_path = PathBuf::from(".rgit/objects")
.join(&oid[0..2])
.join(&oid[2..]);
let data = read(object_path).unwrap();
let mut decompressed = ZlibDecoder::new(&data[..]);
let mut buffer = Vec::new();
decompressed.read_to_end(&mut buffer).unwrap();
let mut cursor = Cursor::new(buffer);
let mut header = Vec::new();
cursor.read_until(b'\0', &mut header).unwrap();
let header = String::from_utf8(header).unwrap();
let mut parts = header.split(' ');
let object_type = parts.next().unwrap();
let object_type = ObjectType::from_str(object_type);
let object_size = parts
.next()
.unwrap()
.trim_end_matches('\0')
.parse::<usize>()
.unwrap();
Header {
object_type,
object_size,
}
}
}
pub struct Content {
pub header: Header,
pub body: Vec<u8>,
}
impl Content {
pub fn parse(oid: &str) -> Result<Self> {
let object_path = PathBuf::from(".rgit/objects")
.join(&oid[0..2])
.join(&oid[2..]);
let data = read(object_path)?;
let mut decoder = ZlibDecoder::new(&data[..]);
let mut buffer = Vec::new();
decoder.read_to_end(&mut buffer)?;
let mut cursor = Cursor::new(buffer);
let mut header = Vec::new();
cursor.read_until(b'\0', &mut header)?;
let mut body = Vec::new();
cursor.read_to_end(&mut body)?;
let header = Header::parse(&oid);
Ok(Content { header, body })
}
}
pub struct Database {
pub object_store: PathBuf,
pub objects: Vec<String>,
}
#[derive(Debug)]
pub enum ParsedContent {
BlobContent(Blob),
CommitContent(Commit),
TreeContent(FlatTree),
}
impl<'a> Database {
pub fn new(object_store: PathBuf) -> Self {
Self {
object_store,
objects: Default::default(),
}
}
pub fn read_object(&self, oid: &str) -> Result<ParsedContent> {
let header = Header::parse(oid);
let parsed_content = match header.object_type {
ObjectType::Blob => ParsedContent::BlobContent(Blob::parse(oid.to_owned())),
ObjectType::Commit => ParsedContent::CommitContent(Commit::parse(oid.to_owned())),
ObjectType::Tree => {
let file_or_dir = Tree::parse(oid.to_owned());
ParsedContent::TreeContent(FlatTree {
entries: file_or_dir,
})
}
ObjectType::Unknown => {
panic!("Unknown object type");
}
};
Ok(parsed_content)
}
pub fn read_head(&self) -> Result<FlatTree> {
let root_part = get_root_path()?;
let git_path = root_part.join(".rgit");
let refs = Refs::new(git_path);
let parent = refs.read_head();
let tree = match parent {
Some(oid) => {
let commit = self.read_object(&oid).unwrap();
match commit {
ParsedContent::CommitContent(commit) => {
let tree_oid = commit.tree;
let tree = self.read_object(&tree_oid).unwrap();
match tree {
ParsedContent::TreeContent(tree) => tree,
_ => panic!("should not happen"),
}
}
_ => panic!("should not happen"),
}
}
None => FlatTree {
entries: Default::default(),
},
};
Ok(tree)
}
pub fn store<T>(&self, storable: &mut T) -> Result<()>
where
T: Storable,
{
let content = storable.data();
let content = format!("{} {}\0{}", storable.blob_type(), content.len(), content);
let hashed_content = hash_content(&content);
storable.set_oid(hashed_content.to_owned());
self.write_object(&hashed_content, &content)?;
Ok(())
}
pub fn write_object(&self, name: &str, content: &str) -> Result<()> {
let object_path = PathBuf::from(&self.object_store).join(&name[0..2]);
std::fs::create_dir_all(&object_path)?;
let object_name = object_path.join(&name[2..]);
if object_name.exists() {
return Ok(());
}
let temp_file_path =
PathBuf::from(object_path).join(format!("{}.tmp", object_name.display()));
let temp_file = File::create(&temp_file_path)?;
let compressed_content = compress_content(content);
let mut buffer = std::io::BufWriter::new(&temp_file);
buffer.write_all(&compressed_content)?;
rename(&temp_file_path, &object_name)?;
Ok(())
}
}