Merge remote-tracking branch 'origin/directory-generic'

* origin/directory-generic:
  radicle-surf: refactor directory

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Fintan Halpenny
2022-11-24 12:01:57 +00:00
10 changed files with 342 additions and 324 deletions
+4
View File
@@ -48,6 +48,10 @@ version = "0.2.0"
path = "../radicle-git-ext"
features = ["serde"]
[dependencies.radicle-std-ext]
version = "0.1.0"
path = "../radicle-std-ext"
[build-dependencies]
anyhow = "1.0"
flate2 = "1"
+4 -4
View File
@@ -25,7 +25,7 @@
//! the directories in a tree-like structure.
use radicle_surf::{
file_system::{Directory, DirectoryEntry},
file_system::{directory, Directory},
git::Repository,
};
use std::{env, time::Instant};
@@ -51,10 +51,10 @@ fn main() {
fn print_directory(d: &Directory, repo: &Repository, indent_level: usize) {
let indent = " ".repeat(indent_level * 4);
println!("{}{}/", &indent, d.name());
for entry in d.contents(repo).unwrap().iter() {
for entry in d.entries(repo).unwrap().entries() {
match entry {
DirectoryEntry::File(f) => println!(" {}{}", &indent, f.name()),
DirectoryEntry::Directory(d) => print_directory(d, repo, indent_level + 1),
directory::Entry::File(f) => println!(" {}{}", &indent, f.name()),
directory::Entry::Directory(d) => print_directory(d, repo, indent_level + 1),
}
}
}
+2 -1
View File
@@ -111,8 +111,9 @@
//! ```
pub mod directory;
pub use directory::{Directory, Entries, Entry, File, FileContent};
mod error;
pub use error::Error;
mod path;
pub use self::{directory::*, path::*};
pub use self::path::*;
+244 -177
View File
@@ -20,55 +20,114 @@
//! A `Directory` is expected to be a non-empty tree of directories and files.
//! See [`Directory`] for more information.
use crate::{
file_system::{error::LabelError, path::*, Error},
git::{self, Repository, Revision},
};
use git2::Blob;
use radicle_git_ext::Oid;
use std::{
collections::BTreeMap,
convert::{Infallible, TryFrom},
convert::{Infallible, Into},
path,
};
/// Represents a `file` in a git repo.
use git2::Blob;
use radicle_git_ext::{is_not_found_err, Oid};
use radicle_std_ext::result::ResultExt as _;
use crate::{
file_system::{path::*, Error},
git::{Repository, Revision},
};
pub mod error {
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum Directory {
#[error(transparent)]
Git(#[from] git2::Error),
#[error(transparent)]
Entry(#[from] Entry),
#[error(transparent)]
File(#[from] File),
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum Entry {
#[error("the entry name was not valid UTF-8")]
Utf8Error,
#[error(transparent)]
Label(#[from] super::Error),
}
#[derive(Debug, Error, PartialEq)]
pub enum File {
#[error(transparent)]
Git(#[from] git2::Error),
}
}
/// A `File` in a git repository.
///
/// The representation is lightweight and contains the [`Oid`] that
/// points to the git blob which is this file.
///
/// The name of a file can be retrieved via [`File::name`].
///
/// The [`FileContent`] of a file can be retrieved via
/// [`File::content`].
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct File {
pub(crate) name: Label,
pub(crate) oid: Oid,
pub(crate) id: Oid,
}
impl File {
/// Create a new `File`.
pub fn new(name: String, oid: Oid) -> Self {
File {
/// The name of this `File`.
pub fn name(&self) -> &str {
self.name.as_str()
}
/// The object identifier of this `File`.
pub fn id(&self) -> Oid {
self.id
}
/// Create a new `File` with the `name` and `id` provided.
///
/// The `id` must point to a `git` blob.
pub fn new(name: String, id: Oid) -> Self {
Self {
name: Label {
label: name,
hidden: false,
},
oid,
id,
}
}
/// Returns the file name reference.
pub fn name(&self) -> &str {
self.name.as_str()
/// Get the [`FileContent`] for this `File`.
///
/// # Errors
///
/// This function will fail if it could not find the `git` blob
/// for the `Oid` of this `File`.
pub fn content<'a>(&self, repo: &'a Repository) -> Result<FileContent<'a>, error::File> {
let blob = repo.git2_repo().find_blob(self.id.into())?;
Ok(FileContent { blob })
}
}
/// Represents the actual content of a file.
/// The contents of a [`File`].
///
/// To construct a `FileContent` use [`File::content`].
pub struct FileContent<'a> {
blob: Blob<'a>,
}
impl<'a> FileContent<'a> {
/// Returns the file content as a byte slice.
/// Return the file contents as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
self.blob.content()
}
/// Returns the size of file
/// Return the size of the file contents.
pub fn size(&self) -> usize {
self.blob.size()
}
@@ -79,190 +138,176 @@ impl<'a> FileContent<'a> {
}
}
/// Represents the listing of a directory.
pub struct DirectoryContent {
listing: BTreeMap<Label, DirectoryEntry>,
/// A representations of a [`Directory`]'s entries.
pub struct Entries {
listing: BTreeMap<Label, Entry>,
}
impl DirectoryContent {
/// Returns an iterator for the listing of a directory.
pub fn iter(&self) -> impl Iterator<Item = &DirectoryEntry> {
impl Entries {
/// Return the [`Label`]s of each [`Entry`].
pub fn names(&self) -> impl Iterator<Item = &Label> {
self.listing.keys()
}
/// Return each [`Entry`].
pub fn entries(&self) -> impl Iterator<Item = &Entry> {
self.listing.values()
}
/// Return each [`Label`] and [`Entry`].
pub fn iter(&self) -> impl Iterator<Item = (&Label, &Entry)> {
self.listing.iter()
}
}
/// Represents an entry in a directory.
/// An `Entry` is either a [`File`] entry or a [`Directory`] entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DirectoryEntry {
/// When the entry is a file.
pub enum Entry {
/// A file entry within a [`Directory`].
File(File),
/// When the entry is a directory.
/// A sub-directory of a [`Directory`].
Directory(Directory),
}
impl DirectoryEntry {
/// Returns the label (short name, without the parent path) of the entry.
impl Entry {
/// Get a label for the `Entriess`, either the name of the [`File`]
/// or the name of the [`Directory`].
pub fn label(&self) -> &Label {
match self {
DirectoryEntry::File(file) => &file.name,
DirectoryEntry::Directory(directory) => directory.name(),
Entry::File(file) => &file.name,
Entry::Directory(directory) => directory.name(),
}
}
pub(crate) fn from_entry(entry: &git2::TreeEntry) -> Result<Option<Self>, error::Entry> {
let name = Label {
label: entry.name().ok_or(error::Entry::Utf8Error)?.to_string(),
hidden: false,
};
let id = entry.id().into();
Ok(entry.kind().and_then(|kind| match kind {
git2::ObjectType::Tree => Some(Self::Directory(Directory { name, id })),
git2::ObjectType::Blob => Some(Self::File(File { name, id })),
_ => None,
}))
}
}
/// A `Directory` is a set of entries of sub-directories and files, ordered
/// by their unique names in the alphabetical order.
/// A `Directory` is the representation of a file system directory, for a given
/// [`git` tree][git-tree].
///
/// The name of a directory can be retrieved via [`File::name`].
///
/// The [`Entries`] of a directory can be retrieved via
/// [`Directory::entries`].
///
/// [git-tree]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directory {
pub(crate) name: Label,
pub(crate) oid: Oid,
pub(crate) id: Oid,
}
impl Directory {
/// Creates a directory given `name` and `oid`, with empty contents.
pub fn new(name: Label, oid: Oid) -> Self {
Directory { name, oid }
}
/// Get the name of the current `Directory`.
pub fn name(&self) -> &Label {
&self.name
}
/// Returns a `DirectoryContent` for the current directory.
pub fn contents(&self, repo: &Repository) -> Result<DirectoryContent, git::Error> {
let listing = repo.directory_get(self)?;
Ok(DirectoryContent { listing })
/// Creates a directory given its `name` and `id`.
///
/// The `id` must point to a `git` tree.
pub fn new(name: Label, id: Oid) -> Self {
Self { name, id }
}
/// Retrieves a `DirectoryEntry` for `path` in `repo`.
pub fn get_path(
/// Return the [`Entries`] for this `Directory`'s `Oid`.
///
/// The resulting `Entries` will only resolve to this
/// `Directory`'s entries. Any sub-directories will need to be
/// resolved independently.
///
/// # Errors
///
/// This function will fail if it could not find the `git` tree
/// for the `Oid`.
pub fn entries(&self, repo: &Repository) -> Result<Entries, error::Directory> {
let tree = repo.git2_repo().find_tree(self.id.into())?;
let mut entries = BTreeMap::new();
let mut error = None;
// Walks only the first level of entries.
tree.walk(git2::TreeWalkMode::PreOrder, |_s, entry| {
match Entry::from_entry(entry) {
Ok(Some(entry)) => match entry {
Entry::File(_) => {
entries.insert(entry.label().clone(), entry);
git2::TreeWalkResult::Ok
},
Entry::Directory(_) => {
entries.insert(entry.label().clone(), entry);
// Skip nested directories
git2::TreeWalkResult::Skip
},
},
Ok(None) => git2::TreeWalkResult::Skip,
Err(err) => {
error = Some(err);
git2::TreeWalkResult::Abort
},
}
})?;
match error {
Some(err) => Err(err.into()),
None => Ok(Entries { listing: entries }),
}
}
/// Find the [`Entry`] found at `path`, if it exists.
pub fn find_entry(
&self,
path: &path::Path,
repo: &Repository,
) -> Result<DirectoryEntry, git::Error> {
) -> Result<Option<Entry>, crate::git::Error> {
// Search the path in git2 tree.
let git2_tree = repo.git2_repo().find_tree(self.oid.into())?;
let entry = git2_tree.get_path(path)?;
let git2_tree = repo.git2_repo().find_tree(self.id.into())?;
let entry = git2_tree
.get_path(path)
.map(Some)
.or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(None))?;
// Construct the DirectoryEntry.
let name = entry.name().ok_or_else(|| {
Error::Label(LabelError::InvalidUTF8 {
label: String::from_utf8_lossy(entry.name_bytes()).into(),
})
})?;
let label = Label {
label: name.to_string(),
hidden: false,
};
let oid: Oid = entry.id().into();
match entry.kind() {
Some(git2::ObjectType::Tree) => {
let dir = Directory::new(label, oid);
Ok(DirectoryEntry::Directory(dir))
},
Some(git2::ObjectType::Blob) => {
let f = File { name: label, oid };
Ok(DirectoryEntry::File(f))
},
_ => {
let file_path = Path::try_from(path.to_path_buf())?;
Err(git::Error::PathNotFound(file_path))
},
}
Ok(entry
.and_then(|entry| Entry::from_entry(&entry).transpose())
.transpose()
.unwrap())
}
/// Find a [`File`] in the directory given the [`Path`] to the [`File`].
///
/// # Failures
///
/// This operation fails if the path does not lead to a [`File`].
/// If the search is for a `Directory` then use `find_directory`.
///
/// # Examples
///
/// Search for a file in the path:
/// * `foo/bar/baz.hs`
/// * `foo`
/// * `foo/bar/qux.rs`
///
/// ```
/// use radicle_surf::file_system::{Directory, File};
/// use radicle_surf::file_system::unsound;
///
/// let file = File::new(b"module Banana ...");
///
/// let mut directory = Directory::root();
/// directory.insert_file(unsound::path::new("foo/bar/baz.rs"), file.clone());
///
/// // The file is succesfully found
/// assert_eq!(directory.find_file(unsound::path::new("foo/bar/baz.rs")), Some(file));
///
/// // We shouldn't be able to find a directory
/// assert_eq!(directory.find_file(unsound::path::new("foo")), None);
///
/// // We shouldn't be able to find a file that doesn't exist
/// assert_eq!(directory.find_file(unsound::path::new("foo/bar/qux.rs")), None);
/// ```
pub fn find_file(&self, path: Path, repo: &Repository) -> Option<Oid> {
/// Find the `Oid`, for a [`File`], found at `path`, if it exists.
pub fn find_file(
&self,
path: Path,
repo: &Repository,
) -> Result<Option<Oid>, crate::git::Error> {
let path_buf: std::path::PathBuf = (&path).into();
let entry = match self.get_path(path_buf.as_path(), repo) {
Ok(entry) => entry,
Err(_) => return None,
};
match entry {
DirectoryEntry::File(f) => Some(f.oid),
DirectoryEntry::Directory(_) => None,
}
Ok(match self.find_entry(path_buf.as_path(), repo)? {
Some(Entry::File(f)) => Some(f.id),
_ => None,
})
}
/// Find a `Directory` in the directory given the [`Path`] to the
/// `Directory`.
///
/// # Failures
///
/// This operation fails if the path does not lead to the `Directory`.
///
/// # Examples
///
/// Search for directories in the path:
/// * `foo`
/// * `foo/bar`
/// * `foo/baz`
///
/// ```
/// use radicle_surf::file_system::{Directory, File};
/// use radicle_surf::file_system::unsound;
///
/// let file = File::new(b"module Banana ...");
///
/// let mut directory = Directory::root();
/// directory.insert_file(unsound::path::new("foo/bar/baz.rs"), file.clone());
///
/// // Can find the first level
/// assert!(directory.find_directory(unsound::path::new("foo")).is_some());
///
/// // Can find the second level
/// assert!(directory.find_directory(unsound::path::new("foo/bar")).is_some());
///
/// // Cannot find 'baz' since it does not exist
/// assert!(directory.find_directory(unsound::path::new("foo/baz")).is_none());
///
/// // 'baz.rs' is a file and not a directory
/// assert!(directory.find_directory(unsound::path::new("foo/bar/baz.rs")).is_none());
/// ```
pub fn find_directory(&self, path: Path, repo: &Repository) -> Option<Self> {
/// Find the `Directory` found at `path`, if it exists.
pub fn find_directory(
&self,
path: Path,
repo: &Repository,
) -> Result<Option<Self>, crate::git::Error> {
let path_buf: std::path::PathBuf = (&path).into();
let entry = match self.get_path(path_buf.as_path(), repo) {
Ok(entry) => entry,
Err(_) => return None,
};
match entry {
DirectoryEntry::File(_) => None,
DirectoryEntry::Directory(d) => Some(d),
}
Ok(match self.find_entry(path_buf.as_path(), repo)? {
Some(Entry::Directory(d)) => Some(d),
_ => None,
})
}
// TODO(fintan): This is going to be a bit trickier so going to leave it out for
@@ -274,21 +319,43 @@ impl Directory {
/// Get the total size, in bytes, of a `Directory`. The size is
/// the sum of all files that can be reached from this `Directory`.
pub fn size(&self, repo: &Repository) -> Result<usize, git::Error> {
let mut size = 0;
let contents = self.contents(repo)?;
for item in contents.iter() {
match item {
DirectoryEntry::File(f) => {
let sz = repo.file_size(f.oid)?;
size += sz;
pub fn size(&self, repo: &Repository) -> Result<usize, error::Directory> {
self.traverse(repo, 0, &mut |size, entry| match entry {
Entry::File(file) => Ok(size + file.content(repo)?.size()),
Entry::Directory(dir) => Ok(size + dir.size(repo)?),
})
}
/// Traverse the entire `Directory` using the `initial`
/// accumulator and the function `f`.
///
/// For each [`Entry::Directory`] this will recursively call
/// [`Directory::traverse`] and obtain its [`Entries`].
///
/// `Error` is the error type of the fallible function.
/// `B` is the type of the accumulator.
/// `F` is the fallible function that takes the accumulator and
/// the next [`Entry`], possibly providing the next accumulator
/// value.
pub fn traverse<Error, B, F>(
&self,
repo: &Repository,
initial: B,
f: &mut F,
) -> Result<B, Error>
where
Error: From<error::Directory>,
F: FnMut(B, &Entry) -> Result<B, Error>,
{
self.entries(repo)?
.entries()
.try_fold(initial, |acc, entry| match entry {
Entry::File(_) => f(acc, entry),
Entry::Directory(directory) => {
let acc = directory.traverse(repo, acc, f)?;
f(acc, entry)
},
DirectoryEntry::Directory(d) => {
size += d.size(repo)?;
},
}
}
Ok(size)
})
}
}
@@ -296,6 +363,6 @@ impl Revision for Directory {
type Error = Infallible;
fn object_id(&self, _repo: &Repository) -> Result<Oid, Self::Error> {
Ok(self.oid)
Ok(self.id)
}
}
+3 -65
View File
@@ -15,14 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{
collections::{BTreeMap, BTreeSet},
convert::TryFrom,
path::PathBuf,
str,
};
use std::{collections::BTreeSet, convert::TryFrom, path::PathBuf, str};
use directory::{Directory, FileContent};
use git_ref_format::{refspec::QualifiedPattern, Qualified};
use radicle_git_ext::Oid;
use thiserror::Error;
@@ -30,7 +24,7 @@ use thiserror::Error;
use crate::{
diff::{self, *},
file_system,
file_system::{directory, DirectoryEntry, Label},
file_system::{directory::FileContent, Directory, Label},
git::{
commit,
glob,
@@ -195,60 +189,10 @@ impl Repository {
let tree = git2_commit.as_object().peel_to_tree()?;
Ok(Directory {
name: Label::root(),
oid: tree.id().into(),
id: tree.id().into(),
})
}
/// Retrieves the content of a directory.
pub(crate) fn directory_get(
&self,
d: &Directory,
) -> Result<BTreeMap<Label, DirectoryEntry>, Error> {
let git2_tree = self.inner.find_tree(d.oid.into())?;
let map = self.tree_first_level(git2_tree)?;
Ok(map)
}
/// Returns a map of the first level entries in `tree`.
fn tree_first_level(&self, tree: git2::Tree) -> Result<BTreeMap<Label, DirectoryEntry>, Error> {
let mut map = BTreeMap::new();
// Walks only the first level of entries.
tree.walk(git2::TreeWalkMode::PreOrder, |_s, entry| {
let oid = entry.id().into();
let label = match entry.name() {
Some(name) => match name.parse::<Label>() {
Ok(label) => label,
Err(_) => return git2::TreeWalkResult::Abort,
},
None => return git2::TreeWalkResult::Abort,
};
match entry.kind() {
Some(git2::ObjectType::Tree) => {
let dir = Directory::new(label.clone(), oid);
map.insert(label, DirectoryEntry::Directory(dir));
return git2::TreeWalkResult::Skip; // Not go into nested
// directories.
},
Some(git2::ObjectType::Blob) => {
let f = directory::File {
name: label.clone(),
oid,
};
map.insert(label, DirectoryEntry::File(f));
},
_ => {
return git2::TreeWalkResult::Skip;
},
}
git2::TreeWalkResult::Ok
})?;
Ok(map)
}
/// Returns the last commit, if exists, for a `path` in the history of
/// `rev`.
pub fn last_commit<C: ToCommit>(
@@ -294,12 +238,6 @@ impl Repository {
Ok(FileContent::new(blob))
}
/// Return the size of a file
pub(crate) fn file_size(&self, oid: Oid) -> Result<usize, Error> {
let blob = self.inner.find_blob(oid.into())?;
Ok(blob.size())
}
/// Retrieves the file with `path` in this commit.
pub fn get_commit_file<R: Revision>(
&self,
+8 -1
View File
@@ -30,7 +30,11 @@ pub use blob::{Blob, BlobContent};
pub mod tree;
pub use tree::{tree, Tree, TreeEntry};
use crate::{commit, file_system, git};
use crate::{
commit,
file_system::{self, directory},
git,
};
/// Git object types.
///
@@ -84,6 +88,9 @@ impl Serialize for Info {
/// An error reported by object types.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Directory(#[from] directory::error::Directory),
/// An error occurred during a file system operation.
#[error(transparent)]
FileSystem(#[from] file_system::Error),
+1 -1
View File
@@ -136,7 +136,7 @@ where
let p = file_system::Path::from_str(path)?;
let file = root
.find_file(p.clone(), repo)
.find_file(p.clone(), repo)?
.ok_or_else(|| Error::PathNotFound(p.clone()))?;
let mut commit_path = file_system::Path::root();
+30 -31
View File
@@ -29,7 +29,7 @@ use serde::{
use crate::{
commit,
file_system::{self, DirectoryEntry},
file_system::{self, directory},
git::Repository,
object::{Error, Info, ObjectType},
revision::Revision,
@@ -111,41 +111,40 @@ pub fn tree(
root_dir
} else {
root_dir
.find_directory(path.clone(), repo)
.find_directory(path.clone(), repo)?
.ok_or_else(|| Error::PathNotFound(path.clone()))?
};
let entries_results: Result<Vec<TreeEntry>, Error> = prefix_dir
.contents(repo)?
.iter()
.map(|entry| {
let entry_path = if path.is_root() {
file_system::Path::new(entry.label().clone())
} else {
let mut p = path.clone();
p.push(entry.label().clone());
p
};
let mut commit_path = file_system::Path::root();
commit_path.append(entry_path.clone());
let mut entries =
prefix_dir
.entries(repo)?
.iter()
.fold(Vec::new(), |mut entries, (label, entry)| {
let entry_path = if path.is_root() {
file_system::Path::new(label.clone())
} else {
let mut p = path.clone();
p.push(label.clone());
p
};
let mut commit_path = file_system::Path::root();
commit_path.append(entry_path.clone());
let info = Info {
name: entry.label().to_string(),
object_type: match entry {
DirectoryEntry::Directory(_) => ObjectType::Tree,
DirectoryEntry::File { .. } => ObjectType::Blob,
},
last_commit: None,
};
let info = Info {
name: label.to_string(),
object_type: match entry {
directory::Entry::Directory(_) => ObjectType::Tree,
directory::Entry::File { .. } => ObjectType::Blob,
},
last_commit: None,
};
Ok(TreeEntry {
info,
path: entry_path.to_string(),
})
})
.collect();
let mut entries = entries_results?;
entries.push(TreeEntry {
info,
path: entry_path.to_string(),
});
entries
});
// We want to ensure that in the response Tree entries come first. `Ord` being
// derived on the enum ensures Variant declaration order.
+22 -22
View File
@@ -32,7 +32,7 @@ mod path {
mod directory {
use git_ref_format::refname;
use radicle_surf::{
file_system::DirectoryEntry,
file_system::directory,
git::{Branch, Repository},
};
use std::path::Path;
@@ -40,38 +40,38 @@ mod directory {
const GIT_PLATINUM: &str = "../data/git-platinum";
#[test]
fn directory_get_path() {
fn directory_find_entry() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let root = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
// get_path for a file.
// find_entry for a file.
let path = Path::new("src/memory.rs");
let entry = root.get_path(path, &repo).unwrap();
assert!(matches!(entry, DirectoryEntry::File(_)));
let entry = root.find_entry(path, &repo).unwrap();
assert!(matches!(entry, Some(directory::Entry::File(_))));
// get_path for a directory.
// find_entry for a directory.
let path = Path::new("this/is/a/really/deeply/nested/directory/tree");
let entry = root.get_path(path, &repo).unwrap();
assert!(matches!(entry, DirectoryEntry::Directory(_)));
let entry = root.find_entry(path, &repo).unwrap();
assert!(matches!(entry, Some(directory::Entry::Directory(_))));
// get_path for a non-leaf directory and its relative path.
// find_entry for a non-leaf directory and its relative path.
let path = Path::new("text");
let entry = root.get_path(path, &repo).unwrap();
assert!(matches!(entry, DirectoryEntry::Directory(_)));
if let DirectoryEntry::Directory(sub_dir) = entry {
let entry = root.find_entry(path, &repo).unwrap();
assert!(matches!(entry, Some(directory::Entry::Directory(_))));
if let Some(directory::Entry::Directory(sub_dir)) = entry {
let inner_path = Path::new("garden.txt");
let inner_entry = sub_dir.get_path(inner_path, &repo).unwrap();
assert!(matches!(inner_entry, DirectoryEntry::File(_)));
let inner_entry = sub_dir.find_entry(inner_path, &repo).unwrap();
assert!(matches!(inner_entry, Some(directory::Entry::File(_))));
}
// get_path for non-existing file
// find_entry for non-existing file
let path = Path::new("this/is/a/really/missing_file");
let result = root.get_path(path, &repo);
assert!(result.is_err());
let result = root.find_entry(path, &repo).unwrap();
assert_eq!(result, None);
// get_path for absolute path: fail.
// find_entry for absolute path: fail.
let path = Path::new("/src/memory.rs");
let result = root.get_path(path, &repo);
let result = root.find_entry(path, &repo);
assert!(result.is_err());
}
@@ -89,9 +89,9 @@ mod directory {
*/
let path = Path::new("src");
let entry = root.get_path(path, &repo).unwrap();
assert!(matches!(entry, DirectoryEntry::Directory(_)));
if let DirectoryEntry::Directory(d) = entry {
let entry = root.find_entry(path, &repo).unwrap();
assert!(matches!(entry, Some(directory::Entry::Directory(_))));
if let Some(directory::Entry::Directory(d)) = entry {
assert_eq!(16297, d.size(&repo).unwrap());
}
}
+24 -22
View File
@@ -7,7 +7,7 @@
use radicle_surf::git::{Author, Commit};
use radicle_surf::{
diff::*,
file_system::{unsound, DirectoryEntry, Path},
file_system::{directory, unsound, Path},
git::{Branch, Error, Glob, Oid, Repository},
};
@@ -697,7 +697,7 @@ mod reference {
.unwrap();
assert_eq!(tags.len(), 6);
let root_dir = repo.root_dir(&tags[0]).unwrap();
assert_eq!(root_dir.contents(&repo).unwrap().iter().count(), 1);
assert_eq!(root_dir.entries(&repo).unwrap().entries().count(), 1);
}
#[test]
@@ -727,7 +727,7 @@ mod code_browsing {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
let count = println_dir(&root_dir, &repo, 0);
let count = println_dir(&root_dir, &repo);
assert_eq!(count, 36); // Check total file count.
@@ -735,16 +735,20 @@ mod code_browsing {
/// For sub-directories, will do Depth-First-Search and print
/// recursively.
/// Returns the number of items visited (i.e. printed)
fn println_dir(dir: &Directory, repo: &Repository, indent_level: usize) -> i32 {
let mut count = 0;
for item in dir.contents(repo).unwrap().iter() {
println!("> {}{}", " ".repeat(indent_level * 4), &item.label());
count += 1;
if let DirectoryEntry::Directory(sub_dir) = item {
count += println_dir(sub_dir, repo, indent_level + 1);
}
}
count
fn println_dir(dir: &Directory, repo: &Repository) -> i32 {
dir.traverse::<directory::error::Directory, _, _>(
repo,
(0, 0),
&mut |(count, indent_level), entry| {
println!("> {}{}", " ".repeat(indent_level * 4), entry.label());
match entry {
directory::Entry::File(_) => Ok((count + 1, indent_level)),
directory::Entry::Directory(_) => Ok((count + 1, indent_level + 1)),
}
},
)
.unwrap()
.0
}
}
@@ -752,20 +756,18 @@ mod code_browsing {
fn browse_repo_lazily() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
let count = root_dir.contents(&repo).unwrap().iter().count();
let count = root_dir.entries(&repo).unwrap().entries().count();
assert_eq!(count, 8);
let count = traverse(&root_dir, &repo);
assert_eq!(count, 36);
fn traverse(dir: &Directory, repo: &Repository) -> i32 {
let mut count = 0;
for item in dir.contents(repo).unwrap().iter() {
count += 1;
if let DirectoryEntry::Directory(sub_dir) = item {
count += traverse(sub_dir, repo)
}
}
count
dir.traverse::<directory::error::Directory, _, _>(
repo,
0,
&mut |count, _| Ok(count + 1),
)
.unwrap()
}
}