diff options
Diffstat (limited to 'apps/servers/octopus/supergit/src/old-files.rs')
-rw-r--r-- | apps/servers/octopus/supergit/src/old-files.rs | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/apps/servers/octopus/supergit/src/old-files.rs b/apps/servers/octopus/supergit/src/old-files.rs new file mode 100644 index 000000000000..a4b10513c7fb --- /dev/null +++ b/apps/servers/octopus/supergit/src/old-files.rs @@ -0,0 +1,232 @@ +use crate::{Branch, BranchIter, Commit, HashId}; +use atomptr::AtomPtr; +use git2::{ObjectType, TreeWalkMode, TreeWalkResult}; +use std::collections::{BTreeMap, BTreeSet}; +use std::{path::PathBuf, sync::Arc}; + +/// A tree of files +pub struct FileTree { + repo: Arc<git2::Repository>, + tree: AtomPtr<BTreeMap<String, Arc<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, |p, entry| { + let path_segs: Vec<_> = p.split("/").filter(|s| s != &"").collect(); + let path = if path_segs.len() == 0 { + None + } else { + Some(path_segs) + }; + + let te = TreeEntry::generate(path, entry); + new_tree.insert(te.path(), Arc::new(te)); + TreeWalkResult::Ok + }) + .unwrap(); + + // Add a special entry for the root of the repo + new_tree.insert( + "".into(), + Arc::new(TreeEntry::Dir(Directory { + id: tree.id().into(), + path: "".into(), + name: "".into(), + })), + ); + + // This is needed to make borrowchk shut up + drop(tree); + + // Atomicly swap new tree into place + self.tree.swap(new_tree); + + self + } + + fn get_entry(&self, path: &str) -> Option<Arc<TreeEntry>> { + self.tree.get_ref().get(path).map(|e| Arc::clone(&e)) + } + + /// Load a file entry in this `FileTree` from disk + /// + /// When calling this function on a directory, nothing will happen + /// (returns `None`), because directories can't be loaded. If you + /// want to get a list of children for a directory, use + /// [`FileTree::enumerate()`]() instead! + pub fn load(&self, path: &str) -> Option<Yield> { + self.get_entry(path).and_then(|e| e.load(&self.repo)) + } + + /// Get the history of a path with a branch iterator + /// + /// This function is very computationally intensive, because it + /// will step through the entire iterator to pull commits from, + /// and see if they touch the respective path. + pub fn history(&self, iter: BranchIter, path: &str) -> Vec<Commit> { + iter.filter_map(|c| { + if c.commit() + .get_paths() + .into_iter() + .collect::<BTreeSet<_>>() + .contains(path) + { + Some(c.commit().clone()) + } else { + None + } + }) + .collect() + } +} + +/// Data yielded from loading a part of the file tree +/// +/// This type is returned when fetching a path via `FileTree::load()`, +/// and can either be a single file read into memory, or an +/// enumeration of direct children of a directory. +/// +/// To get all children of a subtree, use `Yield::into_tree()` to +/// create a new, recursive `FileTree` to enumerate. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Yield { + /// Load a single file into a buffer + File(Vec<u8>), + /// Enumerate children in a directory + Dir(Vec<String>), +} + +enum TreeEntry { + /// A single file + File(File), + /// A sub-tree + Dir(Directory), +} + +impl TreeEntry { + fn generate(path_segments: Option<Vec<&str>>, entry: &git2::TreeEntry) -> Self { + let path = path_segments.map_or("".into(), |p| path_segs_join(p)); + let id = entry.id().into(); + let name = entry.name().unwrap().into(); + + match entry.kind() { + Some(ObjectType::Blob) => Self::File(File::new(id, path, name)), + Some(ObjectType::Tree) => Self::Dir(Directory::new(id, path, name)), + _ => unimplemented!(), + } + } + + fn load(&self, repo: &Arc<git2::Repository>) -> Option<Yield> { + let id = self.id(); + + match self { + Self::File(ref f) => repo + .find_blob(id.into()) + .ok() + .map(|b| Yield::File(b.content().into())), + Self::Dir(ref d) => repo + .find_tree(id.into()) + .ok() + .map(|tree| { + let mut children = vec![]; + + // Iterate the tree, but only as long as there are no + // additional path segments + tree.walk(TreeWalkMode::PreOrder, |p, entry| { + let path_segs: Vec<_> = p.split("/").filter(|s| s != &"").collect(); + if path_segs.len() > 0 { + TreeWalkResult::Skip + } else { + // Take the current tree path, and append the + // name of whatever we're currently iterating + // over is + let path = PathBuf::new().join(self.path()).join(entry.name().unwrap()); + children.push(path.as_path().to_str().unwrap().into()); + TreeWalkResult::Ok + } + }); + + children + }) + .map(|c| Yield::Dir(c)), + } + } + + fn is_file(&self) -> bool { + match self { + Self::File(_) => true, + Self::Dir(_) => false, + } + } + + fn id(&self) -> HashId { + match self { + Self::File(ref f) => f.id.clone(), + Self::Dir(ref d) => d.id.clone(), + } + } + + /// Get the repo-internal path (including name) + /// + /// This is used to index files in a file tree, to allow O(1) + /// access to deeply nested items. + fn path(&self) -> String { + match self { + Self::File(ref f) => PathBuf::new().join(&f.path).join(&f.name), + Self::Dir(ref d) => PathBuf::new().join(&d.path).join(&d.name), + } + .as_path() + .to_str() + .unwrap() + .into() + } +} + +struct File { + id: HashId, + path: String, + name: String, +} + +impl File { + fn new(id: HashId, path: String, name: String) -> Self { + Self { id, path, name } + } +} + +struct Directory { + id: HashId, + path: String, + name: String, +} + +impl Directory { + fn new(id: HashId, path: String, name: String) -> Self { + Self { id, path, name } + } + + fn enumerate(&self, repo: git2::Repository) -> Vec<String> { + vec![] + } +} + +//////////////////////////////// |