You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.4 KiB
168 lines
4.4 KiB
use crate::{Branch, BranchIter, Commit, HashId};
|
|
use git2::{ObjectType, TreeWalkMode, TreeWalkResult};
|
|
use atomptr::AtomPtr;
|
|
use std::collections::BTreeMap;
|
|
use std::{path::PathBuf, sync::Arc};
|
|
|
|
/// 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!(),
|
|
}
|
|
}
|
|
|
|
/// Load this tree entry from disk, if it is a file
|
|
///
|
|
/// When calling this function on a directory, nothing will
|
|
/// happen, 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) -> Option<Vec<u8>> {
|
|
if !self.is_file() {
|
|
return None;
|
|
}
|
|
|
|
let obj =
|
|
}
|
|
|
|
/// Check if this tree entry is a file
|
|
pub fn is_file(&self) -> bool {
|
|
match self {
|
|
Self::File(_) => true,
|
|
Self::Dir(_) => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A file to have ever existed in a git repo
|
|
pub struct File {
|
|
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")
|
|
);
|
|
}
|
|
|