From c44348fc3c832c64ec84efc2f75d055ac52a576c Mon Sep 17 00:00:00 2001 From: Han Xu Date: Mon, 21 Nov 2022 14:07:29 -0800 Subject: [PATCH] Remove RepositoryRef type and simply use &Repository. Signed-off-by: Han Xu --- radicle-surf/README.md | 64 ++------------ radicle-surf/benches/last_commit.rs | 2 +- radicle-surf/examples/browsing.rs | 5 +- radicle-surf/examples/diff.rs | 4 +- radicle-surf/src/commit.rs | 8 +- radicle-surf/src/diff.rs | 4 +- radicle-surf/src/file_system/directory.rs | 16 ++-- radicle-surf/src/git.rs | 38 ++++---- radicle-surf/src/git/history.rs | 12 +-- radicle-surf/src/git/repo.rs | 102 +++++++--------------- radicle-surf/src/lib.rs | 30 +++---- radicle-surf/src/object/blob.rs | 6 +- radicle-surf/src/object/tree.rs | 4 +- radicle-surf/src/revision.rs | 10 +-- radicle-surf/t/src/file_system.rs | 2 - radicle-surf/t/src/git.rs | 45 ++-------- 16 files changed, 113 insertions(+), 239 deletions(-) diff --git a/radicle-surf/README.md b/radicle-surf/README.md index 76aa7d8..aceaa98 100644 --- a/radicle-surf/README.md +++ b/radicle-surf/README.md @@ -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")), -]); -``` diff --git a/radicle-surf/benches/last_commit.rs b/radicle-surf/benches/last_commit.rs index 2b35100..76b69ac 100644 --- a/radicle-surf/benches/last_commit.rs +++ b/radicle-surf/benches/last_commit.rs @@ -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)) }); } } diff --git a/radicle-surf/examples/browsing.rs b/radicle-surf/examples/browsing.rs index d1e72a0..f2a00d0 100644 --- a/radicle-surf/examples/browsing.rs +++ b/radicle-surf/examples/browsing.rs @@ -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() { diff --git a/radicle-surf/examples/diff.rs b/radicle-surf/examples/diff.rs index 9455880..511310b 100644 --- a/radicle-surf/examples/diff.rs +++ b/radicle-surf/examples/diff.rs @@ -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); } diff --git a/radicle-surf/src/commit.rs b/radicle-surf/src/commit.rs index e54c1a9..7318e00 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/commit.rs @@ -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(repo: &RepositoryRef, rev: R) -> Result { +pub fn commit(repo: &Repository, rev: R) -> Result { let commit = repo.commit(rev)?; let sha1 = commit.id; let header = Header::from(&commit); @@ -216,7 +216,7 @@ pub fn commit(repo: &RepositoryRef, rev: R) -> Result Result { +pub fn header(repo: &Repository, sha1: Oid) -> Result { let commit = repo.commit(sha1)?; Ok(Header::from(&commit)) } @@ -227,7 +227,7 @@ pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result { /// /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. -pub fn commits(repo: &RepositoryRef, maybe_revision: Option) -> Result { +pub fn commits(repo: &Repository, maybe_revision: Option) -> Result { let rev = match maybe_revision { Some(revision) => revision, None => Revision::Sha { diff --git a/radicle-surf/src/diff.rs b/radicle-surf/src/diff.rs index 31fe1df..6caaaf3 100644 --- a/radicle-surf/src/diff.rs +++ b/radicle-surf/src/diff.rs @@ -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 { + pub fn diff(left: Directory, right: Directory, repo: Repository) -> Result { // 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 diff --git a/radicle-surf/src/file_system/directory.rs b/radicle-surf/src/file_system/directory.rs index 7eed5ba..572494d 100644 --- a/radicle-surf/src/file_system/directory.rs +++ b/radicle-surf/src/file_system/directory.rs @@ -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 { + pub fn contents(&self, repo: &Repository) -> Result { 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 { // 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 { + pub fn find_file(&self, path: Path, repo: &Repository) -> Option { 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 { + pub fn find_directory(&self, path: Path, repo: &Repository) -> Option { 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 { + pub fn size(&self, repo: &Repository) -> Result { 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 { + fn object_id(&self, _repo: &Repository) -> Result { Ok(self.oid) } } diff --git a/radicle-surf/src/git.rs b/radicle-surf/src/git.rs index 8bdc32d..3da31f5 100644 --- a/radicle-surf/src/git.rs +++ b/radicle-surf/src/git.rs @@ -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; + fn object_id(&self, repo: &Repository) -> Result; } impl Revision for RefString { type Error = git2::Error; - fn object_id(&self, repo: &RepositoryRef) -> Result { - repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from) + fn object_id(&self, repo: &Repository) -> Result { + 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 { - repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from) + fn object_id(&self, repo: &Repository) -> Result { + 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 { - repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from) + fn object_id(&self, repo: &Repository) -> Result { + 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 { - repo.repo_ref.refname_to_id(self.as_str()).map(Oid::from) + fn object_id(&self, repo: &Repository) -> Result { + 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 { + fn object_id(&self, _repo: &Repository) -> Result { 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 { + fn object_id(&self, _repo: &Repository) -> Result { 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 { + fn object_id(&self, repo: &Repository) -> Result { 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 { + fn object_id(&self, _repo: &Repository) -> Result { 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; + fn to_commit(self, repo: &Repository) -> Result; } impl ToCommit for Commit { type Error = Infallible; - fn to_commit(self, _repo: &RepositoryRef) -> Result { + fn to_commit(self, _repo: &Repository) -> Result { Ok(self) } } @@ -203,9 +203,9 @@ impl ToCommit for Commit { impl ToCommit for R { type Error = Error; - fn to_commit(self, repo: &RepositoryRef) -> Result { + fn to_commit(self, repo: &Repository) -> Result { 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)?) } } diff --git a/radicle-surf/src/git/history.rs b/radicle-surf/src/git/history.rs index d6e26c5..806fc9b 100644 --- a/radicle-surf/src/git/history.rs +++ b/radicle-surf/src/git/history.rs @@ -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, @@ -37,11 +37,11 @@ enum FilterBy { impl<'a> History<'a> { /// Creates a new history starting from `head`, in `repo`. - pub fn new(repo: RepositoryRef<'a>, head: C) -> Result { + pub fn new(repo: &'a Repository, head: C) -> Result { 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 { diff --git a/radicle-surf/src/git/repo.rs b/radicle-surf/src/git/repo.rs index 0d46c46..5216453 100644 --- a/radicle-surf/src/git/repo.rs +++ b/radicle-surf/src/git/repo.rs @@ -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, 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 { - 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, 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 { - 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 { - 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 { - 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, 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, 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 { - 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 { 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(&self, head: C) -> Result { - History::new(*self, head) + History::new(self, head) } pub(super) fn object_id(&self, r: &R) -> Result { 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) -> Result { - 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) -> Result { - 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 for Repository { fn from(repo: git2::Repository) -> Self { - Repository(repo) + Repository { inner: repo } } } diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index 24a6139..63d8340 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -1,5 +1,5 @@ -// This file is part of radicle-surf -// +// This file is part of radicle-git +// // // Copyright (C) 2019-2020 The Radicle Team // @@ -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> { //! // 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"); diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/object/blob.rs index 4d61beb..7469e32 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/object/blob.rs @@ -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, path: &str, ) -> Result { @@ -123,7 +123,7 @@ pub fn blob( } fn make_blob( - repo: &RepositoryRef, + repo: &Repository, maybe_revision: Option, path: &str, content: C, diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index aaac499..f51477e 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -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, maybe_prefix: Option, ) -> Result { diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/revision.rs index 938c9aa..37fb261 100644 --- a/radicle-surf/src/revision.rs +++ b/radicle-surf/src/revision.rs @@ -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 { @@ -79,7 +79,7 @@ pub enum Revision { impl git::Revision for &Revision { type Error = git2::Error; - fn object_id(&self, repo: &RepositoryRef) -> Result { + fn object_id(&self, repo: &Repository) -> Result { 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 { /// # Errors /// /// * If we cannot get the branches from the `Browser` -pub fn remote(repo: &RepositoryRef, peer_id: P, user: U) -> Result, Error> +pub fn remote(repo: &Repository, peer_id: P, user: U) -> Result, Error> where P: Clone + ToString, { @@ -144,7 +144,7 @@ where /// # Errors /// /// * If we cannot get the branches from the `Browser` -pub fn local(repo: &RepositoryRef, peer_id: P, user: U) -> Result, Error> +pub fn local(repo: &Repository, peer_id: P, user: U) -> Result, Error> where P: Clone + ToString, { @@ -175,7 +175,7 @@ where /// # Errors /// /// * If we cannot get the branches from the `Browser` -pub fn revisions(repo: &RepositoryRef, peer: Category) -> Result, Error> +pub fn revisions(repo: &Repository, peer: Category) -> Result, Error> where P: Clone + ToString, { diff --git a/radicle-surf/t/src/file_system.rs b/radicle-surf/t/src/file_system.rs index 2d807a3..ab4b070 100644 --- a/radicle-surf/t/src/file_system.rs +++ b/radicle-surf/t/src/file_system.rs @@ -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(); /* diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index 810ee4a..15132b3 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -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 = shared_repo.lock().unwrap(); let mut branches = locked_repo - .as_ref() .branches(&Glob::heads("*")?.and_remotes("*")?)? .collect::, _>>()?; 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::, _>>() .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);