diff options
Diffstat (limited to 'apps/servers/octopus/supergit/src/files.rs')
-rw-r--r-- | apps/servers/octopus/supergit/src/files.rs | 143 |
1 files changed, 141 insertions, 2 deletions
diff --git a/apps/servers/octopus/supergit/src/files.rs b/apps/servers/octopus/supergit/src/files.rs index 681b8877256f..2a1b69a34562 100644 --- a/apps/servers/octopus/supergit/src/files.rs +++ b/apps/servers/octopus/supergit/src/files.rs @@ -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") + ); } |