@ -1,13 +1,13 @@
use crate ::{ Branch , BranchIter , Commit , HashId } ;
use crate ::{ Branch , BranchIter , Commit , HashId } ;
use git2 ::{ ObjectType , TreeWalkMode , TreeWalkResult } ;
use atomptr ::AtomPtr ;
use atomptr ::AtomPtr ;
use git2 ::{ ObjectType , TreeWalkMode , TreeWalkResult } ;
use std ::collections ::BTreeMap ;
use std ::collections ::BTreeMap ;
use std ::{ path ::PathBuf , sync ::Arc } ;
use std ::{ path ::PathBuf , sync ::Arc } ;
/// A tree of files
/// A tree of files
pub struct FileTree {
pub struct FileTree {
repo : Arc < git2 ::Repository > ,
repo : Arc < git2 ::Repository > ,
tree : AtomPtr < BTreeMap < String , TreeEntry > > ,
tree : AtomPtr < BTreeMap < String , Arc < TreeEntry > > > ,
}
}
impl FileTree {
impl FileTree {
@ -23,39 +23,78 @@ impl FileTree {
/// Parse a tree from a specific commit
/// Parse a tree from a specific commit
pub ( crate ) fn parse ( self : Arc < Self > , commit : HashId ) -> Arc < Self > {
pub ( crate ) fn parse ( self : Arc < Self > , commit : HashId ) -> Arc < Self > {
let mut new_tree = BTreeMap ::new ( ) ;
let mut new_tree = BTreeMap ::new ( ) ;
let tree = ( & self . repo )
let tree = ( & self . repo )
. find_commit ( commit . to_oid ( ) )
. find_commit ( commit . to_oid ( ) )
. unwrap ( )
. unwrap ( )
. tree ( )
. tree ( )
. unwrap ( ) ;
. unwrap ( ) ;
tree . walk ( TreeWalkMode ::PreOrder , | what , entry | {
tree . walk ( TreeWalkMode ::PreOrder , | p , entry | {
let path_segs : Vec < _ > = what . split ( "/" ) . filter ( | s | s ! = & "" ) . collect ( ) ;
let path_segs : Vec < _ > = p . split ( "/" ) . filter ( | s | s ! = & "" ) . collect ( ) ;
let path = if path_segs . len ( ) = = 0 {
let path = if path_segs . len ( ) = = 0 {
None
None
} else {
} else {
Some ( path_segs )
Some ( path_segs )
} ;
} ;
println! ( "{:?} {}" , path , entry . name ( ) . unwrap ( ) ) ;
let te = TreeEntry ::generate ( path , entry ) ;
new_tree . insert ( te . path ( ) , Arc ::new ( te ) ) ;
TreeWalkResult ::Ok
TreeWalkResult ::Ok
} )
} )
. unwrap ( ) ;
. 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 ) ;
drop ( tree ) ;
// Atomicly swap new tree into place
// Atomicly swap new tree into place
self . tree . swap ( new_tree ) ;
self . tree . swap ( new_tree ) ;
self
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 ) )
}
}
}
/// An entry in a file tree
/// 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.
///
///
/// It's variants can either be a file (leaf), or a subtree, with it's
/// To get all children of a subtree, use `Yield::into_tree()` to
/// own path handles, and children.
/// create a new, recursive `FileTree` to enumerate.
pub enum TreeEntry {
#[ 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
/// A single file
File ( File ) ,
File ( File ) ,
/// A sub-tree
/// A sub-tree
@ -63,74 +102,114 @@ pub enum TreeEntry {
}
}
impl TreeEntry {
impl TreeEntry {
/// Create a tree entry from a path and `git2::TreeEntry`
fn generate ( path_segments : Option < Vec < & str > > , entry : & git2 ::TreeEntry ) -> Self {
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 ) ) ;
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 ( ) {
match entry . kind ( ) {
Some ( ObjectType ::Blob ) = > Self ::File ( File ::new ( root , path ) ) ,
Some ( ObjectType ::Blob ) = > Self ::File ( File ::new ( id , path , name ) ) ,
Some ( ObjectType ::Tree ) = > Self ::Dir ( Directory ::new ( root , path ) ) ,
Some ( ObjectType ::Tree ) = > Self ::Dir ( Directory ::new ( id , path , name ) ) ,
_ = > unimplemented! ( ) ,
_ = > unimplemented! ( ) ,
}
}
}
}
/// Load this tree entry from disk, if it is a file
fn load ( & self , repo : & Arc < git2 ::Repository > ) -> Option < Yield > {
///
let id = self . id ( ) ;
/// 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 =
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 ) ) ,
}
}
}
/// Check if this tree entry is a file
fn is_file ( & self ) -> bool {
pub fn is_file ( & self ) -> bool {
match self {
match self {
Self ::File ( _ ) = > true ,
Self ::File ( _ ) = > true ,
Self ::Dir ( _ ) = > false ,
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 ( )
}
}
}
/// A file to have ever existed in a git repo
struct File {
pub struct File {
id : HashId ,
root : PathBuf ,
path : String ,
path : String ,
name : String ,
}
}
impl File {
impl File {
pub ( crate ) fn new ( root : PathBuf , path : String ) -> Self {
fn new ( id : HashId , path : String , name : String ) -> Self {
Self { root , path }
Self { id , path , name }
}
/// 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
struct Directory {
///
id : HashId ,
/// 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 ,
path : String ,
name : String ,
}
}
impl Directory {
impl Directory {
pub ( crate ) fn new ( root : PathBuf , path : String ) -> Self {
fn new ( id : HashId , path : String , name : String ) -> Self {
Self { root , path }
Self { id , path , name }
}
fn enumerate ( & self , repo : git2 ::Repository ) -> Vec < String > {
vec! [ ]
}
}
}
}
////////////////////////////////
/// Take a vector of path segments, and turn it into a valid offset 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.
/// There are tests to make sure this function works properly.
@ -139,7 +218,7 @@ impl Directory {
/// * vec![] -> ""
/// * vec![] -> ""
/// * vec!["foo"] -> "foo"
/// * vec!["foo"] -> "foo"
/// * vec!["foo", "bar", "baz"] -> "foo/bar/baz"
/// * vec!["foo", "bar", "baz"] -> "foo/bar/baz"
fn path_segs_join ( segments : Vec < String > ) -> String {
fn path_segs_join ( segments : Vec < & str > ) -> String {
segments
segments
. into_iter ( )
. into_iter ( )
. fold ( PathBuf ::new ( ) , | buf , seg | buf . join ( seg ) )
. fold ( PathBuf ::new ( ) , | buf , seg | buf . join ( seg ) )