My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/apps/servers/octopus/src/git/tree.rs

176 lines
4.9 KiB

//! 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<String, TreeNode>,
}
impl Tree {
/// Insert a node into a subtree with it's full path
fn insert_to_subtree(&mut self, mut path: Vec<String>, 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<FileNode> {
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<String> = 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<String>,
pub name: String,
}
#[derive(Clone, Debug)]
pub(crate) struct DirNode {
pub path: Vec<String>,
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<String> = 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
}