use crate::{Branch, BranchIter, Commit, HashId};
use atomptr::AtomPtr;
use git2::{ObjectType, TreeWalkMode, TreeWalkResult};
use std::collections::{BTreeMap, BTreeSet};
use std::{path::PathBuf, sync::Arc};
pub struct FileTree {
repo: Arc<git2::Repository>,
tree: AtomPtr<BTreeMap<String, Arc<TreeEntry>>>,
}
impl FileTree {
pub(crate) fn new(repo: &Arc<git2::Repository>, commit: HashId) -> Arc<Self> {
Arc::new(Self {
repo: Arc::clone(repo),
tree: AtomPtr::new(BTreeMap::new()),
})
.parse(commit)
}
pub(crate) fn parse(self: Arc<Self>, commit: HashId) -> Arc<Self> {
let mut new_tree = BTreeMap::new();
let tree = (&self.repo)
.find_commit(commit.to_oid())
.unwrap()
.tree()
.unwrap();
tree.walk(TreeWalkMode::PreOrder, |p, entry| {
let path_segs: Vec<_> = p.split("/").filter(|s| s != &"").collect();
let path = if path_segs.len() == 0 {
None
} else {
Some(path_segs)
};
let te = TreeEntry::generate(path, entry);
new_tree.insert(te.path(), Arc::new(te));
TreeWalkResult::Ok
})
.unwrap();
new_tree.insert(
"".into(),
Arc::new(TreeEntry::Dir(Directory {
id: tree.id().into(),
path: "".into(),
name: "".into(),
})),
);
drop(tree);
self.tree.swap(new_tree);
self
}
fn get_entry(&self, path: &str) -> Option<Arc<TreeEntry>> {
self.tree.get_ref().get(path).map(|e| Arc::clone(&e))
}
pub fn load(self: &Arc<Self>, path: &str) -> Option<Yield> {
self.get_entry(path).and_then(|e| e.load(self))
}
pub fn history(&self, iter: BranchIter, path: &str) -> Vec<Commit> {
iter.filter_map(|c| {
if c.commit()
.get_paths()
.into_iter()
.collect::<BTreeSet<_>>()
.contains(path)
{
Some(c.commit().clone())
} else {
None
}
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Yield {
File(Vec<u8>),
Dir(Vec<YieldEntry>),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum YieldEntry {
File(String),
Dir(String),
}
#[derive(Debug)]
enum TreeEntry {
File(File),
Dir(Directory),
}
impl TreeEntry {
fn generate(path_segments: Option<Vec<&str>>, entry: &git2::TreeEntry) -> Self {
let path = path_segments.map_or("".into(), |p| path_segs_join(p));
let id = entry.id().into();
let name = entry.name().unwrap().into();
match entry.kind() {
Some(ObjectType::Blob) => Self::File(File::new(id, path, name)),
Some(ObjectType::Tree) => Self::Dir(Directory::new(id, path, name)),
_ => unimplemented!(),
}
}
fn load(&self, ft: &Arc<FileTree>) -> Option<Yield> {
let id = self.id();
let repo = &ft.repo;
match self {
Self::File(_) => repo
.find_blob(id.into())
.ok()
.map(|b| Yield::File(b.content().into())),
Self::Dir(_) => repo
.find_tree(id.into())
.ok()
.map(|tree| {
let mut children = vec![];
tree.walk(TreeWalkMode::PreOrder, |p, entry| {
let path_segs: Vec<_> = p.split("/").filter(|s| s != &"").collect();
if path_segs.len() > 0 {
TreeWalkResult::Skip
} else {
let path = PathBuf::new().join(self.path()).join(entry.name().unwrap());
let s: String = path.as_path().to_str().unwrap().into();
children.push(if ft.get_entry(s.as_str()).unwrap().is_directory() {
YieldEntry::Dir(s)
} else {
YieldEntry::File(s)
});
TreeWalkResult::Ok
}
})
.unwrap();
children
})
.map(|c| Yield::Dir(c)),
}
}
fn is_directory(&self) -> bool {
match self {
Self::Dir(_) => true,
Self::File(_) => false,
}
}
fn id(&self) -> HashId {
match self {
Self::File(ref f) => f.id.clone(),
Self::Dir(ref d) => d.id.clone(),
}
}
fn path(&self) -> String {
match self {
Self::File(ref f) => PathBuf::new().join(&f.path).join(&f.name),
Self::Dir(ref d) => PathBuf::new().join(&d.path).join(&d.name),
}
.as_path()
.to_str()
.unwrap()
.into()
}
}
#[derive(Debug)]
struct File {
id: HashId,
path: String,
name: String,
}
impl File {
fn new(id: HashId, path: String, name: String) -> Self {
Self { id, path, name }
}
}
#[derive(Debug)]
struct Directory {
id: HashId,
path: String,
name: String,
}
impl Directory {
fn new(id: HashId, path: String, name: String) -> Self {
Self { id, path, name }
}
#[allow(unused)]
fn enumerate(&self, repo: git2::Repository) -> Vec<String> {
vec![]
}
}