aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2021-01-08 02:26:36 +0100
committerMx Kookie <kookie@spacekookie.de>2021-01-08 02:26:36 +0100
commit7a7c3f0adf32382b478b18f745ffd62dcaaa4940 (patch)
treeadba952a653849d9d23f52ab5da87ecc40078310
parent200e21a1548afda95e5c00b3772b5d14c06c1689 (diff)
octopus: supergit: implement initial tree history mechanism
-rw-r--r--apps/servers/octopus/supergit/src/commit.rs11
-rw-r--r--apps/servers/octopus/supergit/src/files/explorer.rs2
-rw-r--r--apps/servers/octopus/supergit/src/files/tree.rs149
-rw-r--r--apps/servers/octopus/supergit/src/files/tree_utils.rs41
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 58b57ce22f73..b88e01331d95 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<Repository>,
}
+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<Repository>, id: HashId) -> Option<Self> {
diff --git a/apps/servers/octopus/supergit/src/files/explorer.rs b/apps/servers/octopus/supergit/src/files/explorer.rs
index 89ad461b8ade..13433dd3cf86 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 5f4fb6671aa5..4903bad58f6f 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<Vec<Commit>> {
+ 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<Vec<TreeEntry>> {
+ 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<String>,
}
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<_>>(),
+ 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 55a6c2e0ffab..f5cb94a4031a 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<I, K>
+where
+ I: Iterator<Item = K>,
+{
+ /// Step through
+ fn pairs<'i, F: FnMut(K, Option<&K>)>(&'i mut self, cb: F);
+
+ fn next_pair<'i>(&'i mut self) -> (Option<K>, Option<&K>);
+}
+
+impl<I, K> PairIter<I, K> for Peekable<I>
+where
+ I: Iterator<Item = K>,
+{
+ 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<K>, 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));
+}