From 7a7c3f0adf32382b478b18f745ffd62dcaaa4940 Mon Sep 17 00:00:00 2001 From: Mx Kookie Date: Fri, 8 Jan 2021 02:26:36 +0100 Subject: [PATCH] octopus: supergit: implement initial tree history mechanism --- apps/servers/octopus/supergit/src/commit.rs | 11 +- .../octopus/supergit/src/files/explorer.rs | 2 + .../octopus/supergit/src/files/tree.rs | 149 +++++++++++++++++- .../octopus/supergit/src/files/tree_utils.rs | 41 ++++- 4 files changed, 191 insertions(+), 12 deletions(-) diff --git a/apps/servers/octopus/supergit/src/commit.rs b/apps/servers/octopus/supergit/src/commit.rs index 58b57ce22f7..b88e01331d9 100644 --- a/apps/servers/octopus/supergit/src/commit.rs +++ b/apps/servers/octopus/supergit/src/commit.rs @@ -1,6 +1,9 @@ use crate::{files::FileTree, Diff, HashId}; use git2::Repository; -use std::sync::Arc; +use std::{ + fmt::{self, Debug, Formatter}, + sync::Arc, +}; /// Represent a commit on a repository /// @@ -12,6 +15,12 @@ pub struct Commit { repo: Arc, } +impl Debug for Commit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.id) + } +} + impl Commit { /// Create a commit object and check if it exists in the repo pub(crate) fn new(r: &Arc, id: HashId) -> Option { diff --git a/apps/servers/octopus/supergit/src/files/explorer.rs b/apps/servers/octopus/supergit/src/files/explorer.rs index 89ad461b8ad..13433dd3cf8 100644 --- a/apps/servers/octopus/supergit/src/files/explorer.rs +++ b/apps/servers/octopus/supergit/src/files/explorer.rs @@ -1,5 +1,7 @@ +/// A dynamic tree explorer /// +/// pub struct Explorer {} /// diff --git a/apps/servers/octopus/supergit/src/files/tree.rs b/apps/servers/octopus/supergit/src/files/tree.rs index 5f4fb6671aa..4903bad58f6 100644 --- a/apps/servers/octopus/supergit/src/files/tree.rs +++ b/apps/servers/octopus/supergit/src/files/tree.rs @@ -1,7 +1,7 @@ //! Low-level abstraction over finding refs inside a commit tree -use super::tree_utils as utils; -use crate::HashId; +use super::tree_utils::{self as utils, PairIter}; +use crate::{branch::BranchIter, files::Yield, Commit, HashId}; use git2::{ObjectType, Repository, TreeWalkMode, TreeWalkResult}; use std::sync::Arc; @@ -25,6 +25,80 @@ impl FileTree { Self { repo, c } } + /// Get a FileTree for a new commit + fn checkout(&self, c: HashId) -> Self { + Self::new(Arc::clone(&self.repo), c) + } + + /// Get the history of a path with a branch iterator + pub fn base_history(&self, iter: BranchIter, path: &str) -> Option> { + let mut iter = iter.peekable(); + + let entry = self.resolve(path)?; + let trgid = entry.id(); + + let mut commits = vec![]; + + // Iterate over the branch in commit pairs + while let (Some(a), b) = iter.next_pair() { + dbg!(&a.commit()); + let ta = self.checkout(a.commit().id.clone()); + let te_a = dbg!(ta.resolve(dbg!(path))); + + let b = match b { + Some(b) => b, + None if te_a.is_some() => { + // If b doesn't exist, but the path exists in a, + // then it is safe to assume that a introduces the + // path + commits.push(a.commit().clone()); + break; + } + None => break, + }; + + let tb = self.checkout(b.commit().id.clone()); + let te_b = match ta.resolve(path) { + Some(b) => b, + None => continue, + }; + + let te_a = match te_a { + Some(a) => a, + None => continue, + }; + + // If the two tree nodes are not equal, add the commit to + // the list. This means that the `a` commit changed + // something in the path. + if dbg!(te_a != te_b) { + commits.push(a.commit().clone()); + } + } + + Some(commits) + } + + /// Enumerate a non-leaf tree entry + pub fn enumerate(&self, path: &str) -> Option> { + let tree = utils::open_tree(&self.repo, &self.c)?; + let target = utils::path_split(path); + + let mut entries = vec![]; + tree.walk(TreeWalkMode::PreOrder, |p, e| { + let path = utils::path_split(p); + if path == target { + entries.push(TreeEntry::new(p, &e)); + TreeWalkResult::Ok + } else { + TreeWalkResult::Skip + } + }) + .ok()?; + + Some(entries) + } + /// Resolve a path inside this file tree /// /// Will return `None` if there is no tree for the selected @@ -38,14 +112,15 @@ impl FileTree { // Walk over tree and swallor errors (which we use to // terminace traversal to speed up indexing time) - let _ = tree.walk(TreeWalkMode::PreOrder, |p, e| { + tree.walk(TreeWalkMode::PreOrder, |p, e| { if utils::path_cmp(&target, p, e.name().unwrap()) { entry = Some(TreeEntry::new(p, &e)); TreeWalkResult::Ok } else { TreeWalkResult::Skip } - }); + }) + .ok()?; // Return whatever the entry is now entry @@ -57,10 +132,12 @@ impl FileTree { /// This type is lazily loaded, and can represent either a Blob or a /// Directory. You can resolve its value by calling /// [`resolve()`](Self::resolve) +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TreeEntry { tt: EntryType, id: HashId, path: String, + name: Option, } impl TreeEntry { @@ -72,15 +149,28 @@ impl TreeEntry { }; let id = entry.id().into(); let path = path.into(); + let name = entry.name().map(|s| s.into()); + + Self { tt, id, path, name } + } - Self { tt, id, path } + pub fn id(&self) -> HashId { + self.id.clone() + } + + /// Get a reference to the name of this TreeEntry + pub fn name(&self) -> Option<&String> { + self.name.as_ref() } /// Resolve this type to a [`Yield`]() - pub fn resolve(&self) {} + pub fn resolve(&self) -> Yield { + todo!() + } } /// Type of a TreeEntry +#[derive(Clone, Debug, PartialEq, Eq)] pub enum EntryType { /// A file that can be loaded File, @@ -89,7 +179,7 @@ pub enum EntryType { } #[test] -fn index_tree() { +fn s_resolve() { let path = env!("CARGO_MANIFEST_DIR").to_owned() + "/test-repo"; use crate::Repository as Repo; @@ -100,5 +190,48 @@ fn index_tree() { let h = b.head(); let t = h.tree(); - t.resolve("README".into()).unwrap(); + t.resolve("README").unwrap(); +} + +#[test] +fn s_enumerate() { + let path = env!("CARGO_MANIFEST_DIR").to_owned() + "/test-repo"; + use crate::Repository as Repo; + + eprintln!("Path: `{}`", path); + + let r = Repo::open(&path).unwrap(); + let b = r.branch("master".into()).unwrap(); + let h = b.head(); + + let t = h.tree(); + let entries = t.enumerate("").unwrap(); + + assert_eq!( + entries + .iter() + .filter_map(|e| e.name().map(|s| s.as_str())) + .collect::>(), + vec!["README", "test.rs"] + ); +} + +#[test] +fn s_history() { + let path = env!("CARGO_MANIFEST_DIR").to_owned() + "/test-repo"; + use crate::Repository as Repo; + + eprintln!("Path: `{}`", path); + + let r = Repo::open(&path).unwrap(); + let b = r.branch("master".into()).unwrap(); + + let head = b.head(); + let iter = b.get_all(); + + let tree = head.tree(); + let history = tree.base_history(iter, "test.rs").unwrap(); + + dbg!(&history); + assert_eq!(history.len(), 1); } diff --git a/apps/servers/octopus/supergit/src/files/tree_utils.rs b/apps/servers/octopus/supergit/src/files/tree_utils.rs index 55a6c2e0ffa..f5cb94a4031 100644 --- a/apps/servers/octopus/supergit/src/files/tree_utils.rs +++ b/apps/servers/octopus/supergit/src/files/tree_utils.rs @@ -2,7 +2,33 @@ use crate::HashId; use git2::{Repository, Tree}; -use std::{path::PathBuf, sync::Arc}; +use std::{iter::Peekable, path::PathBuf, sync::Arc}; + +pub(super) trait PairIter +where + I: Iterator, +{ + /// Step through + fn pairs<'i, F: FnMut(K, Option<&K>)>(&'i mut self, cb: F); + + fn next_pair<'i>(&'i mut self) -> (Option, Option<&K>); +} + +impl PairIter for Peekable +where + I: Iterator, +{ + fn pairs<'i, F: FnMut(K, Option<&K>)>(&'i mut self, mut cb: F) { + // Iterate until a->None + while let (Some(a), b) = self.next_pair() { + cb(a, b); + } + } + + fn next_pair<'i>(&'i mut self) -> (Option, Option<&K>) { + (self.next(), self.peek()) + } +} /// Take a vector of path segments, and turn it into a valid offset path /// @@ -32,8 +58,6 @@ pub(super) fn path_cmp(target: &Vec<&str>, path: &str, entry: &str) -> bool { let mut buf = path_split(path); buf.push(entry); - eprintln!("{:?}", buf); - target == &buf } @@ -59,3 +83,14 @@ fn nested_path() { String::from("foo/bar/baz") ); } + +#[test] +fn pair_iterator() { + // The pair iterator needs access to `peek()` + let mut i = vec![1, 2, 3, 4].into_iter().peekable(); + + assert_eq!(i.next_pair(), (Some(1), Some(&2))); + assert_eq!(i.next_pair(), (Some(2), Some(&3))); + assert_eq!(i.next_pair(), (Some(3), Some(&4))); + assert_eq!(i.next_pair(), (Some(4), None)); +}