Remove RepositoryRef type and simply use &Repository.

Signed-off-by: Han Xu <keepsimple@gmail.com>
This commit is contained in:
Han Xu
2022-11-24 11:23:42 +00:00
committed by Fintan Halpenny
parent d3115a2215
commit c44348fc3c
16 changed files with 113 additions and 239 deletions
+9 -55
View File
@@ -1,13 +1,16 @@
# radicle-surf
A code surfing library for VCS file systems 🏄‍♀️🏄‍♂️
A code surfing library for Git repositories 🏄‍♀️🏄‍♂️
Welcome to `radicle-surf`!
`radicle-surf` is a system to describe a file-system in a VCS world.
We have the concept of files and directories, but these objects can change over time while people iterate on them.
Thus, it is a file-system within history and we, the user, are viewing the file-system at a particular snapshot.
Alongside this, we will wish to take two snapshots and view their differences.
`radicle-surf` is a library to describe a Git repository as a file system. It
aims to provide an easy-to-use API to browse a repository via the concept of
files and directories for any given revision. It also allows the user to diff
any two different revisions.
One of the use cases would be to create a web GUI for interacting with a Git
repository (thinking GitHub, GitLab or similar systems).
## Contributing
@@ -18,57 +21,8 @@ our [LICENSE](../LICENSE) file.
Join our community disccussions at [radicle.community](https://radicle.community)!
# Example
To a taste for the capabilities of `radicle-surf` we provide an example below, but we also
keep our documentation and doc-tests up to date.
```rust
use radicle_surf::vcs::git;
use radicle_surf::file_system::{Label, Path, SystemType};
use radicle_surf::file_system::unsound;
use pretty_assertions::assert_eq;
use std::str::FromStr;
// We're going to point to this repo.
let repo = git::Repository::new("./data/git-platinum")?;
// Here we initialise a new Broswer for a the git repo.
let mut browser = git::Browser::new(&repo, "master")?;
// Set the history to a particular commit
let commit = git::Oid::from_str("80ded66281a4de2889cc07293a8f10947c6d57fe")?;
browser.commit(commit)?;
// Get the snapshot of the directory for our current HEAD of history.
let directory = browser.get_directory()?;
// Let's get a Path to the memory.rs file
let memory = unsound::path::new("src/memory.rs");
// And assert that we can find it!
assert!(directory.find_file(memory).is_some());
let root_contents = directory.list_directory();
assert_eq!(root_contents, vec![
SystemType::file(unsound::label::new(".i-am-well-hidden")),
SystemType::file(unsound::label::new(".i-too-am-hidden")),
SystemType::file(unsound::label::new("README.md")),
SystemType::directory(unsound::label::new("bin")),
SystemType::directory(unsound::label::new("src")),
SystemType::directory(unsound::label::new("text")),
SystemType::directory(unsound::label::new("this")),
]);
let src = directory
.find_directory(Path::new(unsound::label::new("src")))
.expect("failed to find src");
let src_contents = src.list_directory();
assert_eq!(src_contents, vec![
SystemType::file(unsound::label::new("Eval.hs")),
SystemType::file(unsound::label::new("Folder.svelte")),
SystemType::file(unsound::label::new("memory.rs")),
]);
```
+1 -1
View File
@@ -36,7 +36,7 @@ fn last_commit_comparison(c: &mut Criterion) {
.iter()
{
group.bench_with_input(BenchmarkId::new("", path), path, |b, path| {
b.iter(|| repo.as_ref().last_commit(path.clone(), &rev))
b.iter(|| repo.last_commit(path.clone(), &rev))
});
}
}
+2 -3
View File
@@ -26,7 +26,7 @@
use radicle_surf::{
file_system::{Directory, DirectoryEntry},
git::{Repository, RepositoryRef},
git::Repository,
};
use std::{env, time::Instant};
@@ -39,7 +39,6 @@ fn main() {
},
};
let repo = Repository::discover(&repo_path).unwrap();
let repo = repo.as_ref();
let now = Instant::now();
let head = repo.head_oid().unwrap();
let root = repo.root_dir(head).unwrap();
@@ -49,7 +48,7 @@ fn main() {
println!("browse with print: {} ms", elapsed_millis);
}
fn print_directory(d: &Directory, repo: &RepositoryRef, indent_level: usize) {
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() {
+2 -2
View File
@@ -26,13 +26,13 @@ fn main() {
let options = get_options_or_exit();
let repo = init_repository_or_exit(&options.path_to_repo);
let head_oid = match options.head_revision {
HeadRevision::Head => repo.as_ref().head_oid().unwrap(),
HeadRevision::Head => repo.head_oid().unwrap(),
HeadRevision::Commit(id) => Oid::from_str(&id).unwrap(),
};
let base_oid = Oid::from_str(&options.base_revision).unwrap();
let now = Instant::now();
let elapsed_nanos = now.elapsed().as_nanos();
let diff = repo.as_ref().diff(base_oid, head_oid).unwrap();
let diff = repo.diff(base_oid, head_oid).unwrap();
print_diff_summary(&diff, elapsed_nanos);
}
+4 -4
View File
@@ -27,7 +27,7 @@ use serde::{
use crate::{
diff,
file_system,
git::{self, glob, Glob, RepositoryRef},
git::{self, glob, Glob, Repository},
person::Person,
revision::Revision,
};
@@ -146,7 +146,7 @@ pub struct Commits {
///
/// Will return [`Error`] if the project doesn't exist or the surf interaction
/// fails.
pub fn commit<R: git::Revision>(repo: &RepositoryRef, rev: R) -> Result<Commit, Error> {
pub fn commit<R: git::Revision>(repo: &Repository, rev: R) -> Result<Commit, Error> {
let commit = repo.commit(rev)?;
let sha1 = commit.id;
let header = Header::from(&commit);
@@ -216,7 +216,7 @@ pub fn commit<R: git::Revision>(repo: &RepositoryRef, rev: R) -> Result<Commit,
///
/// Will return [`Error`] if the project doesn't exist or the surf interaction
/// fails.
pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result<Header, Error> {
pub fn header(repo: &Repository, sha1: Oid) -> Result<Header, Error> {
let commit = repo.commit(sha1)?;
Ok(Header::from(&commit))
}
@@ -227,7 +227,7 @@ pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result<Header, Error> {
///
/// Will return [`Error`] if the project doesn't exist or the surf interaction
/// fails.
pub fn commits(repo: &RepositoryRef, maybe_revision: Option<Revision>) -> Result<Commits, Error> {
pub fn commits(repo: &Repository, maybe_revision: Option<Revision>) -> Result<Commits, Error> {
let rev = match maybe_revision {
Some(revision) => revision,
None => Revision::Sha {
+2 -2
View File
@@ -24,7 +24,7 @@ use serde::{ser, Serialize, Serializer};
use crate::{
file_system::{Directory, Path},
git::{Error, RepositoryRef},
git::{Error, Repository},
};
pub mod git;
@@ -296,7 +296,7 @@ impl Diff {
// TODO: Direction of comparison is not obvious with this signature.
// For now using conventional approach with the right being "newer".
#[allow(clippy::self_named_constructors)]
pub fn diff(left: Directory, right: Directory, repo: RepositoryRef) -> Result<Self, Error> {
pub fn diff(left: Directory, right: Directory, repo: Repository) -> Result<Self, Error> {
// TODO: Some of the deleted files may actually be moved (renamed) to one of the
// created files. Finding out which of the deleted files were deleted
// and which were moved will probably require performing some variant of
+8 -8
View File
@@ -22,7 +22,7 @@
use crate::{
file_system::{error::LabelError, path::*, Error},
git::{self, RepositoryRef, Revision},
git::{self, Repository, Revision},
};
use git2::Blob;
use radicle_git_ext::Oid;
@@ -130,7 +130,7 @@ impl Directory {
}
/// Returns a `DirectoryContent` for the current directory.
pub fn contents(&self, repo: &RepositoryRef) -> Result<DirectoryContent, git::Error> {
pub fn contents(&self, repo: &Repository) -> Result<DirectoryContent, git::Error> {
let listing = repo.directory_get(self)?;
Ok(DirectoryContent { listing })
}
@@ -139,10 +139,10 @@ impl Directory {
pub fn get_path(
&self,
path: &path::Path,
repo: &RepositoryRef,
repo: &Repository,
) -> Result<DirectoryEntry, git::Error> {
// Search the path in git2 tree.
let git2_tree = repo.repo_ref.find_tree(self.oid.into())?;
let git2_tree = repo.git2_repo().find_tree(self.oid.into())?;
let entry = git2_tree.get_path(path)?;
// Construct the DirectoryEntry.
@@ -204,7 +204,7 @@ impl Directory {
/// // 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: &RepositoryRef) -> Option<Oid> {
pub fn find_file(&self, path: Path, repo: &Repository) -> Option<Oid> {
let path_buf: std::path::PathBuf = (&path).into();
let entry = match self.get_path(path_buf.as_path(), repo) {
Ok(entry) => entry,
@@ -252,7 +252,7 @@ impl Directory {
/// // '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: &RepositoryRef) -> Option<Self> {
pub fn find_directory(&self, path: Path, repo: &Repository) -> Option<Self> {
let path_buf: std::path::PathBuf = (&path).into();
let entry = match self.get_path(path_buf.as_path(), repo) {
Ok(entry) => entry,
@@ -274,7 +274,7 @@ 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: &RepositoryRef) -> Result<usize, git::Error> {
pub fn size(&self, repo: &Repository) -> Result<usize, git::Error> {
let mut size = 0;
let contents = self.contents(repo)?;
for item in contents.iter() {
@@ -295,7 +295,7 @@ impl Directory {
impl Revision for Directory {
type Error = Infallible;
fn object_id(&self, _repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, _repo: &Repository) -> Result<Oid, Self::Error> {
Ok(self.oid)
}
}
+19 -19
View File
@@ -71,7 +71,7 @@ use git_ref_format::{name::Components, Component, Qualified, RefString};
pub use radicle_git_ext::Oid;
mod repo;
pub use repo::{Error, Repository, RepositoryRef};
pub use repo::{Error, Repository};
pub mod glob;
pub use glob::Glob;
@@ -116,45 +116,45 @@ pub trait Revision {
type Error: std::error::Error + Send + Sync + 'static;
/// Returns the object id of this revision in `repo`.
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error>;
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error>;
}
impl Revision for RefString {
type Error = git2::Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from)
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
repo.git2_repo().refname_to_id(self.as_str()).map(Oid::from)
}
}
impl Revision for &RefString {
type Error = git2::Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from)
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
repo.git2_repo().refname_to_id(self.as_str()).map(Oid::from)
}
}
impl Revision for Qualified<'_> {
type Error = git2::Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from)
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
repo.git2_repo().refname_to_id(self.as_str()).map(Oid::from)
}
}
impl Revision for &Qualified<'_> {
type Error = git2::Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from)
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
repo.git2_repo().refname_to_id(self.as_str()).map(Oid::from)
}
}
impl Revision for Oid {
type Error = Infallible;
fn object_id(&self, _repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, _repo: &Repository) -> Result<Oid, Self::Error> {
Ok(*self)
}
}
@@ -162,7 +162,7 @@ impl Revision for Oid {
impl Revision for &str {
type Error = git2::Error;
fn object_id(&self, _repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, _repo: &Repository) -> Result<Oid, Self::Error> {
Oid::from_str(self).map(Oid::from)
}
}
@@ -170,16 +170,16 @@ impl Revision for &str {
impl Revision for &Branch {
type Error = Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
let refname = repo.namespaced_refname(&self.refname())?;
Ok(repo.repo_ref.refname_to_id(&refname).map(Oid::from)?)
Ok(repo.git2_repo().refname_to_id(&refname).map(Oid::from)?)
}
}
impl Revision for &Tag {
type Error = Infallible;
fn object_id(&self, _repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, _repo: &Repository) -> Result<Oid, Self::Error> {
Ok(self.id())
}
}
@@ -189,13 +189,13 @@ pub trait ToCommit {
type Error: std::error::Error + Send + Sync + 'static;
/// Converts to a commit in `repo`.
fn to_commit(self, repo: &RepositoryRef) -> Result<Commit, Self::Error>;
fn to_commit(self, repo: &Repository) -> Result<Commit, Self::Error>;
}
impl ToCommit for Commit {
type Error = Infallible;
fn to_commit(self, _repo: &RepositoryRef) -> Result<Commit, Self::Error> {
fn to_commit(self, _repo: &Repository) -> Result<Commit, Self::Error> {
Ok(self)
}
}
@@ -203,9 +203,9 @@ impl ToCommit for Commit {
impl<R: Revision> ToCommit for R {
type Error = Error;
fn to_commit(self, repo: &RepositoryRef) -> Result<Commit, Self::Error> {
fn to_commit(self, repo: &Repository) -> Result<Commit, Self::Error> {
let oid = repo.object_id(&self)?;
let commit = repo.repo_ref.find_commit(oid.into())?;
let commit = repo.git2_repo().find_commit(oid.into())?;
Ok(Commit::try_from(commit)?)
}
}
+6 -6
View File
@@ -17,14 +17,14 @@
use crate::{
file_system,
git::{Commit, Error, RepositoryRef, ToCommit},
git::{Commit, Error, Repository, ToCommit},
};
use std::convert::TryFrom;
/// An iterator that produces the history of commits for a given `head`,
/// in the `repo`.
pub struct History<'a> {
repo: RepositoryRef<'a>,
repo: &'a Repository,
head: Commit,
revwalk: git2::Revwalk<'a>,
filter_by: Option<FilterBy>,
@@ -37,11 +37,11 @@ enum FilterBy {
impl<'a> History<'a> {
/// Creates a new history starting from `head`, in `repo`.
pub fn new<C: ToCommit>(repo: RepositoryRef<'a>, head: C) -> Result<Self, Error> {
pub fn new<C: ToCommit>(repo: &'a Repository, head: C) -> Result<Self, Error> {
let head = head
.to_commit(&repo)
.to_commit(repo)
.map_err(|err| Error::ToCommit(err.into()))?;
let mut revwalk = repo.repo_ref.revwalk()?;
let mut revwalk = repo.git2_repo().revwalk()?;
revwalk.push(head.id.into())?;
let history = Self {
repo,
@@ -76,7 +76,7 @@ impl<'a> Iterator for History<'a> {
let found = oid
.map_err(Error::Git)
.and_then(|oid| {
let git2_commit = self.repo.repo_ref.find_commit(oid)?;
let git2_commit = self.repo.git2_repo().find_commit(oid)?;
// Handles the optional filter_by.
if let Some(FilterBy::File { path }) = &self.filter_by {
+31 -71
View File
@@ -99,36 +99,14 @@ pub enum Error {
/// Wrapper around the `git2`'s `git2::Repository` type.
/// This is to to limit the functionality that we can do
/// on the underlying object.
pub struct Repository(pub(super) git2::Repository);
/// A reference-only `Repository`. This means that we cannot mutate the
/// underlying `Repository`. Not being able to mutate the `Repository` means
/// that the functions defined for `RepositoryRef` should be thread-safe.
///
/// # Construction
///
/// Use the `From<&'a git2::Repository>` implementation to construct a
/// `RepositoryRef`.
#[derive(Clone, Copy)]
pub struct RepositoryRef<'a> {
pub(crate) repo_ref: &'a git2::Repository,
pub struct Repository {
inner: git2::Repository,
}
// RepositoryRef should be safe to transfer across thread boundaries since it
// only holds a reference to git2::Repository. git2::Repository is also Send
// (see: https://docs.rs/git2/0.13.5/src/git2/repo.rs.html#46)
unsafe impl<'a> Send for RepositoryRef<'a> {}
impl<'a> From<&'a git2::Repository> for RepositoryRef<'a> {
fn from(repo_ref: &'a git2::Repository) -> Self {
RepositoryRef { repo_ref }
}
}
impl<'a> RepositoryRef<'a> {
impl Repository {
/// What is the current namespace we're browsing in.
pub fn which_namespace(&self) -> Result<Option<Namespace>, Error> {
self.repo_ref
self.inner
.namespace_bytes()
.map(|ns| Namespace::try_from(ns).map_err(Error::from))
.transpose()
@@ -139,7 +117,7 @@ impl<'a> RepositoryRef<'a> {
let mut branches = Branches::default();
for glob in pattern.globs() {
let namespaced = self.namespaced_pattern(glob)?;
let references = self.repo_ref.references_glob(&namespaced)?;
let references = self.inner.references_glob(&namespaced)?;
branches.push(references);
}
Ok(branches)
@@ -150,7 +128,7 @@ impl<'a> RepositoryRef<'a> {
let mut tags = Tags::default();
for glob in pattern.globs() {
let namespaced = self.namespaced_pattern(glob)?;
let references = self.repo_ref.references_glob(&namespaced)?;
let references = self.inner.references_glob(&namespaced)?;
tags.push(references);
}
Ok(tags)
@@ -161,7 +139,7 @@ impl<'a> RepositoryRef<'a> {
let mut set = BTreeSet::new();
for glob in pattern.globs() {
let new_set = self
.repo_ref
.inner
.references_glob(glob)?
.map(|reference| {
reference
@@ -202,7 +180,7 @@ impl<'a> RepositoryRef<'a> {
/// Parse an [`Oid`] from the given string.
pub fn oid(&self, oid: &str) -> Result<Oid, Error> {
Ok(self.repo_ref.revparse_single(oid)?.id().into())
Ok(self.inner.revparse_single(oid)?.id().into())
}
/// Returns a top level `Directory` without nested sub-directories.
@@ -213,7 +191,7 @@ impl<'a> RepositoryRef<'a> {
let commit = commit
.to_commit(self)
.map_err(|err| Error::ToCommit(err.into()))?;
let git2_commit = self.repo_ref.find_commit((commit.id).into())?;
let git2_commit = self.inner.find_commit((commit.id).into())?;
let tree = git2_commit.as_object().peel_to_tree()?;
Ok(Directory {
name: Label::root(),
@@ -226,7 +204,7 @@ impl<'a> RepositoryRef<'a> {
&self,
d: &Directory,
) -> Result<BTreeMap<Label, DirectoryEntry>, Error> {
let git2_tree = self.repo_ref.find_tree(d.oid.into())?;
let git2_tree = self.inner.find_tree(d.oid.into())?;
let map = self.tree_first_level(git2_tree)?;
Ok(map)
}
@@ -312,13 +290,13 @@ impl<'a> RepositoryRef<'a> {
/// Obtain the file content
pub(crate) fn file_content(&self, object_id: Oid) -> Result<FileContent, Error> {
let blob = self.repo_ref.find_blob(object_id.into())?;
let blob = self.inner.find_blob(object_id.into())?;
Ok(FileContent::new(blob))
}
/// Return the size of a file
pub(crate) fn file_size(&self, oid: Oid) -> Result<usize, Error> {
let blob = self.repo_ref.find_blob(oid.into())?;
let blob = self.inner.find_blob(oid.into())?;
Ok(blob.size())
}
@@ -332,7 +310,7 @@ impl<'a> RepositoryRef<'a> {
let commit = self.get_git2_commit(id)?;
let tree = commit.tree()?;
let entry = tree.get_path(PathBuf::from(&path).as_ref())?;
let object = entry.to_object(self.repo_ref)?;
let object = entry.to_object(self.git2_repo())?;
let blob = object.into_blob().map_err(|_| Error::PathNotFound(path))?;
Ok(FileContent::new(blob))
}
@@ -349,18 +327,18 @@ impl<'a> RepositoryRef<'a> {
/// Returns the Oid of the current HEAD
pub fn head_oid(&self) -> Result<Oid, Error> {
let head = self.repo_ref.head()?;
let head = self.inner.head()?;
let head_commit = head.peel_to_commit()?;
Ok(head_commit.id().into())
}
/// Switch to a `namespace`
pub fn switch_namespace(&self, namespace: &str) -> Result<(), Error> {
Ok(self.repo_ref.set_namespace(namespace)?)
Ok(self.inner.set_namespace(namespace)?)
}
/// Returns a full reference name with namespace(s) included.
pub(crate) fn namespaced_refname(
pub(crate) fn namespaced_refname<'a>(
&'a self,
refname: &Qualified<'a>,
) -> Result<Qualified<'a>, Error> {
@@ -372,7 +350,7 @@ impl<'a> RepositoryRef<'a> {
}
/// Returns a full reference name with namespace(s) included.
pub(crate) fn namespaced_pattern(
pub(crate) fn namespaced_pattern<'a>(
&'a self,
refname: &QualifiedPattern<'a>,
) -> Result<QualifiedPattern<'a>, Error> {
@@ -385,7 +363,7 @@ impl<'a> RepositoryRef<'a> {
/// Get a particular `git2::Commit` of `oid`.
pub(crate) fn get_git2_commit(&self, oid: Oid) -> Result<git2::Commit, Error> {
self.repo_ref.find_commit(oid.into()).map_err(Error::Git)
self.inner.find_commit(oid.into()).map_err(Error::Git)
}
/// Extract the signature from a commit
@@ -404,7 +382,7 @@ impl<'a> RepositoryRef<'a> {
// git_commit_extract_signature at
// https://libgit2.org/libgit2/#HEAD/group/commit/git_commit_extract_signature
// the return value for a commit without a signature will be GIT_ENOTFOUND
match self.repo_ref.extract_signature(commit_oid, field) {
match self.inner.extract_signature(commit_oid, field) {
Err(error) => {
if error.code() == git2::ErrorCode::NotFound {
Ok(None)
@@ -422,7 +400,7 @@ impl<'a> RepositoryRef<'a> {
for branch in self.branches(&glob)? {
let branch = branch?;
let namespaced = self.namespaced_refname(&branch.refname())?;
let reference = self.repo_ref.find_reference(namespaced.as_str())?;
let reference = self.inner.find_reference(namespaced.as_str())?;
if self.reachable_from(&reference, oid)? {
contained_branches.push(branch);
}
@@ -434,7 +412,7 @@ impl<'a> RepositoryRef<'a> {
fn reachable_from(&self, reference: &git2::Reference, oid: &Oid) -> Result<bool, Error> {
let git2_oid = (*oid).into();
let other = reference.peel_to_commit()?.id();
let is_descendant = self.repo_ref.graph_descendant_of(other, git2_oid)?;
let is_descendant = self.inner.graph_descendant_of(other, git2_oid)?;
Ok(other == git2_oid || is_descendant)
}
@@ -471,7 +449,7 @@ impl<'a> RepositoryRef<'a> {
}
let mut diff =
self.repo_ref
self.inner
.diff_tree_to_tree(old_tree.as_ref(), Some(&new_tree), Some(&mut opts))?;
// Detect renames by default.
@@ -484,57 +462,39 @@ impl<'a> RepositoryRef<'a> {
/// Returns the history with the `head` commit.
pub fn history<C: ToCommit>(&self, head: C) -> Result<History, Error> {
History::new(*self, head)
History::new(self, head)
}
pub(super) fn object_id<R: Revision>(&self, r: &R) -> Result<Oid, Error> {
r.object_id(self).map_err(|err| Error::Revision(err.into()))
}
}
impl<'a> std::fmt::Debug for RepositoryRef<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, ".git")
}
}
impl Repository {
/// Open a git repository given its exact URI.
///
/// # Errors
///
/// * [`Error::Git`]
pub fn open(repo_uri: impl AsRef<std::path::Path>) -> Result<Self, Error> {
git2::Repository::open(repo_uri)
.map(Repository)
.map_err(Error::from)
let repo = git2::Repository::open(repo_uri)?;
Ok(Self { inner: repo })
}
/// Attempt to open a git repository at or above `repo_uri` in the file
/// system.
pub fn discover(repo_uri: impl AsRef<std::path::Path>) -> Result<Self, Error> {
git2::Repository::discover(repo_uri)
.map(Repository)
.map_err(Error::from)
let repo = git2::Repository::discover(repo_uri)?;
Ok(Self { inner: repo })
}
/// Since our operations are read-only when it comes to surfing a repository
/// we have a separate struct called [`RepositoryRef`]. This turns an owned
/// [`Repository`] into a [`RepositoryRef`].
pub fn as_ref(&'_ self) -> RepositoryRef<'_> {
RepositoryRef { repo_ref: &self.0 }
}
}
impl<'a> From<&'a Repository> for RepositoryRef<'a> {
fn from(repo: &'a Repository) -> Self {
repo.as_ref()
/// Get a reference to the underlying git2 repo.
pub(crate) fn git2_repo(&self) -> &git2::Repository {
&self.inner
}
}
impl From<git2::Repository> for Repository {
fn from(repo: git2::Repository) -> Self {
Repository(repo)
Repository { inner: repo }
}
}
+12 -18
View File
@@ -1,5 +1,5 @@
// This file is part of radicle-surf
// <https://github.com/radicle-dev/radicle-surf>
// This file is part of radicle-git
// <https://github.com/radicle-dev/radicle-git>
//
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
//
@@ -17,17 +17,18 @@
//! Welcome to `radicle-surf`!
//!
//! `radicle-surf` is a system to describe a file-system in a VCS world.
//! We have the concept of files and directories, but these objects can change
//! over time while people iterate on them. Thus, it is a file-system within
//! history and we, the user, are viewing the file-system at a particular
//! snapshot. Alongside this, we will wish to take two snapshots and view their
//! differences.
//! `radicle-surf` is a library to describe a Git repository as a file system.
//! It aims to provide an easy-to-use API to browse a repository via the concept
//! of files and directories for any given revision. It also allows the user to
//! diff any two different revisions.
//!
//! One of the use cases would be to create a web GUI for interacting with a Git
//! repository (thinking GitHub, GitLab or similar systems).
//!
//! Let's start surfing (and apologies for the `expect`s):
//!
//! ```
//! use radicle_surf::vcs::git;
//! use radicle_surf::git;
//! use radicle_surf::file_system::{Label, Path, SystemType};
//! use radicle_surf::file_system::unsound;
//! use pretty_assertions::assert_eq;
@@ -36,17 +37,10 @@
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! // We're going to point to this repo.
//! let repo = git::Repository::new("./data/git-platinum")?;
//!
//! // Here we initialise a new Broswer for a the git repo.
//! let mut browser = git::Browser::new(&repo, git::Branch::local("master"))?;
//!
//! // Set the history to a particular commit
//! let commit = git::Oid::from_str("80ded66281a4de2889cc07293a8f10947c6d57fe")?;
//! browser.commit(commit)?;
//! let repo = git::Repository::open("./data/git-platinum")?;
//!
//! // Get the snapshot of the directory for our current HEAD of history.
//! let directory = browser.get_directory()?;
//! let directory = repo.root_dir("80ded66281a4de2889cc07293a8f10947c6d57fe")?;
//!
//! // Let's get a Path to the memory.rs file
//! let memory = unsound::path::new("src/memory.rs");
+3 -3
View File
@@ -29,7 +29,7 @@ use serde::{
use crate::{
commit,
file_system,
git::RepositoryRef,
git::Repository,
object::{Error, Info, ObjectType},
revision::Revision,
};
@@ -115,7 +115,7 @@ impl Serialize for BlobContent {
/// Will return [`Error`] if the project doesn't exist or a surf interaction
/// fails.
pub fn blob(
repo: &RepositoryRef,
repo: &Repository,
maybe_revision: Option<Revision>,
path: &str,
) -> Result<Blob, Error> {
@@ -123,7 +123,7 @@ pub fn blob(
}
fn make_blob<C>(
repo: &RepositoryRef,
repo: &Repository,
maybe_revision: Option<Revision>,
path: &str,
content: C,
+2 -2
View File
@@ -30,7 +30,7 @@ use serde::{
use crate::{
commit,
file_system::{self, DirectoryEntry},
git::RepositoryRef,
git::Repository,
object::{Error, Info, ObjectType},
revision::Revision,
};
@@ -87,7 +87,7 @@ impl Serialize for TreeEntry {
///
/// Will return [`Error`] if any of the surf interactions fail.
pub fn tree(
repo: &RepositoryRef,
repo: &Repository,
maybe_revision: Option<Revision>,
maybe_prefix: Option<String>,
) -> Result<Tree, Error> {
+5 -5
View File
@@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
use radicle_git_ext::Oid;
use crate::git::{self, Error, Glob, RepositoryRef};
use crate::git::{self, Error, Glob, Repository};
/// Types of a peer.
pub enum Category<P, U> {
@@ -79,7 +79,7 @@ pub enum Revision {
impl git::Revision for &Revision {
type Error = git2::Error;
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Self::Error> {
fn object_id(&self, repo: &Repository) -> Result<Oid, Self::Error> {
match self {
Revision::Tag { name } => match name.qualified() {
None => Qualified::from(lit::refs_tags(name)).object_id(repo),
@@ -118,7 +118,7 @@ pub struct Revisions<P, U> {
/// # Errors
///
/// * If we cannot get the branches from the `Browser`
pub fn remote<P, U>(repo: &RepositoryRef, peer_id: P, user: U) -> Result<Revisions<P, U>, Error>
pub fn remote<P, U>(repo: &Repository, peer_id: P, user: U) -> Result<Revisions<P, U>, Error>
where
P: Clone + ToString,
{
@@ -144,7 +144,7 @@ where
/// # Errors
///
/// * If we cannot get the branches from the `Browser`
pub fn local<P, U>(repo: &RepositoryRef, peer_id: P, user: U) -> Result<Revisions<P, U>, Error>
pub fn local<P, U>(repo: &Repository, peer_id: P, user: U) -> Result<Revisions<P, U>, Error>
where
P: Clone + ToString,
{
@@ -175,7 +175,7 @@ where
/// # Errors
///
/// * If we cannot get the branches from the `Browser`
pub fn revisions<P, U>(repo: &RepositoryRef, peer: Category<P, U>) -> Result<Revisions<P, U>, Error>
pub fn revisions<P, U>(repo: &Repository, peer: Category<P, U>) -> Result<Revisions<P, U>, Error>
where
P: Clone + ToString,
{
-2
View File
@@ -42,7 +42,6 @@ mod directory {
#[test]
fn directory_get_path() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let root = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
// get_path for a file.
@@ -79,7 +78,6 @@ mod directory {
#[test]
fn directory_size() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let root = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
/*
+7 -38
View File
@@ -20,9 +20,7 @@ fn test_submodule_failure() {
use git_ref_format::refname;
let repo = Repository::discover(".").unwrap();
repo.as_ref()
.root_dir(&Branch::local(refname!("main")))
.unwrap();
repo.root_dir(&Branch::local(refname!("main"))).unwrap();
}
#[cfg(test)]
@@ -34,7 +32,6 @@ mod namespace {
#[test]
fn switch_to_banana() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let history_master = repo.history(&Branch::local(refname!("master")))?;
repo.switch_namespace("golden")?;
let history_banana = repo.history(&Branch::local(refname!("banana")))?;
@@ -47,7 +44,6 @@ mod namespace {
#[test]
fn me_namespace() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let history = repo.history(&Branch::local(refname!("master")))?;
assert_eq!(repo.which_namespace().unwrap(), None);
@@ -83,7 +79,6 @@ mod namespace {
#[test]
fn golden_namespace() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let history = repo.history(&Branch::local(refname!("master")))?;
assert_eq!(repo.which_namespace().unwrap(), None);
@@ -127,7 +122,6 @@ mod namespace {
#[test]
fn silver_namespace() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let history = repo.history(&Branch::local(refname!("master")))?;
assert_eq!(repo.which_namespace().unwrap(), None);
@@ -173,7 +167,6 @@ mod rev {
#[test]
fn _master() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let mut history =
repo.history(&Branch::remote(component!("origin"), refname!("master")))?;
@@ -199,7 +192,6 @@ mod rev {
#[test]
fn commit() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
let mut history = repo.history(rev)?;
@@ -212,7 +204,6 @@ mod rev {
#[test]
fn commit_parents() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
let history = repo.history(rev)?;
let commit = history.head();
@@ -228,7 +219,6 @@ mod rev {
#[test]
fn commit_short() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev = repo.oid("3873745c8")?;
let mut history = repo.history(rev)?;
@@ -241,7 +231,6 @@ mod rev {
#[test]
fn tag() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev = refname!("refs/tags/v0.2.0");
let history = repo.history(&rev)?;
@@ -268,7 +257,6 @@ mod last_commit {
// memory.rs is commited later so it should not exist here.
let memory_last_commit_oid = repo
.as_ref()
.last_commit(
Path::with_root(&[unsound::label::new("src"), unsound::label::new("memory.rs")]),
oid,
@@ -280,7 +268,6 @@ mod last_commit {
// README.md exists in this commit.
let readme_last_commit = repo
.as_ref()
.last_commit(Path::with_root(&[unsound::label::new("README.md")]), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -299,7 +286,6 @@ mod last_commit {
let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
let folder_svelte = repo
.as_ref()
.last_commit(unsound::path::new("~/examples/Folder.svelte"), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -318,7 +304,6 @@ mod last_commit {
let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
let nested_directory_tree_commit_id = repo
.as_ref()
.last_commit(
unsound::path::new("~/this/is/a/really/deeply/nested/directory/tree"),
oid,
@@ -342,14 +327,12 @@ mod last_commit {
let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
let backslash_commit_id = repo
.as_ref()
.last_commit(unsound::path::new("~/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
.as_ref()
.last_commit(unsound::path::new("~/special/👹👹👹"), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -362,13 +345,11 @@ mod last_commit {
.expect("Could not retrieve ./data/git-platinum as git repository");
let rev = Branch::local(refname!("master"));
let root_last_commit_id = repo
.as_ref()
.last_commit(Path::root(), &rev)
.expect("Failed to get last commit")
.map(|commit| commit.id);
let expected_oid = repo
.as_ref()
.history(&Branch::local(refname!("master")))
.unwrap()
.head()
@@ -380,7 +361,6 @@ mod last_commit {
fn binary_file() {
let repo = Repository::open(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let repo = repo.as_ref();
let history = repo.history(&Branch::local(refname!("dev"))).unwrap();
let file_commit = history.by_path(unsound::path::new("~/bin/cat")).next();
assert!(file_commit.is_some());
@@ -398,7 +378,6 @@ mod diff {
#[test]
fn test_initial_diff() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?;
let commit = repo.commit(oid).unwrap();
assert!(commit.parents.is_empty());
@@ -433,7 +412,6 @@ mod diff {
#[test]
fn test_diff_of_rev() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let diff = repo.diff_from_parent("80bacafba303bf0cdf6142921f430ff265f25095")?;
assert_eq!(diff.created.len(), 0);
assert_eq!(diff.deleted.len(), 0);
@@ -445,7 +423,6 @@ mod diff {
#[test]
fn test_diff() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let oid = "80bacafba303bf0cdf6142921f430ff265f25095";
let commit = repo.commit(oid).unwrap();
let parent_oid = commit.parents.get(0).unwrap();
@@ -479,7 +456,6 @@ mod diff {
#[test]
fn test_branch_diff() -> Result<(), Error> {
let repo = Repository::open(GIT_PLATINUM)?;
let repo = repo.as_ref();
let diff = repo.diff(
&Branch::local(refname!("master")),
&Branch::local(refname!("dev")),
@@ -600,7 +576,6 @@ mod threading {
let shared_repo = Mutex::new(Repository::open(GIT_PLATINUM)?);
let locked_repo: MutexGuard<Repository> = shared_repo.lock().unwrap();
let mut branches = locked_repo
.as_ref()
.branches(&Glob::heads("*")?.and_remotes("*")?)?
.collect::<Result<Vec<_>, _>>()?;
branches.sort();
@@ -700,7 +675,6 @@ mod reference {
#[test]
fn test_branches() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let branches = repo.branches(&Glob::heads("*").unwrap()).unwrap();
for b in branches {
println!("{}", b.unwrap().refname());
@@ -716,21 +690,19 @@ mod reference {
#[test]
fn test_tag_snapshot() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo_ref = repo.as_ref();
let tags = repo_ref
let tags = repo
.tags(&Glob::tags("*").unwrap())
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(tags.len(), 6);
let root_dir = repo_ref.root_dir(&tags[0]).unwrap();
assert_eq!(root_dir.contents(&repo_ref).unwrap().iter().count(), 1);
let root_dir = repo.root_dir(&tags[0]).unwrap();
assert_eq!(root_dir.contents(&repo).unwrap().iter().count(), 1);
}
#[test]
fn test_namespaces() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let namespaces = repo.namespaces(&Glob::namespaces("*").unwrap()).unwrap();
assert_eq!(namespaces.count(), 3);
let namespaces = repo
@@ -748,12 +720,11 @@ mod code_browsing {
use super::*;
use git_ref_format::refname;
use radicle_surf::{file_system::Directory, git::RepositoryRef};
use radicle_surf::{file_system::Directory, git::Repository};
#[test]
fn iterate_root_dir_recursive() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
let count = println_dir(&root_dir, &repo, 0);
@@ -764,7 +735,7 @@ 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: &RepositoryRef, indent_level: usize) -> i32 {
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());
@@ -780,14 +751,13 @@ mod code_browsing {
#[test]
fn browse_repo_lazily() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap();
let count = root_dir.contents(&repo).unwrap().iter().count();
assert_eq!(count, 8);
let count = traverse(&root_dir, &repo);
assert_eq!(count, 36);
fn traverse(dir: &Directory, repo: &RepositoryRef) -> i32 {
fn traverse(dir: &Directory, repo: &Repository) -> i32 {
let mut count = 0;
for item in dir.contents(repo).unwrap().iter() {
count += 1;
@@ -802,7 +772,6 @@ mod code_browsing {
#[test]
fn test_file_history() {
let repo = Repository::open(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let history = repo.history(&Branch::local(refname!("dev"))).unwrap();
let path = unsound::path::new("README.md");
let mut file_history = history.by_path(path);