use bytes::Bytes;
use indexmap::IndexMap;
use super::low_level::{
Commit, CommitUserInfo, HashOutput, PackFileEntry, TreeItem as LowLevelTreeItem, TreeItemKind,
};
#[derive(Default, Debug)]
pub struct GitRepository {
packfile_entries: IndexMap<HashOutput, PackFileEntry>,
tree: Tree,
}
impl GitRepository {
pub fn insert(
&mut self,
path: Vec<String>,
file: String,
content: Bytes,
) -> Result<(), anyhow::Error> {
let mut directory = &mut self.tree;
for part in path {
let tree_item = directory
.0
.entry(part)
.or_insert_with(|| Box::new(TreeItem::Tree(Tree::default())));
if let TreeItem::Tree(d) = tree_item.as_mut() {
directory = d;
} else {
anyhow::bail!("attempted to use a file as a directory");
}
}
let entry = PackFileEntry::Blob(content);
let file_hash = entry.hash()?;
directory
.0
.insert(file, Box::new(TreeItem::Blob(file_hash)));
self.packfile_entries.insert(file_hash, entry);
Ok(())
}
pub fn commit(
mut self,
name: &'static str,
email: &'static str,
message: &'static str,
) -> Result<(HashOutput, Vec<PackFileEntry>), anyhow::Error> {
let tree_hash = self
.tree
.into_packfile_entries(&mut self.packfile_entries)?;
let commit_user = CommitUserInfo {
name,
email,
time: time::OffsetDateTime::now_utc(),
};
let commit = PackFileEntry::Commit(Commit {
tree: tree_hash,
author: commit_user.clone(),
committer: commit_user,
message,
});
let commit_hash = commit.hash()?;
self.packfile_entries.insert(commit_hash, commit);
Ok((
commit_hash,
self.packfile_entries.into_iter().map(|(_, v)| v).collect(),
))
}
}
#[derive(Default, Debug)]
struct Tree(IndexMap<String, Box<TreeItem>>);
impl Tree {
fn into_packfile_entries(
self,
pack_file: &mut IndexMap<HashOutput, PackFileEntry>,
) -> Result<HashOutput, anyhow::Error> {
let mut tree = Vec::with_capacity(self.0.len());
for (name, item) in self.0 {
tree.push(match *item {
TreeItem::Blob(hash) => LowLevelTreeItem {
kind: TreeItemKind::File,
sort_name: name.clone(),
name,
hash,
},
TreeItem::Tree(tree) => LowLevelTreeItem {
kind: TreeItemKind::Directory,
sort_name: format!("{}/", name),
name,
hash: tree.into_packfile_entries(pack_file)?,
},
});
}
tree.sort_unstable_by(|a, b| a.sort_name.cmp(&b.sort_name));
let tree = PackFileEntry::Tree(tree);
let hash = tree.hash()?;
pack_file.insert(hash, tree);
Ok(hash)
}
}
#[derive(Debug)]
enum TreeItem {
Blob(HashOutput),
Tree(Tree),
}