Merge remote-tracking branch 'han/dogfood'

* han/dogfood:
  refactored Directory, File, Tree and Blob.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Fintan Halpenny
2022-12-22 10:49:38 +00:00
9 changed files with 478 additions and 180 deletions
+60 -16
View File
@@ -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<Option<Self>, 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<P>(
&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<P>(
&self,
path: &P,
@@ -432,6 +472,10 @@ impl Directory {
where
P: AsRef<Path>,
{
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,
+1 -1
View File
@@ -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<P>(mut self, path: P) -> Self
pub fn by_path<P>(mut self, path: &P) -> Self
where
P: AsRef<Path>,
{
+72 -4
View File
@@ -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<C: ToCommit, P: AsRef<Path>>(
&self,
commit: C,
path: &P,
) -> Result<Directory, Error> {
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<C: ToCommit, P: AsRef<Path>>(&self, commit: C, path: &P) -> Result<File, Error> {
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<C: ToCommit, P: AsRef<Path>>(&self, commit: C, path: &P) -> Result<Tree, Error> {
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::<Result<Vec<TreeEntry>, 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<C: ToCommit, P: AsRef<Path>>(&self, commit: C, path: &P) -> Result<Blob, Error> {
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<P, C>(&self, path: P, rev: C) -> Result<Option<Commit>, Error>
pub fn last_commit<P, C>(&self, path: &P, rev: C) -> Result<Option<Commit>, Error>
where
P: AsRef<Path>,
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);
}
+48 -57
View File
@@ -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<commit::Header>,
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<P, R>(repo: &Repository, revision: &R, path: &P) -> Result<Blob, Error>
where
P: AsRef<Path>,
R: git::Revision,
{
Self::make_blob(repo, revision, path, |c| BlobContent::from(c))
}
fn make_blob<P, R>(
repo: &Repository,
revision: &R,
path: &P,
content: impl FnOnce(FileContent) -> BlobContent,
) -> Result<Blob, Error>
where
P: AsRef<Path>,
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<u8>),
}
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -133,12 +125,11 @@ impl Serialize for BlobContent {
}
}
impl From<FileContent<'_>> 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()),
}
}
}
+155 -67
View File
@@ -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<commit::Header>,
/// Entries listed in that tree result.
pub entries: Vec<TreeEntry>,
/// The object id of this tree.
id: Oid,
entries: Vec<TreeEntry>,
/// 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<P, R>(repo: &Repository, revision: &R, prefix: Option<&P>) -> Result<Tree, Error>
where
P: AsRef<Path>,
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::<Vec<_>>();
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<TreeEntry>, 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<TreeEntry> {
&self.entries
}
}
#[cfg(feature = "serde")]
impl Serialize for Tree {
/// Sample output:
/// (for `<entry_1>` and `<entry_2>` sample output, see [`TreeEntry`])
/// ```
/// {
/// "entries": [
/// { <entry_1> },
/// { <entry_2> },
/// ],
/// "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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<directory::Entry> 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<TreeEntry> 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<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for TreeEntry {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for TreeEntry {}
impl From<directory::Entry> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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::<commit::Header>)?;
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()
}
}
+13
View File
@@ -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<Entry> = 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"
);
}
}
+2 -4
View File
@@ -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);
}
+9 -15
View File
@@ -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);
}
+118 -16
View File
@@ -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());
}