Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
Remove RepositoryRef type and simply use &Repository.
Han Xu committed 3 years ago
commit c44348fc3c832c64ec84efc2f75d055ac52a576c
parent d3115a2
16 files changed +113 -239
modified 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")),
-
]);
-
```
modified 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))
        });
    }
}
modified 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() {
modified 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);
}

modified 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<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 {
modified 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<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
modified 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<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)
    }
}
modified 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<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)?)
    }
}
modified 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<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 {
modified 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<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 }
    }
}

modified radicle-surf/src/lib.rs
@@ -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");
modified 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<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,
modified 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<Revision>,
    maybe_prefix: Option<String>,
) -> Result<Tree, Error> {
modified 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<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,
{
modified 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();

        /*
modified 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<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);