From 922206e08ea2675f2480f71867ce979f5a664aec Mon Sep 17 00:00:00 2001 From: Han Xu Date: Tue, 6 Dec 2022 05:59:23 -0800 Subject: [PATCH] refactored Directory, File, Tree and Blob. Signed-off-by: Han Xu --- radicle-surf/src/file_system/directory.rs | 76 ++++++-- radicle-surf/src/git/history.rs | 2 +- radicle-surf/src/git/repo.rs | 76 +++++++- radicle-surf/src/source/object/blob.rs | 105 +++++----- radicle-surf/src/source/object/tree.rs | 222 +++++++++++++++------- radicle-surf/t/src/file_system.rs | 13 ++ radicle-surf/t/src/git/code_browsing.rs | 6 +- radicle-surf/t/src/git/last_commit.rs | 24 +-- radicle-surf/t/src/source.rs | 134 +++++++++++-- 9 files changed, 478 insertions(+), 180 deletions(-) diff --git a/radicle-surf/src/file_system/directory.rs b/radicle-surf/src/file_system/directory.rs index 2fb8278..f930c07 100644 --- a/radicle-surf/src/file_system/directory.rs +++ b/radicle-surf/src/file_system/directory.rs @@ -31,7 +31,7 @@ use git2::Blob; use radicle_git_ext::{is_not_found_err, Oid}; use radicle_std_ext::result::ResultExt as _; -use crate::git::{Repository, Revision}; +use crate::git::{Commit, Repository, Revision}; pub mod error { use thiserror::Error; @@ -79,6 +79,8 @@ pub struct File { prefix: PathBuf, /// The object identifier of the git blob of this file. id: Oid, + /// The commit that created this file version. + last_commit: Commit, } impl File { @@ -88,14 +90,19 @@ impl File { /// so should not end in `name`. /// /// The `id` must point to a git blob. - pub(crate) fn new(name: String, prefix: PathBuf, id: Oid) -> Self { + pub(crate) fn new(name: String, prefix: PathBuf, id: Oid, last_commit: Commit) -> Self { debug_assert!( !prefix.ends_with(&name), "prefix = {:?}, name = {}", prefix, name ); - Self { name, prefix, id } + Self { + name, + prefix, + id, + last_commit, + } } /// The name of this `File`. @@ -256,12 +263,26 @@ impl Entry { pub(crate) fn from_entry( entry: &git2::TreeEntry, path: PathBuf, + repo: &Repository, + parent_commit: Oid, ) -> Result, error::Entry> { let name = entry.name().ok_or(error::Entry::Utf8Error)?.to_string(); let id = entry.id().into(); + let escaped_name = name.replace('\\', r"\\"); + let entry_path = path.join(escaped_name); + // FIXME: I don't like to use FIXME, but here it is. I would + // like to simplify the error definitions and then fix these + // unwrap(s). + let last_commit = repo + .last_commit(&entry_path, parent_commit) + .unwrap() + .unwrap(); + Ok(entry.kind().and_then(|kind| match kind { - git2::ObjectType::Tree => Some(Self::Directory(Directory::new(name, path, id))), - git2::ObjectType::Blob => Some(Self::File(File::new(name, path, id))), + git2::ObjectType::Tree => { + Some(Self::Directory(Directory::new(name, path, id, last_commit))) + }, + git2::ObjectType::Blob => Some(Self::File(File::new(name, path, id, last_commit))), _ => None, })) } @@ -285,16 +306,18 @@ pub struct Directory { prefix: PathBuf, /// The object identifier of the git tree of this directory. id: Oid, + /// The commit that created this directory version. + last_commit: Commit, } +const ROOT_DIR: &str = ""; + impl Directory { - /// Creates a directory given its `id`. + /// Creates a directory given its `tree_id`. /// /// The `name` and `prefix` are both set to be empty. - /// - /// The `id` must point to a `git` tree. - pub(crate) fn root(id: Oid) -> Self { - Self::new("".to_string(), PathBuf::new(), id) + pub(crate) fn root(tree_id: Oid, repo_commit: Commit) -> Self { + Self::new(ROOT_DIR.to_string(), PathBuf::new(), tree_id, repo_commit) } /// Creates a directory given its `name` and `id`. @@ -303,14 +326,19 @@ impl Directory { /// so should not end in `name`. /// /// The `id` must point to a `git` tree. - pub(crate) fn new(name: String, prefix: PathBuf, id: Oid) -> Self { + pub(crate) fn new(name: String, prefix: PathBuf, id: Oid, last_commit: Commit) -> Self { debug_assert!( name.is_empty() || !prefix.ends_with(&name), "prefix = {:?}, name = {}", prefix, name ); - Self { name, prefix, id } + Self { + name, + prefix, + id, + last_commit, + } } /// Get the name of the current `Directory`. @@ -354,9 +382,10 @@ impl Directory { let mut error = None; let path = self.path(); - // Walks only the first level of entries. - tree.walk(git2::TreeWalkMode::PreOrder, |_, entry| { - match Entry::from_entry(entry, path.clone()) { + // Walks only the first level of entries. And `_entry_path` is always + // empty for the first level. + tree.walk(git2::TreeWalkMode::PreOrder, |_entry_path, entry| { + match Entry::from_entry(entry, path.clone(), repo, self.last_commit.id) { Ok(Some(entry)) => match entry { Entry::File(_) => { entries.insert(entry.name().clone(), entry); @@ -382,6 +411,11 @@ impl Directory { } } + /// Returns the last commit that created or modified this directory. + pub fn last_commit(&self) -> &Commit { + &self.last_commit + } + /// Find the [`Entry`] found at `path`, if it exists. pub fn find_entry

( &self, @@ -401,9 +435,13 @@ impl Directory { let parent = path .parent() .ok_or_else(|| error::Directory::InvalidPath(path.to_string_lossy().to_string()))?; + let root_path = self.path().join(parent); Ok(entry - .and_then(|entry| Entry::from_entry(&entry, parent.to_path_buf()).transpose()) + .and_then(|entry| { + Entry::from_entry(&entry, root_path.to_path_buf(), repo, self.last_commit.id) + .transpose() + }) .transpose() .unwrap()) } @@ -424,6 +462,8 @@ impl Directory { } /// Find the `Directory` found at `path`, if it exists. + /// + /// If `path` is `ROOT_DIR` (i.e. an empty path), returns self. pub fn find_directory

( &self, path: &P, @@ -432,6 +472,10 @@ impl Directory { where P: AsRef, { + if path.as_ref() == Path::new(ROOT_DIR) { + return Ok(Some(self.clone())); + } + Ok(match self.find_entry(path, repo)? { Some(Entry::Directory(d)) => Some(d), _ => None, diff --git a/radicle-surf/src/git/history.rs b/radicle-surf/src/git/history.rs index 4405c7d..db23f9b 100644 --- a/radicle-surf/src/git/history.rs +++ b/radicle-surf/src/git/history.rs @@ -62,7 +62,7 @@ impl<'a> History<'a> { /// /// Note that it is possible that a filtered History becomes empty, /// even though calling `.head()` still returns the original head. - pub fn by_path

(mut self, path: P) -> Self + pub fn by_path

(mut self, path: &P) -> Self where P: AsRef, { diff --git a/radicle-surf/src/git/repo.rs b/radicle-surf/src/git/repo.rs index 79fdab3..91efd95 100644 --- a/radicle-surf/src/git/repo.rs +++ b/radicle-surf/src/git/repo.rs @@ -28,7 +28,10 @@ use thiserror::Error; use crate::{ diff::{self, *}, - file_system::{directory::FileContent, Directory}, + file_system::{ + directory::{self, File, FileContent}, + Directory, + }, git::{ commit, glob, @@ -44,6 +47,7 @@ use crate::{ Tag, ToCommit, }, + source::{self, commit::Header, Blob, Tree, TreeEntry}, }; pub mod iter; @@ -66,6 +70,10 @@ pub enum Error { Diff(#[from] diff::git::error::Diff), /// A wrapper around the generic [`git2::Error`]. #[error(transparent)] + Directory(#[from] directory::error::Directory), + #[error(transparent)] + File(#[from] directory::error::File), + #[error(transparent)] Git(#[from] git2::Error), #[error(transparent)] Glob(#[from] glob::Error), @@ -249,12 +257,72 @@ impl Repository { .map_err(|err| Error::ToCommit(err.into()))?; let git2_commit = self.inner.find_commit((commit.id).into())?; let tree = git2_commit.as_object().peel_to_tree()?; - Ok(Directory::root(tree.id().into())) + Ok(Directory::root(tree.id().into(), commit)) + } + + /// Returns a [`Directory`] for `path` in `commit`. + pub fn directory>( + &self, + commit: C, + path: &P, + ) -> Result { + let root = self.root_dir(commit)?; + root.find_directory(path, self)? + .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf())) + } + + /// Returns a [`File`] for `path` in `commit`. + pub fn file>(&self, commit: C, path: &P) -> Result { + let root = self.root_dir(commit)?; + root.find_file(path, self)? + .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf())) + } + + /// Returns a [`Tree`] for `path` in `commit`. + pub fn tree>(&self, commit: C, path: &P) -> Result { + let commit = commit + .to_commit(self) + .map_err(|e| Error::ToCommit(e.into()))?; + let dir = self.directory(commit.id, path)?; + let mut entries = dir + .entries(self)? + .map(|en| { + let name = en.name().to_string(); + let path = en.path(); + let commit = self + .last_commit(&path, commit.id)? + .ok_or(Error::PathNotFound(path))?; + let commit_header = Header::from(commit); + Ok(TreeEntry::new(name, en.into(), commit_header)) + }) + .collect::, Error>>()?; + entries.sort(); + + let last_commit = self + .last_commit(path, commit)? + .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf()))?; + let header = source::commit::Header::from(last_commit); + Ok(Tree::new(dir.id(), entries, header)) + } + + /// Returns a [`Blob`] for `path` in `commit`. + pub fn blob>(&self, commit: C, path: &P) -> Result { + let commit = commit + .to_commit(self) + .map_err(|e| Error::ToCommit(e.into()))?; + let file = self.file(commit.id, path)?; + let last_commit = self + .last_commit(path, commit)? + .ok_or_else(|| Error::PathNotFound(path.as_ref().to_path_buf()))?; + let header = source::commit::Header::from(last_commit); + + let content = file.content(self)?; + Ok(Blob::new(file.id(), content.as_bytes(), header)) } /// Returns the last commit, if exists, for a `path` in the history of /// `rev`. - pub fn last_commit(&self, path: P, rev: C) -> Result, Error> + pub fn last_commit(&self, path: &P, rev: C) -> Result, Error> where P: AsRef, C: ToCommit, @@ -444,7 +512,7 @@ impl Repository { let mut opts = git2::DiffOptions::new(); if let Some(path) = path { - opts.pathspec(path); + opts.pathspec(path.to_string_lossy().to_string()); // We're skipping the binary pass because we won't be inspecting deltas. opts.skip_binary_check(true); } diff --git a/radicle-surf/src/source/object/blob.rs b/radicle-surf/src/source/object/blob.rs index 281ef93..c245112 100644 --- a/radicle-surf/src/source/object/blob.rs +++ b/radicle-surf/src/source/object/blob.rs @@ -18,70 +18,33 @@ //! Represents git object type 'blob', i.e. actual file contents. //! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details. -use std::{path::Path, str}; +use std::str; +use radicle_git_ext::Oid; #[cfg(feature = "serde")] use serde::{ ser::{SerializeStruct as _, Serializer}, Serialize, }; -use crate::{ - file_system::{File, FileContent}, - git::{self, Repository}, - source::{commit, object::Error}, -}; +use crate::source::commit; -/// File data abstraction. +/// Represents a git blob object. pub struct Blob { - pub file: File, - pub content: BlobContent, - pub commit: Option, + id: Oid, + content: BlobContent, + commit: commit::Header, } impl Blob { /// Returns the [`Blob`] for a file at `revision` under `path`. - /// - /// # Errors - /// - /// Will return [`Error`] if the project doesn't exist or a surf interaction - /// fails. - pub fn new(repo: &Repository, revision: &R, path: &P) -> Result - where - P: AsRef, - R: git::Revision, - { - Self::make_blob(repo, revision, path, |c| BlobContent::from(c)) - } - - fn make_blob( - repo: &Repository, - revision: &R, - path: &P, - content: impl FnOnce(FileContent) -> BlobContent, - ) -> Result - where - P: AsRef, - R: git::Revision, - { - let path = path.as_ref(); - let root = repo.root_dir(revision)?; - - let file = root - .find_file(&path, repo)? - .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?; - - let last_commit = repo - .last_commit(path, revision)? - .map(|c| commit::Header::from(&c)); - - let content = content(file.content(repo)?); - - Ok(Blob { - file, + pub(crate) fn new(id: Oid, content: &[u8], commit: commit::Header) -> Self { + let content = BlobContent::from(content); + Self { + id, content, - commit: last_commit, - }) + commit, + } } /// Indicates if the content of the [`Blob`] is binary. @@ -89,6 +52,19 @@ impl Blob { pub fn is_binary(&self) -> bool { matches!(self.content, BlobContent::Binary(_)) } + + pub fn object_id(&self) -> Oid { + self.id + } + + pub fn content(&self) -> &BlobContent { + &self.content + } + + /// Returns the commit that created this blob. + pub fn commit(&self) -> &commit::Header { + &self.commit + } } #[cfg(feature = "serde")] @@ -102,8 +78,6 @@ impl Serialize for Blob { state.serialize_field("binary", &self.is_binary())?; state.serialize_field("content", &self.content)?; state.serialize_field("lastCommit", &self.commit)?; - state.serialize_field("name", &self.file.name())?; - state.serialize_field("path", &self.file.path())?; state.end() } } @@ -117,6 +91,24 @@ pub enum BlobContent { Binary(Vec), } +impl BlobContent { + /// Returns the size of this `BlobContent`. + pub fn size(&self) -> usize { + match self { + Self::Plain(content) => content.len(), + Self::Binary(bytes) => bytes.len(), + } + } + + /// Returns the content as bytes. + pub fn as_bytes(&self) -> &[u8] { + match self { + Self::Plain(content) => content.as_bytes(), + Self::Binary(bytes) => &bytes[..], + } + } +} + #[cfg(feature = "serde")] impl Serialize for BlobContent { fn serialize(&self, serializer: S) -> Result @@ -133,12 +125,11 @@ impl Serialize for BlobContent { } } -impl From> for BlobContent { - fn from(content: FileContent) -> Self { - let content = content.as_bytes(); - match str::from_utf8(content) { +impl From<&[u8]> for BlobContent { + fn from(bytes: &[u8]) -> Self { + match str::from_utf8(bytes) { Ok(utf8) => BlobContent::Plain(utf8.to_owned()), - Err(_) => BlobContent::Binary(content.to_owned()), + Err(_) => BlobContent::Binary(bytes.to_owned()), } } } diff --git a/radicle-surf/src/source/object/tree.rs b/radicle-surf/src/source/object/tree.rs index 40e5a4b..4463946 100644 --- a/radicle-surf/src/source/object/tree.rs +++ b/radicle-surf/src/source/object/tree.rs @@ -18,125 +18,213 @@ //! Represents git object type 'tree', i.e. like directory entries in Unix. //! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details. -use std::path::Path; +use std::cmp::Ordering; +use radicle_git_ext::Oid; #[cfg(feature = "serde")] use serde::{ ser::{SerializeStruct as _, Serializer}, Serialize, }; -use crate::{ - file_system::{directory, Directory}, - git::{self, Repository}, - source::{commit, object::Error}, -}; +use crate::{file_system::directory, source::commit}; -/// Result of a directory listing, carries other trees and blobs. +/// Represents a tree object as in git. It is essentially the content of +/// one directory. Note that multiple directories can have the same content, +/// i.e. have the same tree object. Hence this struct does not embed its path. #[derive(Clone, Debug)] pub struct Tree { - pub directory: Directory, - pub commit: Option, - /// Entries listed in that tree result. - pub entries: Vec, + /// The object id of this tree. + id: Oid, + entries: Vec, + /// The commit object that created this tree object. + commit: commit::Header, } impl Tree { - /// Retrieve the [`Tree`] for the given `revision` and directory `prefix`. - /// - /// # Errors - /// - /// Will return [`Error`] if any of the surf interactions fail. - pub fn new(repo: &Repository, revision: &R, prefix: Option<&P>) -> Result - where - P: AsRef, - R: git::Revision, - { - let prefix = prefix.map(|p| p.as_ref()); - - let prefix_dir = match prefix { - None => repo.root_dir(revision)?, - Some(path) => repo - .root_dir(revision)? - .find_directory(&path, repo)? - .ok_or_else(|| Error::PathNotFound(path.to_path_buf()))?, - }; - - let mut entries = prefix_dir - .entries(repo)? - .entries() - .cloned() - .map(TreeEntry::from) - .collect::>(); - entries.sort(); - - let last_commit = if prefix.is_none() { - let history = repo.history(revision)?; - Some(commit::Header::from(history.head())) - } else { - None - }; - - Ok(Tree { + /// Creates a new tree. + pub(crate) fn new(id: Oid, entries: Vec, commit: commit::Header) -> Self { + Self { + id, entries, - directory: prefix_dir, - commit: last_commit, - }) + commit, + } + } + + pub fn object_id(&self) -> Oid { + self.id + } + + /// Returns the commit that created this tree. + pub fn commit(&self) -> &commit::Header { + &self.commit + } + + /// Returns the entries of the tree. + pub fn entries(&self) -> &Vec { + &self.entries } } #[cfg(feature = "serde")] impl Serialize for Tree { + /// Sample output: + /// (for `` and `` sample output, see [`TreeEntry`]) + /// ``` + /// { + /// "entries": [ + /// { }, + /// { }, + /// ], + /// "lastCommit": { + /// "author": { + /// "email": "foobar@gmail.com", + /// "name": "Foo Bar" + /// }, + /// "committer": { + /// "email": "noreply@github.com", + /// "name": "GitHub" + /// }, + /// "committerTime": 1582198877, + /// "description": "A sample commit.", + /// "sha1": "b57846bbc8ced6587bf8329fc4bce970eb7b757e", + /// "summary": "Add a new sample" + /// }, + /// "oid": "dd52e9f8dfe1d8b374b2a118c25235349a743dd2" + /// } + /// ``` fn serialize(&self, serializer: S) -> Result where S: Serializer, { const FIELDS: usize = 4; let mut state = serializer.serialize_struct("Tree", FIELDS)?; + state.serialize_field("oid", &self.id)?; state.serialize_field("entries", &self.entries)?; state.serialize_field("lastCommit", &self.commit)?; - state.serialize_field("name", &self.directory.name())?; - state.serialize_field("path", &self.directory.path())?; state.end() } } -/// Entry in a Tree result. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct TreeEntry { - pub entry: directory::Entry, +#[derive(Debug, Clone, Copy)] +pub enum Entry { + Tree(Oid), + Blob(Oid), } -impl From for TreeEntry { - fn from(entry: directory::Entry) -> Self { - Self { entry } +/// Entry in a Tree result. +#[derive(Clone, Debug)] +pub struct TreeEntry { + name: String, + entry: Entry, + + /// The commit object that created this entry object. + commit: commit::Header, +} + +impl TreeEntry { + pub(crate) fn new(name: String, entry: Entry, commit: commit::Header) -> Self { + Self { + name, + entry, + commit, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn entry(&self) -> &Entry { + &self.entry + } + + pub fn is_tree(&self) -> bool { + matches!(self.entry, Entry::Tree(_)) + } + + pub fn commit(&self) -> &commit::Header { + &self.commit + } + + pub fn object_id(&self) -> Oid { + match self.entry { + Entry::Blob(id) => id, + Entry::Tree(id) => id, + } } } -impl From for directory::Entry { - fn from(TreeEntry { entry }: TreeEntry) -> Self { - entry +// To support `sort`. +impl Ord for TreeEntry { + fn cmp(&self, other: &Self) -> Ordering { + self.name.cmp(&other.name) + } +} + +impl PartialOrd for TreeEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for TreeEntry { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for TreeEntry {} + +impl From for Entry { + fn from(entry: directory::Entry) -> Self { + match entry { + directory::Entry::File(f) => Entry::Blob(f.id()), + directory::Entry::Directory(d) => Entry::Tree(d.id()), + } } } #[cfg(feature = "serde")] impl Serialize for TreeEntry { + /// Sample output: + /// ``` + /// { + /// "kind": "blob", + /// "lastCommit": { + /// "author": { + /// "email": "foobar@gmail.com", + /// "name": "Foo Bar" + /// }, + /// "committer": { + /// "email": "noreply@github.com", + /// "name": "GitHub" + /// }, + /// "committerTime": 1578309972, + /// "description": "This is a sample file", + /// "sha1": "2873745c8f6ffb45c990eb23b491d4b4b6182f95", + /// "summary": "Add a new sample" + /// }, + /// "name": "Sample.rs", + /// "oid": "6d6240123a8d8ea8a8376610168a0a4bcb96afd0" + /// }, + /// ``` fn serialize(&self, serializer: S) -> Result where S: Serializer, { const FIELDS: usize = 4; let mut state = serializer.serialize_struct("TreeEntry", FIELDS)?; - state.serialize_field("path", &self.entry.path())?; - state.serialize_field("name", &self.entry.name())?; - state.serialize_field("lastCommit", &None::)?; + state.serialize_field("name", &self.name)?; state.serialize_field( "kind", match self.entry { - directory::Entry::File(_) => "blob", - directory::Entry::Directory(_) => "tree", + Entry::Blob(_) => "blob", + Entry::Tree(_) => "tree", }, )?; + state.serialize_field("oid", &self.object_id())?; + state.serialize_field("lastCommit", &self.commit)?; state.end() } } diff --git a/radicle-surf/t/src/file_system.rs b/radicle-surf/t/src/file_system.rs index e232dd1..6ce557b 100644 --- a/radicle-surf/t/src/file_system.rs +++ b/radicle-surf/t/src/file_system.rs @@ -85,6 +85,7 @@ mod directory { .find_directory(&Path::new("src"), &repo) .unwrap() .unwrap(); + assert_eq!(src.path(), Path::new("src").to_path_buf()); let src_contents: Vec = src.entries(&repo).unwrap().collect(); assert_eq!(src_contents.len(), 3); assert_eq!(src_contents[0].name(), "Eval.hs"); @@ -112,4 +113,16 @@ mod directory { assert_eq!(16297, d.size(&repo).unwrap()); } } + + #[test] + fn directory_last_commit() { + let repo = Repository::open(GIT_PLATINUM).unwrap(); + let root = repo.root_dir(Branch::local(refname!("dev"))).unwrap(); + let dir = root.find_directory(&"this/is", &repo).unwrap().unwrap(); + let last_commit = dir.last_commit(); + assert_eq!( + last_commit.id.to_string(), + "2429f097664f9af0c5b7b389ab998b2199ffa977" + ); + } } diff --git a/radicle-surf/t/src/git/code_browsing.rs b/radicle-surf/t/src/git/code_browsing.rs index 2aa0795..181febc 100644 --- a/radicle-surf/t/src/git/code_browsing.rs +++ b/radicle-surf/t/src/git/code_browsing.rs @@ -59,11 +59,9 @@ fn test_file_history() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let history = repo.history(&Branch::local(refname!("dev"))).unwrap(); let path = Path::new("README.md"); - let mut file_history = history.by_path(path); + let mut file_history = history.by_path(&path); let commit = file_history.next().unwrap().unwrap(); - let file = repo - .get_commit_file(&commit.id, &Path::new("README.md")) - .unwrap(); + let file = repo.get_commit_file(&commit.id, &path).unwrap(); assert_eq!(file.size(), 67); } diff --git a/radicle-surf/t/src/git/last_commit.rs b/radicle-surf/t/src/git/last_commit.rs index c461724..22ee5e9 100644 --- a/radicle-surf/t/src/git/last_commit.rs +++ b/radicle-surf/t/src/git/last_commit.rs @@ -1,7 +1,4 @@ -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{path::PathBuf, str::FromStr}; use git_ref_format::refname; use radicle_git_ext::Oid; @@ -18,7 +15,7 @@ fn readme_missing_and_memory() { // memory.rs is commited later so it should not exist here. let memory_last_commit_oid = repo - .last_commit(Path::new("src/memory.rs"), oid) + .last_commit(&"src/memory.rs", oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -26,7 +23,7 @@ fn readme_missing_and_memory() { // README.md exists in this commit. let readme_last_commit = repo - .last_commit(Path::new("README.md"), oid) + .last_commit(&"README.md", oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -44,7 +41,7 @@ fn folder_svelte() { let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap(); let folder_svelte = repo - .last_commit(Path::new("examples/Folder.svelte"), oid) + .last_commit(&"examples/Folder.svelte", oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -62,10 +59,7 @@ fn nest_directory() { let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap(); let nested_directory_tree_commit_id = repo - .last_commit( - Path::new("this/is/a/really/deeply/nested/directory/tree"), - oid, - ) + .last_commit(&"this/is/a/really/deeply/nested/directory/tree", oid) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -85,13 +79,13 @@ fn can_get_last_commit_for_special_filenames() { let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap(); let backslash_commit_id = repo - .last_commit(Path::new(r"special/faux\\path"), oid) + .last_commit(&r"special/faux\\path", oid) .expect("Failed to get last commit") .map(|commit| commit.id); assert_eq!(backslash_commit_id, Some(expected_commit_id)); let ogre_commit_id = repo - .last_commit(Path::new("special/👹👹👹"), oid) + .last_commit(&"special/👹👹👹", oid) .expect("Failed to get last commit") .map(|commit| commit.id); assert_eq!(ogre_commit_id, Some(expected_commit_id)); @@ -103,7 +97,7 @@ fn root() { .expect("Could not retrieve ./data/git-platinum as git repository"); let rev = Branch::local(refname!("master")); let root_last_commit_id = repo - .last_commit(PathBuf::new(), rev) + .last_commit(&PathBuf::new(), rev) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -120,7 +114,7 @@ fn binary_file() { let repo = Repository::open(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); let history = repo.history(&Branch::local(refname!("dev"))).unwrap(); - let file_commit = history.by_path(Path::new("bin/cat")).next(); + let file_commit = history.by_path(&"bin/cat").next(); assert!(file_commit.is_some()); println!("file commit: {:?}", &file_commit); } diff --git a/radicle-surf/t/src/source.rs b/radicle-surf/t/src/source.rs index 5f098ee..f0cfd3c 100644 --- a/radicle-surf/t/src/source.rs +++ b/radicle-surf/t/src/source.rs @@ -1,7 +1,5 @@ -use std::path::Path; - use git_ref_format::refname; -use radicle_surf::{git::Repository, source}; +use radicle_surf::git::Repository; use serde_json::json; const GIT_PLATINUM: &str = "../data/git-platinum"; @@ -9,31 +7,135 @@ const GIT_PLATINUM: &str = "../data/git-platinum"; #[test] fn tree_serialization() { let repo = Repository::open(GIT_PLATINUM).unwrap(); - let tree = source::Tree::new( - &repo, - &refname!("refs/heads/master"), - Some(&Path::new("src")), - ) - .unwrap(); + let tree = repo.tree(refname!("refs/heads/master"), &"src").unwrap(); let expected = json!({ "entries": [ { "kind": "blob", - "lastCommit": null, + "lastCommit": { + "author": { + "email": "fintan.halpenny@gmail.com", + "name": "Fintan Halpenny" + }, + "committer": { + "email": "noreply@github.com", + "name": "GitHub" + }, + "committerTime": 1578309972, + "description": "I want to have files under src that have separate commits.\r\nThat way src's latest commit isn't the same as all its files, instead it's the file that was touched last.", + "sha1": "3873745c8f6ffb45c990eb23b491d4b4b6182f95", + "summary": "Extend the docs (#2)" + }, "name": "Eval.hs", - "path": "src/Eval.hs" + "oid": "7d6240123a8d8ea8a8376610168a0a4bcb96afd0" }, { "kind": "blob", - "lastCommit": null, + "lastCommit": { + "author": { + "email": "rudolfs@osins.org", + "name": "Rūdolfs Ošiņš" + }, + "committer": { + "email": "rudolfs@osins.org", + "name": "Rūdolfs Ošiņš" + }, + "committerTime": 1575283266, + "description": "", + "sha1": "e24124b7538658220b5aaf3b6ef53758f0a106dc", + "summary": "Move examples to \"src\"" + }, "name": "memory.rs", - "path": "src/memory.rs" + "oid": "b84992d24be67536837f5ab45a943f1b3f501878" } ], - "lastCommit": null, - "name": "src", - "path": "src" + "lastCommit": { + "author": { + "email": "rudolfs@osins.org", + "name": "Rūdolfs Ošiņš" + }, + "committer": { + "email": "noreply@github.com", + "name": "GitHub" + }, + "committerTime": 1582198877, + "description": "It was a bad idea to have an actual source file which is used by\r\nradicle-upstream in the fixtures repository. It gets in the way of\r\nlinting and editors pick it up as a regular source file by accident.", + "sha1": "a57846bbc8ced6587bf8329fc4bce970eb7b757e", + "summary": "Remove src/Folder.svelte (#3)" + }, + "oid": "ed52e9f8dfe1d8b374b2a118c25235349a743dd2" }); assert_eq!(serde_json::to_value(tree).unwrap(), expected) } + +#[test] +fn repo_tree() { + let repo = Repository::open(GIT_PLATINUM).unwrap(); + let tree = repo + .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"src") + .unwrap(); + assert_eq!(tree.entries().len(), 3); + + let commit_header = tree.commit(); + assert_eq!( + commit_header.sha1.to_string(), + "e24124b7538658220b5aaf3b6ef53758f0a106dc" + ); + + let tree_oid = tree.object_id(); + assert_eq!( + tree_oid.to_string(), + "dbd5d80c64a00969f521b96401a315e9481e9561" + ); + + let entries = tree.entries(); + assert_eq!(entries.len(), 3); + let entry = &entries[0]; + assert!(!entry.is_tree()); + assert_eq!(entry.name(), "Eval.hs"); + assert_eq!( + entry.object_id().to_string(), + "8c7447d13b907aa994ac3a38317c1e9633bf0732" + ); + let commit = entry.commit(); + assert_eq!( + commit.sha1.to_string(), + "e24124b7538658220b5aaf3b6ef53758f0a106dc" + ); + + // Verify that an empty path works for getting the root tree. + let root_tree = repo + .tree("27acd68c7504755aa11023300890bb85bbd69d45", &"") + .unwrap(); + assert_eq!(root_tree.entries().len(), 8); +} + +#[test] +fn repo_blob() { + let repo = Repository::open(GIT_PLATINUM).unwrap(); + let blob = repo + .blob("27acd68c7504755aa11023300890bb85bbd69d45", &"src/memory.rs") + .unwrap(); + + let blob_oid = blob.object_id(); + assert_eq!( + blob_oid.to_string(), + "b84992d24be67536837f5ab45a943f1b3f501878" + ); + + let commit_header = blob.commit(); + assert_eq!( + commit_header.sha1.to_string(), + "e24124b7538658220b5aaf3b6ef53758f0a106dc" + ); + + assert!(!blob.is_binary()); + + // Verify the blob content size matches with the file size of "memory.rs" + let content = blob.content(); + assert_eq!(content.size(), 6253); + + // Verify as_bytes. + assert_eq!(content.as_bytes().len(), content.size()); +}