octopus: implementing supergit basic tree abstraction

wip/yesman
Katharina Fey 4 years ago
parent 4c4d16d58c
commit eac4297914
  1. 7
      apps/servers/octopus/Cargo.lock
  2. 3
      apps/servers/octopus/supergit/Cargo.toml
  3. 31
      apps/servers/octopus/supergit/src/bin/test.rs
  4. 14
      apps/servers/octopus/supergit/src/branch.rs
  5. 13
      apps/servers/octopus/supergit/src/commit.rs
  6. 143
      apps/servers/octopus/supergit/src/files.rs
  7. 24
      apps/servers/octopus/supergit/src/lib.rs

@ -507,6 +507,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atomptr"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a29d2348bcb8ee04660963dbe624a1bbbd1ea49ca6ab51138aecb057135e3cb"
[[package]]
name = "atty"
version = "0.2.14"
@ -1826,6 +1832,7 @@ name = "supergit"
version = "0.1.0"
dependencies = [
"async-std",
"atomptr",
"git2",
]

@ -7,4 +7,5 @@ edition = "2018"
[dependencies]
git2 = "0.11"
async-std = { version = "1.0", features = ["unstable"] }
async-std = { version = "1.0", features = ["unstable"] }
atomptr = "1.0"

@ -13,31 +13,14 @@ fn main() {
};
let repo = Repository::open(path.as_str()).unwrap();
let (tx, rx) = channel();
let branches = repo.branches().unwrap();
branches
let main = branches
.into_iter()
.filter(|b| b.name == Some("main".to_string()))
.for_each(|b| tx.send(b.get_all()).unwrap());
// Iterate over all branch iterators we get
while let Some(biter) = rx.recv().ok() {
use BranchCommit::*;
.filter(|b| b.name() == Some("master".to_string()))
.nth(0)
.unwrap();
biter.for_each(|bc| match bc {
Commit(c) => println!("{}: {}", c.id_str(), c.summary()),
Merge(c, _b) => {
println!("{}: {}", c.id_str(), c.summary());
tx.send(_b.get_all()).unwrap();
}
Octopus(c, branches) => {
println!("{}: {}", c.id_str(), c.summary());
for _b in branches {
tx.send(_b.get_all()).unwrap();
}
}
});
}
let head = main.get_head();
let tree = head.get_tree();
}

@ -8,8 +8,8 @@ use std::{mem, sync::Arc};
#[derive(Clone)]
pub struct Branch {
repo: Arc<Repository>,
pub name: Option<String>,
pub head: HashId,
name: Option<String>,
head: HashId,
}
impl Branch {
@ -77,6 +77,16 @@ impl Branch {
SegLimit::Length(0, num),
)
}
/// Get the commit pointed at by HEAD
pub fn get_head(&self) -> Commit {
Commit::new(&self.repo, self.head.clone()).unwrap()
}
/// Get the branch name, if it exists
pub fn name(&self) -> Option<String> {
self.name.clone()
}
}
/// A branch segment iterator

@ -1,4 +1,4 @@
use crate::HashId;
use crate::{FileTree, HashId};
use git2::Repository;
use std::sync::Arc;
@ -14,21 +14,24 @@ pub struct Commit {
impl Commit {
/// Create a commit object and check if it exists in the repo
pub fn new(r: &Arc<Repository>, id: HashId) -> Option<Self> {
pub(crate) fn new(r: &Arc<Repository>, id: HashId) -> Option<Self> {
r.find_commit(id.to_oid()).ok().map(|_| Self {
id,
repo: Arc::clone(r),
})
}
/// Get a utf-8 string representation of the commit ID
pub fn id_str(&self) -> String {
self.id.to_string()
}
/// Get the summary line as a utf-7 string
pub fn summary(&self) -> String {
self.find().summary().unwrap().into()
}
/// Get the number of parents
pub fn parent_count(&self) -> usize {
self.repo
.find_commit(self.id.to_oid())
@ -44,6 +47,7 @@ impl Commit {
.and_then(|c| Self::new(&self.repo, c.id().into()))
}
/// Get a specific parent, if it exists
pub fn parent(&self, num: usize) -> Option<Self> {
self.find()
.parent(num)
@ -58,6 +62,11 @@ impl Commit {
.collect()
}
/// Get the file tree for this commit
pub fn get_tree(&self) -> Arc<FileTree> {
FileTree::new(&self.repo, self.id.clone())
}
fn find(&self) -> git2::Commit {
self.repo.find_commit(self.id.to_oid()).unwrap()
}

@ -1,7 +1,146 @@
use crate::{Branch, BranchIter, Commit, HashId};
use git2::{ObjectType, TreeWalkMode, TreeWalkResult};
use atomptr::AtomPtr;
use std::collections::BTreeMap;
use std::{path::PathBuf, sync::Arc};
pub type FileId = usize;
/// A tree of files
pub struct FileTree {
repo: Arc<git2::Repository>,
tree: AtomPtr<BTreeMap<String, TreeEntry>>,
}
impl FileTree {
/// Utility function to create a tree, and then parse it too
pub(crate) fn new(repo: &Arc<git2::Repository>, commit: HashId) -> Arc<Self> {
Arc::new(Self {
repo: Arc::clone(repo),
tree: AtomPtr::new(BTreeMap::new()),
})
.parse(commit)
}
/// Parse a tree from a specific commit
pub(crate) fn parse(self: Arc<Self>, commit: HashId) -> Arc<Self> {
let mut new_tree = BTreeMap::new();
let tree = (&self.repo)
.find_commit(commit.to_oid())
.unwrap()
.tree()
.unwrap();
tree.walk(TreeWalkMode::PreOrder, |what, entry| {
let path_segs: Vec<_> = what.split("/").filter(|s| s != &"").collect();
let path = if path_segs.len() == 0 {
None
} else {
Some(path_segs)
};
println!("{:?} {}", path, entry.name().unwrap());
TreeWalkResult::Ok
})
.unwrap();
drop(tree);
// Atomicly swap new tree into place
self.tree.swap(new_tree);
self
}
}
/// An entry in a file tree
///
/// It's variants can either be a file (leaf), or a subtree, with it's
/// own path handles, and children.
pub enum TreeEntry {
/// A single file
File(File),
/// A sub-tree
Dir(Directory),
}
impl TreeEntry {
/// Create a tree entry from a path and `git2::TreeEntry`
fn generate(root: PathBuf, path_segments: Option<Vec<String>>, entry: git2::TreeEntry) -> Self {
let path = path_segments.map_or("".into(), |p| path_segs_join(p));
match entry.kind() {
Some(ObjectType::Blob) => Self::File(File::new(root, path)),
Some(ObjectType::Tree) => Self::Dir(Directory::new(root, path)),
_ => unimplemented!(),
}
}
}
/// A file to have ever existed in a git repo
pub struct File {
id: FileId,
root: PathBuf,
path: String,
}
impl File {
pub(crate) fn new(root: PathBuf, path: String) -> Self {
Self { root, path }
}
/// Get the history of a file from a branch iterator
pub fn get_history(&self, branch: BranchIter) -> Vec<Commit> {
todo!()
}
}
/// A subdirectory in a file tree
///
/// A directory has a set of children, which can either be Files, or
/// other directories. Many of the functions to retrieve metadata
/// (such as the last commit, count, etc) will be deferred to the
/// children of this directory.
pub struct Directory {
root: PathBuf,
path: String,
}
impl Directory {
pub(crate) fn new(root: PathBuf, path: String) -> Self {
Self { root, path }
}
}
/// 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<String>) -> 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")
);
}

@ -1,15 +1,20 @@
//! Strongly typed git repository explorer library
//! Read-only git repository explorer library.
//!
//! This library exposes a read-only view into a git repository. To
//! get started, open an existing bare repo, and then call `sync()` to
//! build a cache of it. Every time you want your view of the repo to
//! update, call `sync()` again. If you want the sync operation to be
//! blocking, call `sync_blocking()` instead.
//! This library provides a more Rustic interface for libgit2, built
//! on the `git2` bindings. If you want more low-level access to your
//! repository, consider using that library instead.
//!
//! supergit aims to make queries into a git repo as typed and easy as
//! possible. Start by creating a [`Repository`](), and enumerating
//! or fetching [`Branch`]()es that you are interested in.
//!
//! Unlike `libgit2`, this library can resolve reverse dependencies
//! between files, and their commit history. Some of these functions
//! are very computationally intensive, and will be marked with their
//! runtime cost.
mod branch;
pub use branch::{Branch, BranchCommit};
pub use branch::{Branch, BranchIter, BranchCommit};
mod commit;
pub use commit::Commit;
@ -18,8 +23,11 @@ mod diff;
pub use diff::Diff;
mod repo;
pub use repo::Repository;
pub(crate) use repo::HashId;
pub use repo::Repository;
mod files;
pub use files::{File, FileTree};
use async_std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicUsize, Ordering};

Loading…
Cancel
Save