use crate::{Branch, BranchIter, Commit, HashId}; use git2::{ObjectType, TreeWalkMode, TreeWalkResult}; use atomptr::AtomPtr; use std::collections::BTreeMap; use std::{path::PathBuf, sync::Arc}; /// A tree of files pub struct FileTree { repo: Arc, tree: AtomPtr>, } impl FileTree { /// Utility function to create a tree, and then parse it too pub(crate) fn new(repo: &Arc, commit: HashId) -> Arc { Arc::new(Self { repo: Arc::clone(repo), tree: AtomPtr::new(BTreeMap::new()), }) .parse(commit) } /// Parse a tree from a specific commit pub(crate) fn parse(self: Arc, commit: HashId) -> Arc { let mut new_tree = BTreeMap::new(); let tree = (&self.repo) .find_commit(commit.to_oid()) .unwrap() .tree() .unwrap(); tree.walk(TreeWalkMode::PreOrder, |what, entry| { let path_segs: Vec<_> = what.split("/").filter(|s| s != &"").collect(); let path = if path_segs.len() == 0 { None } else { Some(path_segs) }; println!("{:?} {}", path, entry.name().unwrap()); TreeWalkResult::Ok }) .unwrap(); drop(tree); // Atomicly swap new tree into place self.tree.swap(new_tree); self } } /// An entry in a file tree /// /// It's variants can either be a file (leaf), or a subtree, with it's /// own path handles, and children. pub enum TreeEntry { /// A single file File(File), /// A sub-tree Dir(Directory), } impl TreeEntry { /// Create a tree entry from a path and `git2::TreeEntry` fn generate(root: PathBuf, path_segments: Option>, entry: git2::TreeEntry) -> Self { let path = path_segments.map_or("".into(), |p| path_segs_join(p)); match entry.kind() { Some(ObjectType::Blob) => Self::File(File::new(root, path)), Some(ObjectType::Tree) => Self::Dir(Directory::new(root, path)), _ => unimplemented!(), } } /// Load this tree entry from disk, if it is a file /// /// When calling this function on a directory, nothing will /// happen, because directories can't be loaded. If you want to /// get a list of children for a directory, use /// [`FileTree::enumerate()`]() instead! pub fn load(&self) -> Option> { if !self.is_file() { return None; } let obj = } /// Check if this tree entry is a file pub fn is_file(&self) -> bool { match self { Self::File(_) => true, Self::Dir(_) => false, } } } /// A file to have ever existed in a git repo pub struct File { root: PathBuf, path: String, } impl File { pub(crate) fn new(root: PathBuf, path: String) -> Self { Self { root, path } } /// Get the history of a file from a branch iterator pub fn get_history(&self, branch: BranchIter) -> Vec { todo!() } } /// A subdirectory in a file tree /// /// A directory has a set of children, which can either be Files, or /// other directories. Many of the functions to retrieve metadata /// (such as the last commit, count, etc) will be deferred to the /// children of this directory. pub struct Directory { root: PathBuf, path: String, } impl Directory { pub(crate) fn new(root: PathBuf, path: String) -> Self { Self { root, path } } } /// Take a vector of path segments, and turn it into a valid offset path /// /// There are tests to make sure this function works properly. /// Following are some example transformations. /// /// * vec![] -> "" /// * vec!["foo"] -> "foo" /// * vec!["foo", "bar", "baz"] -> "foo/bar/baz" fn path_segs_join(segments: Vec) -> String { segments .into_iter() .fold(PathBuf::new(), |buf, seg| buf.join(seg)) .as_path() .to_str() .unwrap() .to_owned() } #[test] fn empty_path() { assert_eq!(path_segs_join(vec![]), String::from("")); } #[test] fn one_path() { assert_eq!(path_segs_join(vec!["foo".into()]), String::from("foo")); } #[test] fn nested_path() { assert_eq!( path_segs_join(vec!["foo".into(), "bar".into(), "baz".into()]), String::from("foo/bar/baz") ); }