//! Tree handling utilities //! //! The way that libgit2 handles trees is super low-level and overkill //! for what we need. In this module we knock it down a notch or two. //! //! This code takes a tree returned by //! `crate::git::repo::Repo::get_tree()`, and transforms it into a //! `TreeData` type that the template engine can render. use crate::templ_data::repo::{CommitData, FileData, TreeData}; use git2::{self, ObjectType, TreeWalkMode}; use std::collections::BTreeMap; /// A cache of a repository tree #[derive(Default, Debug, Clone)] pub(crate) struct Tree { inner: BTreeMap, } impl Tree { /// Insert a node into a subtree with it's full path fn insert_to_subtree(&mut self, mut path: Vec, name: String, node: TreeNode) { // If we are given a path, resolve it first let curr = if path.len() > 0 { let rest = path.split_off(1); let mut curr = self.inner.get_mut(&path[0]).unwrap(); for dir in rest { match curr { TreeNode::Dir(ref mut d) => { curr = d.children.inner.get_mut(&dir).unwrap(); } _ => panic!("Not a tree!"), } } match curr { TreeNode::Dir(ref mut d) => &mut d.children, TreeNode::File(_) => panic!("Not a tree!"), } } else { // If no path was given, we assume the root is meant self }; curr.inner.insert(name, node); } /// Walk through the tree and only return filenode objects pub(crate) fn flatten(&self) -> Vec { self.inner.values().fold(vec![], |mut vec, node| { match node { TreeNode::File(f) => vec.push(f.clone()), TreeNode::Dir(d) => vec.append(&mut d.children.flatten()), } vec }) } /// Get all the commits that touch a file pub(crate) fn grab_path_history(&self, mut path: String) -> String { let mut path: Vec = path .split("/") .filter_map(|seg| match seg { "" => None, val => Some(val.into()), }) .collect(); let leaf = if path.len() > 0 { let rest = path.split_off(1); let mut curr = self.inner.get(&path[0]).unwrap(); for dir in rest { match curr { TreeNode::Dir(d) => curr = d.children.inner.get(&dir).unwrap(), TreeNode::File(_) => break, // we reached the leaf } } curr } else { panic!("No valid path!"); }; match leaf { TreeNode::File(f) => f.id.clone(), _ => panic!("Not a leaf!"), } } } #[derive(Clone, Debug)] pub(crate) enum TreeNode { File(FileNode), Dir(DirNode), } impl TreeNode { fn name(&self) -> String { match self { Self::File(f) => f.name.clone(), Self::Dir(d) => d.name.clone(), } } } #[derive(Clone, Debug)] pub(crate) struct FileNode { pub id: String, pub path: Vec, pub name: String, } #[derive(Clone, Debug)] pub(crate) struct DirNode { pub path: Vec, pub name: String, pub children: Tree, } impl DirNode { fn append(&mut self, node: TreeNode) { self.children.inner.insert(node.name(), node); } } /// Take a series of path-segments and render a tree at that location pub(crate) fn parse_tree(tree: git2::Tree, repo: &git2::Repository) -> Tree { let mut root = Tree::default(); tree.walk(TreeWalkMode::PreOrder, |path, entry| { let path: Vec = path .split("/") .filter_map(|seg| match seg { "" => None, val => Some(val.into()), }) .collect(); let name = entry.name().unwrap().to_string(); match entry.kind() { // For every tree in the tree we create a new TreeNode with the path we know about Some(ObjectType::Tree) => { root.insert_to_subtree( path.clone(), name.clone(), TreeNode::Dir(DirNode { path, name, children: Tree::default(), }), ); } // If we encounter a blob, this is a file that we can simply insert into the tree Some(ObjectType::Blob) => { root.insert_to_subtree( path.clone(), name.clone(), TreeNode::File(FileNode { id: format!("{}", entry.id()), path, name, }), ); } _ => {} } 0 }) .unwrap(); root }