octopus: supergit: implement initial tree history mechanism

wip/yesman
Katharina Fey 3 years ago
parent 200e21a154
commit 7a7c3f0adf
  1. 11
      apps/servers/octopus/supergit/src/commit.rs
  2. 2
      apps/servers/octopus/supergit/src/files/explorer.rs
  3. 149
      apps/servers/octopus/supergit/src/files/tree.rs
  4. 41
      apps/servers/octopus/supergit/src/files/tree_utils.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> {

@ -1,5 +1,7 @@
/// A dynamic tree explorer
///
///
pub struct Explorer {}
///

@ -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);
}

@ -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));
}

Loading…
Cancel
Save