diff --git a/apps/servers/octopus/supergit/src/bin/test.rs b/apps/servers/octopus/supergit/src/bin/test.rs index 166047e9dd1..3b5fad9462c 100644 --- a/apps/servers/octopus/supergit/src/bin/test.rs +++ b/apps/servers/octopus/supergit/src/bin/test.rs @@ -10,7 +10,14 @@ fn main() { std::process::exit(2); } }; - + let rr = RawRepository::open(path.as_str()).unwrap(); - + let branches = rr.parse_branches().unwrap(); + + for branch in branches { + if branch.name.as_str() != "main" && continue {} + println!("Branch: {}", branch.name); + + branch.enumerate(" ".into(), &rr.inner); + } } diff --git a/apps/servers/octopus/supergit/src/raw/branch.rs b/apps/servers/octopus/supergit/src/raw/branch.rs new file mode 100644 index 00000000000..1e2d27ad9c2 --- /dev/null +++ b/apps/servers/octopus/supergit/src/raw/branch.rs @@ -0,0 +1,51 @@ +use super::{HashId, RawRepository}; +use crate::Branch; +use git2::{Commit, Repository}; + +/// Represent some raw branch metadata +pub struct RawBranch { + pub name: String, + pub head: HashId, +} + +fn print_commit(i: &String, c: &Commit) { + println!( + "{}{}: {}", + i, + c.id().to_string(), + c.message().unwrap().trim().split("\n").nth(0).unwrap() + ); +} + +fn print_parent_tree(c: &Commit, indent: String) { + c.parents().for_each(|c| { + println!( + "{}{}: {}", + indent, + c.id().to_string(), + c.message().unwrap().trim().split("\n").nth(0).unwrap() + ); + + print_parent_tree(&c, indent.clone()); + }); +} + +impl RawBranch { + /// Consume branch reference and enumerate real branch history + pub fn into_branch(self, repo: &mut RawRepository) -> Branch { + todo!() + } + + /// **REMOVE ME** A test function to do some test things + pub fn enumerate(&self, indent: String, repo: &Repository) { + let c = repo.find_commit((&self.head).into()).unwrap(); + println!( + "{}{}: {}", + indent, + c.id().to_string(), + c.message().unwrap().trim().split("\n").nth(0).unwrap() + ); + + print_parent_tree(&c, indent); + } +} diff --git a/apps/servers/octopus/supergit/src/raw/mod.rs b/apps/servers/octopus/supergit/src/raw/mod.rs index 7bf6c0a396a..80bff3528f3 100644 --- a/apps/servers/octopus/supergit/src/raw/mod.rs +++ b/apps/servers/octopus/supergit/src/raw/mod.rs @@ -1,13 +1,45 @@ //! Raw representation wrappers for libgit2 +mod branch; +pub use branch::RawBranch; + mod branch_walk; mod tree_walk; use crate::{Branch, BranchCommit}; -use git2::{self, Repository}; +use git2::{self, Oid, Repository}; pub type RawResult = Result; +/// The hex ID of a commit +#[derive(Debug)] +pub struct HashId(String); + +impl From for HashId { + fn from(o: Oid) -> Self { + Self(o.to_string()) + } +} + +impl From for Oid { + fn from(hid: HashId) -> Self { + Oid::from_str(hid.0.as_str()).expect(&format!( + "Tried turning an invalid HashId variant into an Oid: {:?}", + hid + )) + } +} + +impl<'any> From<&'any HashId> for Oid { + fn from(hid: &'any HashId) -> Self { + Oid::from_str(hid.0.as_str()).expect(&format!( + "Tried turning an invalid HashId variant into an Oid: {:?}", + hid + )) + } +} + + /// An error abstraction for raw git operations #[derive(Debug)] pub enum RawError { @@ -20,15 +52,9 @@ impl From for RawError { } } -/// Represent a raw branch -pub struct RawBranch { - name: String, - head: String, -} - /// Wrap a libgit2 repository to provide an API fascade pub struct RawRepository { - inner: Repository, + pub inner: Repository, } impl RawRepository { @@ -39,7 +65,30 @@ impl RawRepository { } /// Parse branch data from repository + /// + /// ## Panics + /// + /// If there is an error around getting the name, or head commit. pub fn parse_branches(&self) -> RawResult> { + Ok(self + .inner + .branches(None)? + .into_iter() + .filter_map(|e| e.ok()) + .map(|(branch, _)| { + let name = branch.name().unwrap().unwrap().into(); + let head = branch.get().peel_to_commit().unwrap().id().into(); + + RawBranch { name, head } + }) + .collect()) + } + + /// Get the files touched by a commit + pub fn get_files_for(&self, id: HashId) -> RawResult> { + let c = self.inner.find_commit(id.into())?; + let tree = c.tree()?; + todo!() } } diff --git a/apps/servers/octopus/supergit/src/raw/tree_walk.rs b/apps/servers/octopus/supergit/src/raw/tree_walk.rs index 05337640cd1..f6bb3a96490 100644 --- a/apps/servers/octopus/supergit/src/raw/tree_walk.rs +++ b/apps/servers/octopus/supergit/src/raw/tree_walk.rs @@ -1 +1,168 @@ //! Walk the file tree for a particular commit + +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, 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) -> 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 +}