diff --git a/apps/servers/octopus/supergit/src/bin/test.rs b/apps/servers/octopus/supergit/src/bin/test.rs index 6b9a67b7581..78367ea2c6b 100644 --- a/apps/servers/octopus/supergit/src/bin/test.rs +++ b/apps/servers/octopus/supergit/src/bin/test.rs @@ -1,33 +1,38 @@ //! A test binary to use during development -use std::sync::mpsc::channel; -use supergit::{BranchCommit, Repository}; +// use std::sync::mpsc::channel; +// use supergit::{BranchCommit, Repository}; -fn main() { - let path = match std::env::args().nth(1) { - Some(p) => p, - None => { - eprintln!("USAGE: supergit-test "); - std::process::exit(2); - } - }; +// fn main() { +// let path = match std::env::args().nth(1) { +// Some(p) => p, +// None => { +// eprintln!("USAGE: supergit-test "); +// std::process::exit(2); +// } +// }; + +// let repo = Repository::open(path.as_str()).unwrap(); +// let branches = repo.branches().unwrap(); +// let main = branches +// .into_iter() +// .filter(|b| b.name() == Some("master".to_string())) +// .nth(0) +// .unwrap(); - let repo = Repository::open(path.as_str()).unwrap(); - let branches = repo.branches().unwrap(); - let main = branches - .into_iter() - .filter(|b| b.name() == Some("master".to_string())) - .nth(0) - .unwrap(); +// let head = main.get_head(); +// let tree = head.get_tree(); + +// println!( +// "{:#?}", +// tree.history(main.get_all(), "Cargo.toml") +// .into_iter() +// .map(|c| c.summary()) +// .collect::>() +// ); +// } - let head = main.get_head(); - let tree = head.get_tree(); + +fn main() { - println!( - "{:#?}", - tree.history(main.get_all(), "Cargo.toml") - .into_iter() - .map(|c| c.summary()) - .collect::>() - ); } diff --git a/apps/servers/octopus/supergit/src/branch.rs b/apps/servers/octopus/supergit/src/branch.rs index bbbf96dbf2c..7d62941bdb5 100644 --- a/apps/servers/octopus/supergit/src/branch.rs +++ b/apps/servers/octopus/supergit/src/branch.rs @@ -1,3 +1,5 @@ +//! Type system for working with git branches + use crate::{Commit, HashId}; use atomptr::AtomPtr; use git2::Repository; @@ -114,7 +116,7 @@ impl Branch { } /// Get the current HEAD commit - pub fn get_head(&self) -> Commit { + pub fn head(&self) -> Commit { Commit::new(&self.repo, self.head.clone()).unwrap() } @@ -326,7 +328,7 @@ pub enum IterMode { BreadthFirst, } -/// the limit applied to a branch segment +/// Limiter applied to a branch segment pub enum SegLimit { /// No limit, enumerating all children None, diff --git a/apps/servers/octopus/supergit/src/commit.rs b/apps/servers/octopus/supergit/src/commit.rs index 99c4dbba6fc..58b57ce22f7 100644 --- a/apps/servers/octopus/supergit/src/commit.rs +++ b/apps/servers/octopus/supergit/src/commit.rs @@ -1,4 +1,4 @@ -use crate::{Diff, FileTree, HashId}; +use crate::{files::FileTree, Diff, HashId}; use git2::Repository; use std::sync::Arc; @@ -67,8 +67,8 @@ impl Commit { } /// Get the file tree for this commit - pub fn get_tree(&self) -> Arc { - FileTree::new(&self.repo, self.id.clone()) + pub fn tree(&self) -> FileTree { + FileTree::new(Arc::clone(&self.repo), self.id.clone()) } /// Get the list of paths in the repository touched by this commit diff --git a/apps/servers/octopus/supergit/src/files/explorer.rs b/apps/servers/octopus/supergit/src/files/explorer.rs new file mode 100644 index 00000000000..89ad461b8ad --- /dev/null +++ b/apps/servers/octopus/supergit/src/files/explorer.rs @@ -0,0 +1,9 @@ + +/// +pub struct Explorer {} + +/// +pub struct Yield {} + +/// +pub enum YieldType {} diff --git a/apps/servers/octopus/supergit/src/files/mod.rs b/apps/servers/octopus/supergit/src/files/mod.rs new file mode 100644 index 00000000000..d278818b23a --- /dev/null +++ b/apps/servers/octopus/supergit/src/files/mod.rs @@ -0,0 +1,15 @@ +//! File tree abstractions +//! +//! The supergit files API is split into two parts: `FileTree`, which +//! is mostly used internally and only exposed to allow external tools +//! to rely on the same indexing mechanism as supergit, and +//! `Explorer`, which is a high-level API for loading trees for +//! specific commits. +//! + +pub(self) mod tree_utils; +mod tree; +pub use tree::{FileTree, TreeEntry, EntryType}; + +mod explorer; +pub use explorer::{Explorer, Yield, YieldType}; diff --git a/apps/servers/octopus/supergit/src/files/tree.rs b/apps/servers/octopus/supergit/src/files/tree.rs new file mode 100644 index 00000000000..5f4fb6671aa --- /dev/null +++ b/apps/servers/octopus/supergit/src/files/tree.rs @@ -0,0 +1,104 @@ +//! Low-level abstraction over finding refs inside a commit tree + +use super::tree_utils as utils; +use crate::HashId; +use git2::{ObjectType, Repository, TreeWalkMode, TreeWalkResult}; +use std::sync::Arc; + +/// A git directory tree walker abstraction +/// +/// This type is meant to be used ephemerally, and internally uses the +/// libgit2 `Tree` abstraction to walk directory trees lazily to +/// resolve paths to [`TreeEntry`](self::TreeEntry)'s. +/// +/// Note: this type _may_ be removed in the future. For a more +/// high-level (and stable) API, check +/// [`Explorer`](crate::files::Explorer) +pub struct FileTree { + repo: Arc, + c: HashId, +} + +impl FileTree { + /// Construct a new FileTree with a repository + pub(crate) fn new(repo: Arc, c: HashId) -> Self { + Self { repo, c } + } + + /// Resolve a path inside this file tree + /// + /// Will return `None` if there is no tree for the selected + /// commit, or the file inside the tree does not exist. + pub fn resolve(&self, path: &str) -> Option { + let tree = utils::open_tree(&self.repo, &self.c)?; + let target = utils::path_split(path); + + // Initialise entry to None as a fallback + let mut entry = None; + + // Walk over tree and swallor errors (which we use to + // terminace traversal to speed up indexing time) + let _ = 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 + } + }); + + // Return whatever the entry is now + entry + } +} + +/// An entry in a commit tree +/// +/// This type is lazily loaded, and can represent either a Blob or a +/// Directory. You can resolve its value by calling +/// [`resolve()`](Self::resolve) +pub struct TreeEntry { + tt: EntryType, + id: HashId, + path: String, +} + +impl TreeEntry { + fn new(path: &str, entry: &git2::TreeEntry) -> Self { + let tt = match entry.kind() { + Some(ObjectType::Blob) => EntryType::File, + Some(ObjectType::Tree) => EntryType::Dir, + _ => unimplemented!(), + }; + let id = entry.id().into(); + let path = path.into(); + + Self { tt, id, path } + } + + /// Resolve this type to a [`Yield`]() + pub fn resolve(&self) {} +} + +/// Type of a TreeEntry +pub enum EntryType { + /// A file that can be loaded + File, + /// A directory that can be indexed + Dir, +} + +#[test] +fn index_tree() { + 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(); + t.resolve("README".into()).unwrap(); +} diff --git a/apps/servers/octopus/supergit/src/files/tree_utils.rs b/apps/servers/octopus/supergit/src/files/tree_utils.rs new file mode 100644 index 00000000000..55a6c2e0ffa --- /dev/null +++ b/apps/servers/octopus/supergit/src/files/tree_utils.rs @@ -0,0 +1,61 @@ +//! A set of tree utilities used internally in the tree.rs module + +use crate::HashId; +use git2::{Repository, Tree}; +use std::{path::PathBuf, sync::Arc}; + +/// Take a vector of path segments, and turn it into a valid offset path +/// +/// There are tests to make sure this function works properly. +/// Following are some example transformations. +/// +/// * vec![] -> "" +/// * vec!["foo"] -> "foo" +/// * vec!["foo", "bar", "baz"] -> "foo/bar/baz" +pub(super) fn path_segs_join(segments: Vec<&str>) -> String { + segments + .into_iter() + .fold(PathBuf::new(), |buf, seg| buf.join(seg)) + .as_path() + .to_str() + .unwrap() + .to_owned() +} + +/// Take an offset path inside the repository and split it into a vec +pub(super) fn path_split(path: &str) -> Vec<&str> { + path.split("/").filter(|s| s != &"").collect() +} + +/// Compare a path and entry to a target buffer +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 +} + +/// Open a tree for a particular commit +pub(super) fn open_tree<'r>(repo: &'r Arc, c: &HashId) -> Option> { + repo.find_commit(c.to_oid()).unwrap().tree().ok() +} + +#[test] +fn empty_path() { + assert_eq!(path_segs_join(vec![]), String::from("")); +} + +#[test] +fn one_path() { + assert_eq!(path_segs_join(vec!["foo".into()]), String::from("foo")); +} + +#[test] +fn nested_path() { + assert_eq!( + path_segs_join(vec!["foo".into(), "bar".into(), "baz".into()]), + String::from("foo/bar/baz") + ); +} diff --git a/apps/servers/octopus/supergit/src/lib.rs b/apps/servers/octopus/supergit/src/lib.rs index 17c9094c32d..e7414c2862a 100644 --- a/apps/servers/octopus/supergit/src/lib.rs +++ b/apps/servers/octopus/supergit/src/lib.rs @@ -15,8 +15,7 @@ //! are very computationally intensive, and will be marked with their //! runtime cost. -mod branch; -pub use branch::{Branch, BranchIter, BranchCommit}; +pub mod branch; mod commit; pub use commit::Commit; @@ -28,8 +27,11 @@ mod repo; pub(crate) use repo::HashId; pub use repo::Repository; -mod files; -pub use files::{Yield, FileTree}; +pub mod files; -use async_std::sync::{Arc, RwLock}; -use std::sync::atomic::{AtomicUsize, Ordering}; +/// Contains all core functions and types in supergit +pub mod prelude { + pub use crate::branch::{Branch, BranchIter}; + pub use crate::files::{EntryType, Explorer, TreeEntry}; + pub use crate::{Commit, Diff, Repository}; +} diff --git a/apps/servers/octopus/supergit/src/files.rs b/apps/servers/octopus/supergit/src/old-files.rs similarity index 89% rename from apps/servers/octopus/supergit/src/files.rs rename to apps/servers/octopus/supergit/src/old-files.rs index c593a492392..a4b10513c7f 100644 --- a/apps/servers/octopus/supergit/src/files.rs +++ b/apps/servers/octopus/supergit/src/old-files.rs @@ -230,39 +230,3 @@ impl Directory { } //////////////////////////////// - -/// Take a vector of path segments, and turn it into a valid offset path -/// -/// There are tests to make sure this function works properly. -/// Following are some example transformations. -/// -/// * vec![] -> "" -/// * vec!["foo"] -> "foo" -/// * vec!["foo", "bar", "baz"] -> "foo/bar/baz" -fn path_segs_join(segments: Vec<&str>) -> String { - segments - .into_iter() - .fold(PathBuf::new(), |buf, seg| buf.join(seg)) - .as_path() - .to_str() - .unwrap() - .to_owned() -} - -#[test] -fn empty_path() { - assert_eq!(path_segs_join(vec![]), String::from("")); -} - -#[test] -fn one_path() { - assert_eq!(path_segs_join(vec!["foo".into()]), String::from("foo")); -} - -#[test] -fn nested_path() { - assert_eq!( - path_segs_join(vec!["foo".into(), "bar".into(), "baz".into()]), - String::from("foo/bar/baz") - ); -} diff --git a/apps/servers/octopus/supergit/src/repo.rs b/apps/servers/octopus/supergit/src/repo.rs index 3d802a929e7..2edc78dcecf 100644 --- a/apps/servers/octopus/supergit/src/repo.rs +++ b/apps/servers/octopus/supergit/src/repo.rs @@ -1,6 +1,6 @@ //! Raw representation wrappers for libgit2 -use crate::{Branch, BranchCommit}; +use crate::branch::{Branch, BranchCommit}; use git2::{self, Oid}; use std::{fmt, sync::Arc}; @@ -104,7 +104,7 @@ impl Repository { /// desired one. If you want to make repeated queries onto the /// branch set, it's recommended you call `branches()`, and cache /// the data yourself. - pub fn get_branch(&self, name: String) -> Option { + pub fn branch(&self, name: String) -> Option { self.branches().ok().and_then(|ok| { ok.into_iter() .filter(|b| b.name().is_some())