From cd9132c377cde48a4d2db4049d5f20ecdec98fe8 Mon Sep 17 00:00:00 2001 From: Han Xu Date: Thu, 29 Sep 2022 10:07:16 -0700 Subject: [PATCH] radicle-surf: an updated design - A new design document. - Uses examples/diff.rs to test out some ideas. - Removes the type Browser. - Removes the type Snapshot. --- radicle-surf/benches/last_commit.rs | 7 +- radicle-surf/docs/refactor-design.md | 47 ++ radicle-surf/examples/diff.rs | 73 +- radicle-surf/src/commit.rs | 46 +- radicle-surf/src/object.rs | 2 +- radicle-surf/src/object/blob.rs | 19 +- radicle-surf/src/object/tree.rs | 19 +- radicle-surf/src/revision.rs | 24 +- radicle-surf/src/vcs.rs | 82 +- radicle-surf/src/vcs/git.rs | 1043 ------------------------- radicle-surf/src/vcs/git/namespace.rs | 2 +- radicle-surf/src/vcs/git/repo.rs | 192 ++++- radicle-surf/t/src/git.rs | 245 +++--- 13 files changed, 414 insertions(+), 1387 deletions(-) create mode 100644 radicle-surf/docs/refactor-design.md diff --git a/radicle-surf/benches/last_commit.rs b/radicle-surf/benches/last_commit.rs index 2655a51..fef5f92 100644 --- a/radicle-surf/benches/last_commit.rs +++ b/radicle-surf/benches/last_commit.rs @@ -18,14 +18,13 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use radicle_surf::{ file_system::{unsound, Path}, - vcs::git::{Branch, Browser, Repository}, + vcs::git::{Branch, Repository, Rev}, }; fn last_commit_comparison(c: &mut Criterion) { let repo = Repository::new("./data/git-platinum") .expect("Could not retrieve ./data/git-platinum as git repository"); - let browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); + let rev: Rev = Branch::local("master").into(); let mut group = c.benchmark_group("Last Commit"); for path in [ @@ -36,7 +35,7 @@ fn last_commit_comparison(c: &mut Criterion) { .iter() { group.bench_with_input(BenchmarkId::new("", path), path, |b, path| { - b.iter(|| browser.last_commit(path.clone())) + b.iter(|| repo.as_ref().last_commit(path.clone(), &rev)) }); } } diff --git a/radicle-surf/docs/refactor-design.md b/radicle-surf/docs/refactor-design.md new file mode 100644 index 0000000..b5449c9 --- /dev/null +++ b/radicle-surf/docs/refactor-design.md @@ -0,0 +1,47 @@ +# An updated design for radicle-surf + +Now we have ported the `radicle-surf` crate from its own github repo to be part of the `radicle-git` repo. We are taking this opportunity to refactor its design as well. Intuitively, `radicle-surf` provides an API so that one can use it to create a github-like UI for a git repo: + +- Given a commit (or other types of ref), list the content: i.e. files and directories. +- Generate a diff between two commits. +- List the history of the commits. +- List refs: Branches, Tags. + +The main goals of the changes are: + +- make API simpler whenever possible. +- address open issues in the original `radicle-surf` repo as much as possible. +- not to shy away from being `git` specific. (i.e. not to consider supporting other VCS systems) + +## Make API simpler + +The current API has quite a bit accidental complexity that is not inherent with the requirements, especially when we can be git specific and don't care about other VCS systems. + +### Remove the `Browser` + +The type `Browser` is awkward as of today: + +- it is not a source of truth of any information. For example, `list_branches` method is just a wrapper of `Repository::list_branches`. +- it takes in `History`, but really works at the `Snapshot` level. +- it is mutable but its state does not help much. + +Can we just remove `Browser` and implement its functionalities using other types? + +- For iteratoring the history, use `History`. +- For generating `Directory`, use `Repository` directly given a `Rev`. +- For accessing `Branch`, `Tag` or `Commit`, use `Repository`. + +## Remove the `Snapshot` + +A `Snapshot` should be really just a tree (or `Directory`) of a `Commit` in git. Currently it is a function that returns a `Directory`. Because it is OK to be git specific, we don't need to have this generic function to create a snapshot across different VCS systems. + +The only `snapshot` function defined currently: + +```Rust +let snapshot = Box::new(|repository: &RepositoryRef<'a>, history: &History| { + let tree = Self::get_tree(repository.repo_ref, history.0.first())?; + Ok(directory::Directory::from_hash_map(tree)) + }); +``` + +The above function can be easily implement as a method of `Repository`. diff --git a/radicle-surf/examples/diff.rs b/radicle-surf/examples/diff.rs index 22d67c4..6f721ae 100644 --- a/radicle-surf/examples/diff.rs +++ b/radicle-surf/examples/diff.rs @@ -19,37 +19,20 @@ extern crate radicle_surf; use std::{env::Args, str::FromStr, time::Instant}; -use nonempty::NonEmpty; - use radicle_git_ext::Oid; -use radicle_surf::{ - diff::Diff, - file_system::Directory, - vcs::{git, History}, -}; +use radicle_surf::{diff::Diff, vcs::git}; fn main() { let options = get_options_or_exit(); let repo = init_repository_or_exit(&options.path_to_repo); - let mut browser = - git::Browser::new(&repo, git::Branch::local("master")).expect("failed to create browser:"); - - match options.head_revision { - HeadRevision::Head => { - reset_browser_to_head_or_exit(&mut browser); - }, - HeadRevision::Commit(id) => { - set_browser_history_or_exit(&mut browser, &id); - }, - } - let head_directory = get_directory_or_exit(&browser); - - set_browser_history_or_exit(&mut browser, &options.base_revision); - let base_directory = get_directory_or_exit(&browser); - + let head_oid = match options.head_revision { + HeadRevision::Head => repo.as_ref().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 = Diff::diff(base_directory, head_directory); + let diff = repo.as_ref().diff(base_oid, head_oid).unwrap(); print_diff_summary(&diff, elapsed_nanos); } @@ -73,46 +56,6 @@ fn init_repository_or_exit(path_to_repo: &str) -> git::Repository { } } -fn reset_browser_to_head_or_exit(browser: &mut git::Browser) { - if let Err(e) = browser.head() { - println!("Failed to set browser to HEAD: {:?}", e); - std::process::exit(1); - } -} - -fn set_browser_history_or_exit(browser: &mut git::Browser, commit_id: &str) { - // TODO: Might consider to not require resetting to HEAD when history is not at - // HEAD - reset_browser_to_head_or_exit(browser); - if let Err(e) = set_browser_history(browser, commit_id) { - println!("Failed to set browser history: {:?}", e); - std::process::exit(1); - } -} - -fn set_browser_history(browser: &mut git::Browser, commit_id: &str) -> Result<(), String> { - let oid = match Oid::from_str(commit_id) { - Ok(oid) => oid, - Err(e) => return Err(format!("{}", e)), - }; - let commit = match browser.get().find_in_history(&oid, |artifact| artifact.id) { - Some(commit) => commit, - None => return Err(format!("Git commit not found: {}", commit_id)), - }; - browser.set(History(NonEmpty::new(commit))); - Ok(()) -} - -fn get_directory_or_exit(browser: &git::Browser) -> Directory { - match browser.get_directory() { - Ok(dir) => dir, - Err(e) => { - println!("Failed to get directory: {:?}", e); - std::process::exit(1) - }, - } -} - fn print_diff_summary(diff: &Diff, elapsed_nanos: u128) { diff.created.iter().for_each(|created| { println!("+++ {}", created.path); @@ -131,7 +74,7 @@ fn print_diff_summary(diff: &Diff, elapsed_nanos: u128) { diff.modified.len(), diff.created.len() + diff.deleted.len() + diff.modified.len() ); - println!("diff took {} micros ", elapsed_nanos / 1000); + println!("diff took {} nanos ", elapsed_nanos); } struct Options { diff --git a/radicle-surf/src/commit.rs b/radicle-surf/src/commit.rs index 5d1491f..983c4cb 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/commit.rs @@ -30,7 +30,10 @@ use crate::{ file_system, person::Person, revision::Revision, - vcs::git::{self, BranchName, Browser, Rev}, + vcs::{ + git::{self, BranchName, RepositoryRef, Rev}, + Vcs, + }, }; use radicle_git_ext::Oid; @@ -141,16 +144,12 @@ pub struct Commits { /// /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. -pub fn commit(browser: &mut Browser<'_>, sha1: Oid) -> Result { - browser.commit(sha1)?; - - let history = browser.get(); - let commit = history.first(); - +pub fn commit(repo: &RepositoryRef, sha1: Oid) -> Result { + let commit = repo.get_commit(sha1)?; let diff = if let Some(parent) = commit.parents.first() { - browser.diff(*parent, sha1)? + repo.diff(*parent, sha1)? } else { - browser.initial_diff(sha1)? + repo.initial_diff(sha1)? }; let mut deletions = 0; @@ -194,14 +193,14 @@ pub fn commit(browser: &mut Browser<'_>, sha1: Oid) -> Result { } } - let branches = browser - .revision_branches(sha1)? + let branches = repo + .revision_branches(&sha1)? .into_iter() .map(|b| b.name) .collect(); Ok(Commit { - header: Header::from(commit), + header: Header::from(&commit), stats: Stats { additions, deletions, @@ -217,13 +216,9 @@ pub fn commit(browser: &mut Browser<'_>, sha1: Oid) -> Result { /// /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. -pub fn header(browser: &mut Browser<'_>, sha1: Oid) -> Result { - browser.commit(sha1)?; - - let history = browser.get(); - let commit = history.first(); - - Ok(Header::from(commit)) +pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result { + let commit = repo.get_commit(sha1)?; + Ok(Header::from(&commit)) } /// Retrieves the [`Commit`] history for the given `revision`. @@ -233,7 +228,7 @@ pub fn header(browser: &mut Browser<'_>, sha1: Oid) -> Result { /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. pub fn commits

( - browser: &mut Browser<'_>, + repo: &RepositoryRef, maybe_revision: Option>, ) -> Result where @@ -241,12 +236,13 @@ where { let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?; - if let Some(revision) = maybe_revision { - browser.rev(revision)?; - } + let rev: Rev = match maybe_revision { + Some(revision) => revision, + None => repo.head_oid()?.into(), + }; - let headers = browser.get().iter().map(Header::from).collect(); - let stats = browser.get_stats()?; + let stats = repo.get_stats(&rev)?; + let headers = repo.get_history(rev)?.iter().map(Header::from).collect(); Ok(Commits { headers, stats }) } diff --git a/radicle-surf/src/object.rs b/radicle-surf/src/object.rs index fc87a7d..e2b4e7b 100644 --- a/radicle-surf/src/object.rs +++ b/radicle-surf/src/object.rs @@ -25,7 +25,7 @@ use serde::{ }; pub mod blob; -pub use blob::{blob, Blob, BlobContent}; +pub use blob::{Blob, BlobContent}; pub mod tree; pub use tree::{tree, Tree, TreeEntry}; diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/object/blob.rs index ee6b98b..abe820c 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/object/blob.rs @@ -32,9 +32,10 @@ use serde::{ use crate::{ commit, file_system, + git::RepositoryRef, object::{Error, Info, ObjectType}, revision::Revision, - vcs::git::{Browser, Rev}, + vcs::git::Rev, }; #[cfg(feature = "syntax")] @@ -118,18 +119,18 @@ impl Serialize for BlobContent { /// Will return [`Error`] if the project doesn't exist or a surf interaction /// fails. pub fn blob

( - browser: &mut Browser, + repo: &RepositoryRef, maybe_revision: Option>, path: &str, ) -> Result where P: ToString, { - make_blob(browser, maybe_revision, path, content) + make_blob(repo, maybe_revision, path, content) } fn make_blob( - browser: &mut Browser, + repo: &RepositoryRef, maybe_revision: Option>, path: &str, content: C, @@ -139,11 +140,9 @@ where C: FnOnce(&[u8]) -> BlobContent, { let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?; - if let Some(revision) = maybe_revision { - browser.rev(revision)?; - } + let revision = maybe_revision.unwrap(); - let root = browser.get_directory()?; + let root = repo.snapshot(&revision)?; let p = file_system::Path::from_str(path)?; let file = root @@ -153,8 +152,8 @@ where let mut commit_path = file_system::Path::root(); commit_path.append(p.clone()); - let last_commit = browser - .last_commit(commit_path)? + let last_commit = repo + .last_commit(commit_path, &revision)? .map(|c| commit::Header::from(&c)); let (_rest, last) = p.split_last(); diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index b538a75..608cdf5 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -29,9 +29,13 @@ use serde::{ use crate::{ commit, file_system, + git::RepositoryRef, object::{Error, Info, ObjectType}, revision::Revision, - vcs::git::{Browser, Rev}, + vcs::{ + git::{Branch, Rev}, + Vcs, + }, }; /// Result of a directory listing, carries other trees and blobs. @@ -86,7 +90,7 @@ impl Serialize for TreeEntry { /// /// Will return [`Error`] if any of the surf interactions fail. pub fn tree

( - browser: &mut Browser<'_>, + repo: &RepositoryRef, maybe_revision: Option>, maybe_prefix: Option, ) -> Result @@ -96,9 +100,10 @@ where let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?; let prefix = maybe_prefix.unwrap_or_default(); - if let Some(revision) = maybe_revision { - browser.rev(revision)?; - } + let rev = match maybe_revision { + Some(r) => r, + None => Branch::local("main").into(), + }; let path = if prefix == "/" || prefix.is_empty() { file_system::Path::root() @@ -106,7 +111,7 @@ where file_system::Path::from_str(&prefix)? }; - let root_dir = browser.get_directory()?; + let root_dir = repo.snapshot(&rev)?; let prefix_dir = if path.is_root() { root_dir } else { @@ -155,7 +160,7 @@ where entries.sort_by(|a, b| a.info.object_type.cmp(&b.info.object_type)); let last_commit = if path.is_root() { - Some(commit::Header::from(browser.get().first())) + Some(commit::Header::from(repo.get_history(rev).unwrap().first())) } else { None }; diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/revision.rs index 66dda79..3998b1f 100644 --- a/radicle-surf/src/revision.rs +++ b/radicle-surf/src/revision.rs @@ -27,8 +27,8 @@ use serde::{Deserialize, Serialize}; use radicle_git_ext::Oid; use crate::{ - git::BranchName, - vcs::git::{self, error::Error, Browser, RefScope, Rev, TagName}, + git::{BranchName, RepositoryRef}, + vcs::git::{self, error::Error, RefScope, Rev, TagName}, }; /// Types of a peer. @@ -122,14 +122,14 @@ pub struct Revisions { /// /// * If we cannot get the branches from the `Browser` pub fn remote( - browser: &Browser, + repo: &RepositoryRef, peer_id: P, user: U, ) -> Result>, Error> where P: Clone + ToString, { - let remote_branches = browser.branch_names(Some(peer_id.clone()).into())?; + let remote_branches = repo.branch_names(Some(peer_id.clone()).into())?; Ok( NonEmpty::from_vec(remote_branches).map(|branches| Revisions { peer_id, @@ -150,12 +150,16 @@ where /// # Errors /// /// * If we cannot get the branches from the `Browser` -pub fn local(browser: &Browser, peer_id: P, user: U) -> Result>, Error> +pub fn local( + repo: &RepositoryRef, + peer_id: P, + user: U, +) -> Result>, Error> where P: Clone + ToString, { - let local_branches = browser.branch_names(RefScope::Local)?; - let tags = browser.tag_names()?; + let local_branches = repo.branch_names(RefScope::Local)?; + let tags = repo.tag_names()?; Ok( NonEmpty::from_vec(local_branches).map(|branches| Revisions { peer_id, @@ -178,14 +182,14 @@ where /// /// * If we cannot get the branches from the `Browser` pub fn revisions( - browser: &Browser, + repo: &RepositoryRef, peer: Category, ) -> Result>, Error> where P: Clone + ToString, { match peer { - Category::Local { peer_id, user } => local(browser, peer_id, user), - Category::Remote { peer_id, user } => remote(browser, peer_id, user), + Category::Local { peer_id, user } => local(repo, peer_id, user), + Category::Remote { peer_id, user } => remote(repo, peer_id, user), } } diff --git a/radicle-surf/src/vcs.rs b/radicle-surf/src/vcs.rs index 49ea0da..3b3d6e6 100644 --- a/radicle-surf/src/vcs.rs +++ b/radicle-surf/src/vcs.rs @@ -15,10 +15,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! A model of a general VCS. The components consist of a [`History`], a -//! [`Browser`], and a [`Vcs`] trait. +//! A model of a general VCS. The components consist of a [`History`] +//! and a [`Vcs`] trait. -use crate::file_system::directory::Directory; +// use crate::file_system::directory::Directory; use nonempty::NonEmpty; pub mod git; @@ -135,82 +135,6 @@ impl History { } } -/// A Snapshot is a function that renders a `Directory` given -/// the `Repo` object and a `History` of artifacts. -type Snapshot = Box) -> Result>; - -/// A `Browser` is a way of rendering a `History` into a -/// `Directory` snapshot, and the current `History` it is -/// viewing. -pub struct Browser { - snapshot: Snapshot, - history: History, - repository: Repo, -} - -impl Browser { - /// Get the current `History` the `Browser` is viewing. - pub fn get(&self) -> History - where - A: Clone, - { - self.history.clone() - } - - /// Get the current `History` the `Browser` is viewing as a ref. - pub fn as_history(&self) -> &History { - &self.history - } - - /// Set the `History` the `Browser` should view. - pub fn set(&mut self, history: History) { - self.history = history; - } - - /// Render the `Directory` for this `Browser`. - pub fn get_directory(&self) -> Result { - (self.snapshot)(&self.repository, &self.history) - } - - /// Modify the `History` in this `Browser`. - pub fn modify(&mut self, f: F) - where - F: Fn(&History) -> History, - { - self.history = f(&self.history) - } - - /// Change the `Browser`'s view of `History` by modifying it, or - /// using the default `History` provided if the operation fails. - pub fn view_at(&mut self, default_history: History, f: F) - where - A: Clone, - F: Fn(&History) -> Option>, - { - self.modify(|history| f(history).unwrap_or_else(|| default_history.clone())) - } -} - -impl Vcs for Browser -where - Repo: Vcs, -{ - type HistoryId = Repo::HistoryId; - type ArtefactId = Repo::ArtefactId; - - fn get_history(&self, identifier: Self::HistoryId) -> Result, Error> { - self.repository.get_history(identifier) - } - - fn get_histories(&self) -> Result>, Error> { - self.repository.get_histories() - } - - fn get_identifier(artifact: &A) -> Self::ArtefactId { - Repo::get_identifier(artifact) - } -} - pub(crate) trait GetVcs where Self: Sized, diff --git a/radicle-surf/src/vcs/git.rs b/radicle-surf/src/vcs/git.rs index cde8f81..7932b6c 100644 --- a/radicle-surf/src/vcs/git.rs +++ b/radicle-surf/src/vcs/git.rs @@ -100,19 +100,6 @@ pub use stats::Stats; pub use crate::diff::Diff; -use crate::{ - file_system, - file_system::directory, - vcs, - vcs::{git::error::*, Vcs}, -}; -use nonempty::NonEmpty; -use std::{ - collections::{BTreeSet, HashMap}, - convert::TryFrom, - str, -}; - /// The signature of a commit #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Signature(Vec); @@ -151,1033 +138,3 @@ where }) } } - -/// A [`crate::vcs::Browser`] that uses [`Repository`] as the underlying -/// repository backend, [`git2::Commit`] as the artifact, and [`Error`] for -/// error reporting. -pub type Browser<'a> = vcs::Browser, Commit, Error>; - -impl<'a> Browser<'a> { - /// Create a new browser to interact with. - /// - /// The `revspec` provided will be used to kick off the [`History`] for this - /// `Browser`. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Browser, Branch, Repository}; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let browser = Browser::new(&repo, Branch::local("master"))?; - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn new( - repository: impl Into>, - rev: impl Into, - ) -> Result { - let repository = repository.into(); - let history = repository.get_history(rev.into())?; - Ok(Self::init(repository, history)) - } - - /// Create a new `Browser` that starts in a given `namespace`. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Browser, Repository, Branch, RefScope, BranchName, Namespace}; - /// use std::convert::TryFrom; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let browser = Browser::new_with_namespace( - /// &repo, - /// &Namespace::try_from("golden")?, - /// Branch::local("master") - /// )?; - /// - /// let mut branches = browser.list_branches(RefScope::Local)?; - /// branches.sort(); - /// - /// assert_eq!( - /// branches, - /// vec![ - /// Branch::local("banana"), - /// Branch::local("master"), - /// ] - /// ); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn new_with_namespace( - repository: impl Into>, - namespace: &Namespace, - rev: impl Into, - ) -> Result { - let repository = repository.into(); - // This is a bit weird, the references don't seem to all be present unless we - // make a call to `references` o_O. - let _ = repository.repo_ref.references()?; - repository.switch_namespace(&namespace.to_string())?; - let history = repository.get_history(rev.into())?; - Ok(Self::init(repository, history)) - } - - fn init(repository: RepositoryRef<'a>, history: History) -> Self { - let snapshot = Box::new(|repository: &RepositoryRef<'a>, history: &History| { - let tree = Self::get_tree(repository.repo_ref, history.0.first())?; - Ok(directory::Directory::from_hash_map(tree)) - }); - vcs::Browser { - snapshot, - history, - repository, - } - } - - /// Switch the namespace you are browsing in. This will consume the previous - /// `Browser` and give you back a new `Browser` for that particular - /// namespace. The `revision` provided will kick-off the history for - /// this `Browser`. - pub fn switch_namespace( - self, - namespace: &Namespace, - rev: impl Into, - ) -> Result { - self.repository.switch_namespace(&namespace.to_string())?; - let history = self.get_history(Rev::from(rev))?; - Ok(Browser { - snapshot: self.snapshot, - repository: self.repository, - history, - }) - } - - /// What is the current namespace we're browsing in. - pub fn which_namespace(&self) -> Result, Error> { - self.repository - .repo_ref - .namespace_bytes() - .map(Namespace::try_from) - .transpose() - } - - /// Set the current `Browser` history to the `HEAD` commit of the underlying - /// repository. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Browser, Repository, Branch}; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // ensure we're at HEAD - /// browser.head(); - /// - /// let directory = browser.get_directory(); - /// - /// // We are able to render the directory - /// assert!(directory.is_ok()); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn head(&mut self) -> Result<(), Error> { - let history = self.repository.head()?; - self.set(history); - Ok(()) - } - - /// Set the current `Browser`'s [`History`] to the given [`BranchName`] - /// provided. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// * [`error::Error::NotBranch`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Repository}; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // ensure we're on 'master' - /// browser.branch(Branch::local("master")); - /// - /// let directory = browser.get_directory(); - /// - /// // We are able to render the directory - /// assert!(directory.is_ok()); - /// # - /// # Ok(()) - /// # } - /// ``` - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Repository}; - /// use radicle_surf::file_system::{Label, Path, SystemType}; - /// use radicle_surf::file_system::unsound; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// browser.branch(Branch::remote("dev", "origin"))?; - /// - /// let directory = browser.get_directory()?; - /// let mut directory_contents = directory.list_directory(); - /// directory_contents.sort(); - /// - /// assert!(directory_contents.contains( - /// &SystemType::file(unsound::label::new("here-we-are-on-a-dev-branch.lol")) - /// )); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn branch(&mut self, branch: Branch) -> Result<(), Error> { - let name = BranchName(branch.name()); - self.set(self.repository.reference(branch, |reference| { - let is_branch = ext::is_branch(reference) || reference.is_remote(); - if !is_branch { - Some(Error::NotBranch(name)) - } else { - None - } - })?); - Ok(()) - } - - /// Set the current `Browser`'s [`History`] to the [`TagName`] provided. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// * [`error::Error::NotTag`] - /// - /// # Examples - /// - /// ``` - /// use nonempty::NonEmpty; - /// use radicle_surf::vcs::History; - /// use radicle_surf::vcs::git::{TagName, Branch, Browser, Oid, Repository}; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // Switch to "v0.3.0" - /// browser.tag(TagName::new("v0.3.0"))?; - /// - /// let expected_history = History(NonEmpty::from(( - /// Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97")?, - /// vec![ - /// Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2")?, - /// Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?, - /// Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?, - /// ] - /// ))); - /// - /// let history_ids = browser.get().map(|commit| commit.id); - /// - /// // We are able to render the directory - /// assert_eq!(history_ids, expected_history); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn tag(&mut self, tag_name: TagName) -> Result<(), Error> { - let name = tag_name.clone(); - self.set(self.repository.reference(tag_name, |reference| { - if !ext::is_tag(reference) { - Some(Error::NotTag(name)) - } else { - None - } - })?); - Ok(()) - } - - /// Set the current `Browser`'s [`History`] to the [`Oid`] (SHA digest) - /// provided. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::file_system::{Label, SystemType}; - /// use radicle_surf::file_system::unsound; - /// use radicle_surf::vcs::git::{Branch, Browser, Oid, Repository}; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // Set to the initial commit - /// let commit = Oid::from_str("e24124b7538658220b5aaf3b6ef53758f0a106dc")?; - /// - /// browser.commit(commit)?; - /// - /// let directory = browser.get_directory()?; - /// let mut directory_contents = directory.list_directory(); - /// - /// assert_eq!( - /// directory_contents, - /// vec![ - /// SystemType::file(unsound::label::new("README.md")), - /// SystemType::directory(unsound::label::new("bin")), - /// SystemType::directory(unsound::label::new("src")), - /// SystemType::directory(unsound::label::new("this")), - /// ] - /// ); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn commit(&mut self, oid: Oid) -> Result<(), Error> { - self.set(self.get_history(Rev::Oid(oid))?); - Ok(()) - } - - /// Set a `Browser`'s [`History`] based on a [revspec](https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions). - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// * [`error::Error::RevParseFailure`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::file_system::{Label, SystemType}; - /// use radicle_surf::file_system::unsound; - /// use radicle_surf::vcs::git::{Browser, Branch, Oid, Repository}; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// browser.rev(Branch::remote("dev", "origin"))?; - /// - /// let directory = browser.get_directory()?; - /// let mut directory_contents = directory.list_directory(); - /// directory_contents.sort(); - /// - /// assert!(directory_contents.contains( - /// &SystemType::file(unsound::label::new("here-we-are-on-a-dev-branch.lol")) - /// )); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn rev(&mut self, rev: impl Into) -> Result<(), Error> { - let history = self.get_history(rev.into())?; - self.set(history); - Ok(()) - } - - /// Parse an [`Oid`] from the given string. This is useful if we have a - /// shorthand version of the `Oid`, as opposed to the full one. - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Oid, Repository}; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // Set to the initial commit - /// let commit = Oid::from_str("e24124b7538658220b5aaf3b6ef53758f0a106dc")?; - /// - /// assert_eq!( - /// commit, - /// browser.oid("e24124b")?, - /// ); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn oid(&self, oid: &str) -> Result { - self.repository.oid(oid) - } - - /// Get the [`Diff`] between two commits. - pub fn diff(&self, from: Oid, to: Oid) -> Result { - self.repository.diff(from, to) - } - - /// Get the [`Diff`] of a commit with no parents. - pub fn initial_diff(&self, oid: Oid) -> Result { - self.repository.initial_diff(oid) - } - - /// List the names of the _branches_ that are contained in the underlying - /// [`Repository`]. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, RefScope, BranchName, Browser, Namespace, Repository}; - /// use std::convert::TryFrom; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// let branches = browser.list_branches(RefScope::All)?; - /// - /// // 'master' exists in the list of branches - /// assert!(branches.contains(&Branch::local("master"))); - /// - /// // Filter the branches by `Remote` 'origin'. - /// let mut branches = browser.list_branches(RefScope::Remote { - /// name: Some("origin".to_string()) - /// })?; - /// branches.sort(); - /// - /// assert_eq!(branches, vec![ - /// Branch::remote("HEAD", "origin"), - /// Branch::remote("dev", "origin"), - /// Branch::remote("master", "origin"), - /// ]); - /// - /// // Filter the branches by all `Remote`s. - /// let mut branches = browser.list_branches(RefScope::Remote { - /// name: None - /// })?; - /// branches.sort(); - /// - /// assert_eq!(branches, vec![ - /// Branch::remote("HEAD", "origin"), - /// Branch::remote("dev", "origin"), - /// Branch::remote("master", "origin"), - /// Branch::remote("orange/pineapple", "banana"), - /// Branch::remote("pineapple", "banana"), - /// ]); - /// - /// // We can also switch namespaces and list the branches in that namespace. - /// let golden = browser.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?; - /// - /// let mut branches = golden.list_branches(RefScope::Local)?; - /// branches.sort(); - /// - /// assert_eq!(branches, vec![ - /// Branch::local("banana"), - /// Branch::local("master"), - /// ]); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn list_branches(&self, filter: RefScope) -> Result, Error> { - self.repository.list_branches(filter) - } - - /// Given a project id to a repo returns the list of branches. - /// - /// # Errors - /// - /// Will return [`Error`] if the project doesn't exist or the surf - /// interaction fails. - pub fn branch_names(&self, filter: RefScope) -> Result, Error> { - let mut branches = self - .list_branches(filter)? - .into_iter() - .map(|b| b.name) - .collect::>(); - - branches.sort(); - - Ok(branches) - } - - /// List the names of the _tags_ that are contained in the underlying - /// [`Repository`]. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, RefScope, Browser, Namespace, Oid, Repository, Tag, TagName, Author, Time}; - /// use std::convert::TryFrom; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// let tags = browser.list_tags(RefScope::Local)?; - /// - /// assert_eq!( - /// tags, - /// vec![ - /// Tag::Light { - /// id: Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?, - /// name: TagName::new("v0.1.0"), - /// remote: None, - /// }, - /// Tag::Light { - /// id: Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?, - /// name: TagName::new("v0.2.0"), - /// remote: None, - /// }, - /// Tag::Light { - /// id: Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97")?, - /// name: TagName::new("v0.3.0"), - /// remote: None, - /// }, - /// Tag::Light { - /// id: Oid::from_str("91b69e00cd8e5a07e20942e9e4457d83ce7a3ff1")?, - /// name: TagName::new("v0.4.0"), - /// remote: None, - /// }, - /// Tag::Light { - /// id: Oid::from_str("80ded66281a4de2889cc07293a8f10947c6d57fe")?, - /// name: TagName::new("v0.5.0"), - /// remote: None, - /// }, - /// Tag::Annotated { - /// id: Oid::from_str("4d1f4af2703074d37cb877f4fdbe36322c8e541d")?, - /// target_id: Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?, - /// name: TagName::new("v0.6.0"), - /// remote: None, - /// tagger: Some(Author { - /// name: "Thomas Scholtes".to_string(), - /// email: "thomas@monadic.xyz".to_string(), - /// time: Time::new(1620740737, 120), - /// }), - /// message: Some("An annotated tag message for v0.6.0\n".to_string()) - /// }, - /// ] - /// ); - /// - /// // We can also switch namespaces and list the branches in that namespace. - /// let golden = browser.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?; - /// - /// let branches = golden.list_tags(RefScope::Local)?; - /// assert_eq!(branches, vec![ - /// Tag::Light { - /// id: Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?, - /// name: TagName::new("v0.1.0"), - /// remote: None, - /// }, - /// Tag::Light { - /// id: Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?, - /// name: TagName::new("v0.2.0"), - /// remote: None, - /// }, - /// ]); - /// let golden = golden.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?; - /// - /// let branches = golden.list_tags(RefScope::Remote { name: Some("kickflip".to_string()) })?; - /// assert_eq!(branches, vec![ - /// Tag::Light { - /// id: Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?, - /// name: TagName::new("v0.1.0"), - /// remote: Some("kickflip".to_string()), - /// }, - /// ]); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn list_tags(&self, scope: RefScope) -> Result, Error> { - self.repository.list_tags(scope) - } - - /// Returns a sorted list of [`TagName`] from the browser. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - pub fn tag_names(&self) -> Result, Error> { - let tag_names = self.list_tags(RefScope::Local)?; - let mut tags: Vec = tag_names - .into_iter() - .map(|tag_name| tag_name.name()) - .collect(); - - tags.sort(); - - Ok(tags) - } - - /// List the namespaces within a `Browser`, filtering out ones that do not - /// parse correctly. - /// - /// # Errors - /// - /// * [`Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, BranchType, BranchName, Browser, Namespace, Repository}; - /// use std::convert::TryFrom; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// let mut namespaces = browser.list_namespaces()?; - /// namespaces.sort(); - /// - /// assert_eq!(namespaces, vec![ - /// Namespace::try_from("golden")?, - /// Namespace::try_from("golden/silver")?, - /// Namespace::try_from("me")?, - /// ]); - /// - /// - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn list_namespaces(&self) -> Result, Error> { - self.repository.list_namespaces() - } - - /// Given a [`crate::file_system::Path`] to a file, return the last - /// [`Commit`] that touched that file or directory. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// * [`error::Error::LastCommitException`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Oid, Repository}; - /// use radicle_surf::file_system::{Label, Path, SystemType}; - /// use radicle_surf::file_system::unsound; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // Clamp the Browser to a particular commit - /// let commit = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?; - /// browser.commit(commit)?; - /// - /// let head_commit = browser.get().first().clone(); - /// let expected_commit = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?; - /// - /// let readme_last_commit = browser - /// .last_commit(Path::with_root(&[unsound::label::new("README.md")]))? - /// .map(|commit| commit.id); - /// - /// assert_eq!(readme_last_commit, Some(expected_commit)); - /// - /// let expected_commit = Oid::from_str("e24124b7538658220b5aaf3b6ef53758f0a106dc")?; - /// - /// let memory_last_commit = browser - /// .last_commit(Path::with_root(&[unsound::label::new("src"), unsound::label::new("memory.rs")]))? - /// .map(|commit| commit.id); - /// - /// assert_eq!(memory_last_commit, Some(expected_commit)); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn last_commit(&self, path: file_system::Path) -> Result, Error> { - let file_history = self.repository.file_history( - &path, - repo::CommitHistory::Last, - self.get().first().clone(), - )?; - Ok(file_history.first().cloned()) - } - - /// Get the commit history for a file _or_ directory. - /// - /// # Examples - /// - /// ``` - /// use nonempty::NonEmpty; - /// use radicle_surf::vcs::git::{Branch, Browser, Oid, Repository}; - /// use radicle_surf::file_system::{Label, Path, SystemType}; - /// use radicle_surf::file_system::unsound; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// // Clamp the Browser to a particular commit - /// let commit = Oid::from_str("223aaf87d6ea62eef0014857640fd7c8dd0f80b5")?; - /// browser.commit(commit)?; - /// - /// let root_commits: Vec = browser - /// .file_history(unsound::path::new("~"))? - /// .into_iter() - /// .map(|commit| commit.id) - /// .collect(); - /// - /// assert_eq!(root_commits, - /// vec![ - /// Oid::from_str("223aaf87d6ea62eef0014857640fd7c8dd0f80b5")?, - /// Oid::from_str("80bacafba303bf0cdf6142921f430ff265f25095")?, - /// Oid::from_str("a57846bbc8ced6587bf8329fc4bce970eb7b757e")?, - /// Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?, - /// Oid::from_str("80ded66281a4de2889cc07293a8f10947c6d57fe")?, - /// Oid::from_str("91b69e00cd8e5a07e20942e9e4457d83ce7a3ff1")?, - /// Oid::from_str("1820cb07c1a890016ca5578aa652fd4d4c38967e")?, - /// Oid::from_str("1e0206da8571ca71c51c91154e2fee376e09b4e7")?, - /// Oid::from_str("e24124b7538658220b5aaf3b6ef53758f0a106dc")?, - /// Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97")?, - /// Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2")?, - /// Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?, - /// Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?, - /// ] - /// ); - /// - /// let eval_commits: Vec = browser - /// .file_history(unsound::path::new("~/src/Eval.hs"))? - /// .into_iter() - /// .map(|commit| commit.id) - /// .collect(); - /// - /// assert_eq!(eval_commits, - /// vec![ - /// Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?, - /// Oid::from_str("e24124b7538658220b5aaf3b6ef53758f0a106dc")?, - /// ] - /// ); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn file_history(&self, path: file_system::Path) -> Result, Error> { - self.repository - .file_history(&path, repo::CommitHistory::Full, self.get().first().clone()) - } - - /// Extract the signature for a commit - /// - /// # Arguments - /// - /// * `commit` - The commit to extract the signature for - /// * `field` - the name of the header field containing the signature block; - /// pass `None` to extract the default 'gpgsig' - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Repository, Oid, error}; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// let commit_with_signature_oid = Oid::from_str( - /// "e24124b7538658220b5aaf3b6ef53758f0a106dc" - /// )?; - /// - /// browser.commit(commit_with_signature_oid)?; - /// let history = browser.get(); - /// let commit_with_signature = history.first(); - /// let signature = browser.extract_signature(commit_with_signature, None)?; - /// - /// // We have a signature - /// assert!(signature.is_some()); - /// - /// let commit_without_signature_oid = Oid::from_str( - /// "80bacafba303bf0cdf6142921f430ff265f25095" - /// )?; - /// - /// browser.commit(commit_without_signature_oid)?; - /// let history = browser.get(); - /// let commit_without_signature = history.first(); - /// let signature = browser.extract_signature(commit_without_signature, None)?; - /// - /// // There is no signature - /// assert!(signature.is_none()); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn extract_signature( - &self, - commit: &Commit, - field: Option<&str>, - ) -> Result, Error> { - self.repository.extract_signature(&commit.id, field) - } - - /// List the [`Branch`]es, which contain the provided [`Commit`]. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Browser, Repository, Branch, BranchName, Namespace, Oid}; - /// use std::convert::TryFrom; - /// use std::str::FromStr; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// - /// let branches = browser.revision_branches(Oid::from_str("27acd68c7504755aa11023300890bb85bbd69d45")?)?; - /// assert_eq!( - /// branches, - /// vec![ - /// Branch::local("dev"), - /// Branch::remote("dev", "origin"), - /// ] - /// ); - /// - /// // TODO(finto): I worry that this test will fail as other branches get added - /// let mut branches = browser.revision_branches(Oid::from_str("1820cb07c1a890016ca5578aa652fd4d4c38967e")?)?; - /// branches.sort(); - /// assert_eq!( - /// branches, - /// vec![ - /// Branch::remote("HEAD", "origin"), - /// Branch::local("dev"), - /// Branch::remote("dev", "origin"), - /// Branch::local("master"), - /// Branch::remote("master", "origin"), - /// Branch::remote("orange/pineapple", "banana"), - /// Branch::remote("pineapple", "banana"), - /// ] - /// ); - /// - /// let golden_browser = browser.switch_namespace(&Namespace::try_from("golden")?, - /// Branch::local("master"))?; - /// - /// let branches = golden_browser.revision_branches(Oid::from_str("27acd68c7504755aa11023300890bb85bbd69d45")?)?; - /// assert_eq!( - /// branches, - /// vec![ - /// Branch::local("banana"), - /// Branch::remote("fakie/bigspin", "kickflip"), - /// Branch::remote("heelflip", "kickflip"), - /// ] - /// ); - /// # - /// # Ok(()) - /// # } - /// ``` - pub fn revision_branches(&self, rev: impl Into) -> Result, Error> { - let commit = self.repository.rev_to_commit(&rev.into())?; - self.repository.revision_branches(&commit.id().into()) - } - - /// Get the [`Stats`] of the underlying [`Repository`]. - /// - /// # Errors - /// - /// * [`error::Error::Git`] - /// - /// # Examples - /// - /// ``` - /// use radicle_surf::vcs::git::{Branch, Browser, Repository}; - /// # use std::error::Error; - /// - /// # fn main() -> Result<(), Box> { - /// let repo = Repository::new("./data/git-platinum")?; - /// let mut browser = Browser::new(&repo, Branch::local("master"))?; - /// - /// let stats = browser.get_stats()?; - /// - /// assert_eq!(stats.branches, 2); - /// - /// assert_eq!(stats.commits, 15); - /// - /// assert_eq!(stats.contributors, 4); - /// - /// # Ok(()) - /// # } - /// ``` - pub fn get_stats(&self) -> Result { - let branches = self.list_branches(RefScope::Local)?.len(); - let commits = self.history.len(); - - let contributors = self - .history - .iter() - .cloned() - .map(|commit| (commit.author.name, commit.author.email)) - .collect::>(); - - Ok(Stats { - branches, - commits, - contributors: contributors.len(), - }) - } - - /// Do a pre-order TreeWalk of the given commit. This turns a Tree - /// into a HashMap of Paths and a list of Files. We can then turn that - /// into a Directory. - fn get_tree( - repo: &git2::Repository, - commit: &Commit, - ) -> Result>, Error> - { - let mut file_paths_or_error: Result< - HashMap>, - Error, - > = Ok(HashMap::new()); - - let commit = repo.find_commit(commit.id.into())?; - let tree = commit.as_object().peel_to_tree()?; - - tree.walk( - git2::TreeWalkMode::PreOrder, - |s, entry| match Self::tree_entry_to_file_and_path(repo, s, entry) { - Ok((path, name, file)) => { - match file_paths_or_error.as_mut() { - Ok(files) => Self::update_file_map(path, name, file, files), - - // We don't need to update, we want to keep the error. - Err(_err) => {}, - } - git2::TreeWalkResult::Ok - }, - Err(err) => match err { - // We want to continue if the entry was not a Blob. - TreeWalkError::NotBlob => git2::TreeWalkResult::Ok, - - // We found a ObjectType::Commit (likely a submodule) and - // so we can skip it. - TreeWalkError::Commit => git2::TreeWalkResult::Ok, - - // But we want to keep the error and abort otherwise. - TreeWalkError::Git(err) => { - file_paths_or_error = Err(err); - git2::TreeWalkResult::Abort - }, - }, - }, - )?; - - file_paths_or_error - } - - /// Find the best common ancestor between two commits if it exists. - /// - /// See [`git2::Repository::merge_base`] for details. - pub fn merge_base(&self, one: Oid, two: Oid) -> Result, Error> { - match self.repository.repo_ref.merge_base(one.into(), two.into()) { - Ok(merge_base) => Ok(Some(merge_base.into())), - Err(err) => { - if err.code() == git2::ErrorCode::NotFound { - Ok(None) - } else { - Err(Error::Git(err)) - } - }, - } - } - - fn update_file_map( - path: file_system::Path, - name: file_system::Label, - file: directory::File, - files: &mut HashMap>, - ) { - files - .entry(path) - .and_modify(|entries| entries.push((name.clone(), file.clone()))) - .or_insert_with(|| NonEmpty::new((name, file))); - } - - fn tree_entry_to_file_and_path( - repo: &git2::Repository, - tree_path: &str, - entry: &git2::TreeEntry, - ) -> Result<(file_system::Path, file_system::Label, directory::File), TreeWalkError> { - // Account for the "root" of git being the empty string - let path = if tree_path.is_empty() { - Ok(file_system::Path::root()) - } else { - file_system::Path::try_from(tree_path) - }?; - - // We found a Commit object in the Tree, likely a submodule. - // We will skip this entry. - if let Some(git2::ObjectType::Commit) = entry.kind() { - return Err(TreeWalkError::Commit); - } - - let object = entry.to_object(repo)?; - let blob = object.as_blob().ok_or(TreeWalkError::NotBlob)?; - let name = str::from_utf8(entry.name_bytes())?; - - let name = file_system::Label::try_from(name).map_err(Error::FileSystem)?; - - Ok(( - path, - name, - directory::File { - contents: blob.content().to_owned(), - size: blob.size(), - }, - )) - } -} diff --git a/radicle-surf/src/vcs/git/namespace.rs b/radicle-surf/src/vcs/git/namespace.rs index 6a4c382..9b30e8b 100644 --- a/radicle-surf/src/vcs/git/namespace.rs +++ b/radicle-surf/src/vcs/git/namespace.rs @@ -21,7 +21,7 @@ pub use radicle_git_ext::Oid; use std::{convert::TryFrom, fmt, str}; /// A `Namespace` value allows us to switch the git namespace of -/// [`super::Browser`]. +/// a repo. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Namespace { /// Since namespaces can be nested we have a vector of strings. diff --git a/radicle-surf/src/vcs/git/repo.rs b/radicle-surf/src/vcs/git/repo.rs index 47f08f1..9c4c64b 100644 --- a/radicle-surf/src/vcs/git/repo.rs +++ b/radicle-surf/src/vcs/git/repo.rs @@ -18,29 +18,37 @@ use crate::{ diff::*, file_system, + file_system::directory, vcs, vcs::{ git::{ error::*, reference::{glob::RefGlob, Ref, Rev}, Branch, + BranchName, Commit, Namespace, RefScope, Signature, + Stats, Tag, + TagName, }, Vcs, }, }; use nonempty::NonEmpty; use radicle_git_ext::Oid; -use std::{collections::HashSet, convert::TryFrom, str}; +use std::{ + collections::{BTreeSet, HashMap, HashSet}, + convert::TryFrom, + str, +}; /// This is for flagging to the `file_history` function that it should /// stop at the first (i.e. Last) commit it finds for a file. pub(super) enum CommitHistory { - Full, + _Full, Last, } @@ -171,6 +179,81 @@ impl<'a> RepositoryRef<'a> { Ok(self.repo_ref.revparse_single(oid)?.id().into()) } + /// Gets a snapshot of the repo as a Directory. + pub fn snapshot(&self, rev: &Rev) -> Result { + let commit = self.rev_to_commit(rev)?; + let oid: Oid = commit.id().into(); + let tree = self.get_tree(&oid)?; + Ok(directory::Directory::from_hash_map(tree)) + } + + /// Returns the last commit, if exists, for a `path` in the history of + /// `rev`. + pub fn last_commit(&self, path: file_system::Path, rev: &Rev) -> Result, Error> { + let git2_commit = self.rev_to_commit(rev)?; + let commit = Commit::try_from(git2_commit)?; + let file_history = self.file_history(&path, CommitHistory::Last, commit)?; + Ok(file_history.first().cloned()) + } + + /// Retrieves `Commit` identified by `oid`. + pub fn get_commit(&self, oid: Oid) -> Result { + let git2_commit = self.get_git2_commit(oid)?; + Commit::try_from(git2_commit) + } + + /// Gets stats of `rev`. + pub fn get_stats(&self, rev: &Rev) -> Result { + let branches = self.list_branches(RefScope::Local)?.len(); + let history = self.get_history(rev.clone())?; + let commits = history.len(); + + let contributors = history + .iter() + .cloned() + .map(|commit| (commit.author.name, commit.author.email)) + .collect::>(); + + Ok(Stats { + branches, + commits, + contributors: contributors.len(), + }) + } + + /// Lists branch names with `filter`. + pub fn branch_names(&self, filter: RefScope) -> Result, Error> { + let mut branches = self + .list_branches(filter)? + .into_iter() + .map(|b| b.name) + .collect::>(); + + branches.sort(); + + Ok(branches) + } + + /// Lists tag names in the local RefScope. + pub fn tag_names(&self) -> Result, Error> { + let tag_names = self.list_tags(RefScope::Local)?; + let mut tags: Vec = tag_names + .into_iter() + .map(|tag_name| tag_name.name()) + .collect(); + + tags.sort(); + + Ok(tags) + } + + /// Returns the Oid of the current HEAD + pub fn head_oid(&self) -> Result { + let head = self.repo_ref.head()?; + let head_commit = head.peel_to_commit()?; + Ok(head_commit.id().into()) + } + pub(super) fn rev_to_commit(&self, rev: &Rev) -> Result { match rev { Rev::Oid(oid) => Ok(self.repo_ref.find_commit((*oid).into())?), @@ -178,7 +261,8 @@ impl<'a> RepositoryRef<'a> { } } - pub(super) fn switch_namespace(&self, namespace: &str) -> Result<(), Error> { + /// Switch to a `namespace` + pub fn switch_namespace(&self, namespace: &str) -> Result<(), Error> { Ok(self.repo_ref.set_namespace(namespace)?) } @@ -189,7 +273,7 @@ impl<'a> RepositoryRef<'a> { } /// Build a [`History`] using the `head` reference. - pub(super) fn head(&self) -> Result { + pub fn head_history(&self) -> Result { let head = self.repo_ref.head()?; self.to_history(&head) } @@ -235,7 +319,7 @@ impl<'a> RepositoryRef<'a> { /// `commit_oid` - The object ID of the commit /// `field` - the name of the header field containing the signature block; /// pass `None` to extract the default 'gpgsig' - pub(super) fn extract_signature( + pub fn extract_signature( &self, commit_oid: &Oid, field: Option<&str>, @@ -256,7 +340,8 @@ impl<'a> RepositoryRef<'a> { } } - pub(crate) fn revision_branches(&self, oid: &Oid) -> Result, Error> { + /// Lists branches that are reachable from `oid`. + pub fn revision_branches(&self, oid: &Oid) -> Result, Error> { let local = RefGlob::LocalBranch.references(self)?; let remote = RefGlob::RemoteBranch { remote: None }.references(self)?; let mut references = local.iter().chain(remote.iter()); @@ -307,7 +392,7 @@ impl<'a> RepositoryRef<'a> { commits.push(Commit::try_from(parent)?); match &commit_history { CommitHistory::Last => break, - CommitHistory::Full => {}, + CommitHistory::_Full => {}, } } } @@ -355,6 +440,99 @@ impl<'a> RepositoryRef<'a> { Ok(diff) } + + fn update_file_map( + path: file_system::Path, + name: file_system::Label, + file: directory::File, + files: &mut HashMap>, + ) { + files + .entry(path) + .and_modify(|entries| entries.push((name.clone(), file.clone()))) + .or_insert_with(|| NonEmpty::new((name, file))); + } + + /// Do a pre-order TreeWalk of the given commit. This turns a Tree + /// into a HashMap of Paths and a list of Files. We can then turn that + /// into a Directory. + fn get_tree( + &self, + oid: &Oid, + ) -> Result>, Error> + { + let mut file_paths_or_error: Result< + HashMap>, + Error, + > = Ok(HashMap::new()); + + let commit = self.repo_ref.find_commit((*oid).into())?; + let tree = commit.as_object().peel_to_tree()?; + + tree.walk(git2::TreeWalkMode::PreOrder, |s, entry| { + match self.tree_entry_to_file_and_path(s, entry) { + Ok((path, name, file)) => { + match file_paths_or_error.as_mut() { + Ok(files) => Self::update_file_map(path, name, file, files), + + // We don't need to update, we want to keep the error. + Err(_err) => {}, + } + git2::TreeWalkResult::Ok + }, + Err(err) => match err { + // We want to continue if the entry was not a Blob. + TreeWalkError::NotBlob => git2::TreeWalkResult::Ok, + + // We found a ObjectType::Commit (likely a submodule) and + // so we can skip it. + TreeWalkError::Commit => git2::TreeWalkResult::Ok, + + // But we want to keep the error and abort otherwise. + TreeWalkError::Git(err) => { + file_paths_or_error = Err(err); + git2::TreeWalkResult::Abort + }, + }, + } + })?; + + file_paths_or_error + } + + fn tree_entry_to_file_and_path( + &self, + tree_path: &str, + entry: &git2::TreeEntry, + ) -> Result<(file_system::Path, file_system::Label, directory::File), TreeWalkError> { + // Account for the "root" of git being the empty string + let path = if tree_path.is_empty() { + Ok(file_system::Path::root()) + } else { + file_system::Path::try_from(tree_path) + }?; + + // We found a Commit object in the Tree, likely a submodule. + // We will skip this entry. + if let Some(git2::ObjectType::Commit) = entry.kind() { + return Err(TreeWalkError::Commit); + } + + let object = entry.to_object(self.repo_ref)?; + let blob = object.as_blob().ok_or(TreeWalkError::NotBlob)?; + let name = str::from_utf8(entry.name_bytes())?; + + let name = file_system::Label::try_from(name).map_err(Error::FileSystem)?; + + Ok(( + path, + name, + directory::File { + contents: blob.content().to_owned(), + size: blob.size(), + }, + )) + } } impl<'a> Vcs for RepositoryRef<'a> { diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index 1f09f1b..57b2576 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -8,17 +8,7 @@ use radicle_surf::git::{Author, BranchType, Commit}; use radicle_surf::{ diff::*, file_system::{unsound, Path}, - git::{ - error::Error, - Branch, - BranchName, - Browser, - Namespace, - Oid, - RefScope, - Repository, - TagName, - }, + git::{error::Error, Branch, BranchName, Namespace, Oid, RefScope, Repository, Rev, TagName}, }; const GIT_PLATINUM: &str = "../data/git-platinum"; @@ -28,29 +18,26 @@ const GIT_PLATINUM: &str = "../data/git-platinum"; // An issue with submodules, see: https://github.com/radicle-dev/radicle-surf/issues/54 fn test_submodule_failure() { let repo = Repository::new("../..").unwrap(); - let browser = Browser::new(&repo, Branch::local("main")).unwrap(); - - browser.get_directory().unwrap(); + repo.as_ref() + .snapshot(&Branch::local("main").into()) + .unwrap(); } #[cfg(test)] mod namespace { use super::*; use pretty_assertions::{assert_eq, assert_ne}; + use radicle_surf::vcs::Vcs; #[test] fn switch_to_banana() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new_with_namespace( - &repo, - &Namespace::try_from("golden")?, - Branch::local("master"), - )?; - let history = browser.get(); + let repo = repo.as_ref(); + let history_master = repo.get_history(Branch::local("master").into())?; + repo.switch_namespace("golden")?; + let history_banana = repo.get_history(Branch::local("banana").into())?; - browser.branch(Branch::local("banana"))?; - - assert_ne!(history, browser.get()); + assert_ne!(history_master, history_banana); Ok(()) } @@ -58,28 +45,25 @@ mod namespace { #[test] fn me_namespace() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let browser = Browser::new(&repo, Branch::local("master"))?; - let history = browser.get(); + let repo = repo.as_ref(); + let history = repo.get_history(Branch::local("master").into())?; - assert_eq!(browser.which_namespace(), Ok(None)); + assert_eq!(repo.which_namespace(), Ok(None)); - let browser = browser - .switch_namespace(&Namespace::try_from("me")?, Branch::local("feature/#1194"))?; + repo.switch_namespace("me")?; + assert_eq!(repo.which_namespace(), Ok(Some(Namespace::try_from("me")?))); - assert_eq!( - browser.which_namespace(), - Ok(Some(Namespace::try_from("me")?)) - ); - assert_eq!(history, browser.get()); + let history_feature = repo.get_history(Branch::local("feature/#1194").into())?; + assert_eq!(history, history_feature); let expected_branches: Vec = vec![Branch::local("feature/#1194")]; - let mut branches = browser.list_branches(RefScope::Local)?; + let mut branches = repo.list_branches(RefScope::Local)?; branches.sort(); assert_eq!(expected_branches, branches); let expected_branches: Vec = vec![Branch::remote("feature/#1194", "fein")]; - let mut branches = browser.list_branches(RefScope::Remote { + let mut branches = repo.list_branches(RefScope::Remote { name: Some("fein".to_string()), })?; branches.sort(); @@ -92,22 +76,23 @@ mod namespace { #[test] fn golden_namespace() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let browser = Browser::new(&repo, Branch::local("master"))?; - let history = browser.get(); + let repo = repo.as_ref(); + let history = repo.get_history(Branch::local("master").into())?; - assert_eq!(browser.which_namespace(), Ok(None)); + assert_eq!(repo.which_namespace(), Ok(None)); - let golden_browser = - browser.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?; + repo.switch_namespace("golden")?; assert_eq!( - golden_browser.which_namespace(), + repo.which_namespace(), Ok(Some(Namespace::try_from("golden")?)) ); - assert_eq!(history, golden_browser.get()); + + let golden_history = repo.get_history(Branch::local("master").into())?; + assert_eq!(history, golden_history); let expected_branches: Vec = vec![Branch::local("banana"), Branch::local("master")]; - let mut branches = golden_browser.list_branches(RefScope::Local)?; + let mut branches = repo.list_branches(RefScope::Local)?; branches.sort(); assert_eq!(expected_branches, branches); @@ -117,7 +102,7 @@ mod namespace { Branch::remote("heelflip", "kickflip"), Branch::remote("v0.1.0", "kickflip"), ]; - let mut branches = golden_browser.list_branches(RefScope::Remote { + let mut branches = repo.list_branches(RefScope::Remote { name: Some("kickflip".to_string()), })?; branches.sort(); @@ -130,24 +115,21 @@ mod namespace { #[test] fn silver_namespace() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let browser = Browser::new(&repo, Branch::local("master"))?; - let history = browser.get(); + let repo = repo.as_ref(); + let history = repo.get_history(Branch::local("master").into())?; - assert_eq!(browser.which_namespace(), Ok(None)); - - let silver_browser = browser.switch_namespace( - &Namespace::try_from("golden/silver")?, - Branch::local("master"), - )?; + assert_eq!(repo.which_namespace(), Ok(None)); + repo.switch_namespace("golden/silver")?; assert_eq!( - silver_browser.which_namespace(), + repo.which_namespace(), Ok(Some(Namespace::try_from("golden/silver")?)) ); - assert_ne!(history, silver_browser.get()); + let silver_history = repo.get_history(Branch::local("master").into())?; + assert_ne!(history, silver_history); let expected_branches: Vec = vec![Branch::local("master")]; - let mut branches = silver_browser.list_branches(RefScope::All)?; + let mut branches = repo.list_branches(RefScope::All)?; branches.sort(); assert_eq!(expected_branches, branches); @@ -158,6 +140,8 @@ mod namespace { #[cfg(test)] mod rev { + use radicle_surf::vcs::Vcs; + use super::*; use std::str::FromStr; @@ -175,13 +159,12 @@ mod rev { #[test] fn _master() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new(&repo, Branch::local("master"))?; - browser.rev(Branch::remote("master", "origin"))?; + let repo = repo.as_ref(); + let history = repo.get_history(Branch::remote("master", "origin").into())?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; assert!( - browser - .as_history() + history .find(|commit| if commit.id == commit1 { Some(commit.clone()) } else { @@ -190,13 +173,12 @@ mod rev { .is_some(), "commit_id={}, history =\n{:#?}", commit1, - browser.as_history() + &history ); let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?; assert!( - browser - .as_history() + history .find(|commit| if commit.id == commit2 { Some(commit.clone()) } else { @@ -205,7 +187,7 @@ mod rev { .is_some(), "commit_id={}, history =\n{:#?}", commit2, - browser.as_history() + &history ); Ok(()) @@ -214,12 +196,11 @@ mod rev { #[test] fn commit() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new(&repo, Branch::local("master"))?; - browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?; + let rev: Rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?.into(); + let history = repo.as_ref().get_history(rev)?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; - assert!(browser - .as_history() + assert!(history .find(|commit| if commit.id == commit1 { Some(commit.clone()) } else { @@ -233,9 +214,9 @@ mod rev { #[test] fn commit_parents() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new(&repo, Branch::local("master"))?; - browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?; - let commit = browser.as_history().first(); + let rev: Rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?.into(); + let history = repo.as_ref().get_history(rev)?; + let commit = history.first(); assert_eq!( commit.parents, @@ -248,12 +229,11 @@ mod rev { #[test] fn commit_short() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new(&repo, Branch::local("master"))?; - browser.rev(browser.oid("3873745c8")?)?; + let rev: Rev = repo.as_ref().oid("3873745c8")?.into(); + let history = repo.as_ref().get_history(rev)?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; - assert!(browser - .as_history() + assert!(history .find(|commit| if commit.id == commit1 { Some(commit.clone()) } else { @@ -267,11 +247,11 @@ mod rev { #[test] fn tag() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; - let mut browser = Browser::new(&repo, Branch::local("master"))?; - browser.rev(TagName::new("v0.2.0"))?; + let rev: Rev = TagName::new("v0.2.0").into(); + let history = repo.as_ref().get_history(rev)?; let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?; - assert_eq!(browser.as_history().first().id, commit1); + assert_eq!(history.first().id, commit1); Ok(()) } @@ -279,6 +259,8 @@ mod rev { #[cfg(test)] mod last_commit { + use radicle_surf::vcs::Vcs; + use super::*; use std::str::FromStr; @@ -286,52 +268,46 @@ mod last_commit { fn readme_missing_and_memory() { let repo = Repository::new(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let mut browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); - - // Set the browser history to the initial commit - let commit = + let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3").expect("Failed to parse SHA"); - browser.commit(commit).unwrap(); - - let head_commit = browser.get().0.first().clone(); // memory.rs is commited later so it should not exist here. - let memory_last_commit = browser - .last_commit(Path::with_root(&[ - unsound::label::new("src"), - unsound::label::new("memory.rs"), - ])) + let rev: Rev = oid.into(); + let memory_last_commit_oid = repo + .as_ref() + .last_commit( + Path::with_root(&[unsound::label::new("src"), unsound::label::new("memory.rs")]), + &rev, + ) .expect("Failed to get last commit") .map(|commit| commit.id); - assert_eq!(memory_last_commit, None); + assert_eq!(memory_last_commit_oid, None); // README.md exists in this commit. - let readme_last_commit = browser - .last_commit(Path::with_root(&[unsound::label::new("README.md")])) + let readme_last_commit = repo + .as_ref() + .last_commit(Path::with_root(&[unsound::label::new("README.md")]), &rev) .expect("Failed to get last commit") .map(|commit| commit.id); - assert_eq!(readme_last_commit, Some(head_commit.id)); + assert_eq!(readme_last_commit, Some(oid)); } #[test] fn folder_svelte() { let repo = Repository::new(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let mut browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); - // Check that last commit is the actual last commit even if head commit differs. - let commit = + let oid = Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Could not parse SHA"); - browser.commit(commit).unwrap(); + let rev: Rev = oid.into(); let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap(); - let folder_svelte = browser - .last_commit(unsound::path::new("~/examples/Folder.svelte")) + let folder_svelte = repo + .as_ref() + .last_commit(unsound::path::new("~/examples/Folder.svelte"), &rev) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -342,20 +318,19 @@ mod last_commit { fn nest_directory() { let repo = Repository::new(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let mut browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); - // Check that last commit is the actual last commit even if head commit differs. - let commit = + let oid = Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Failed to parse SHA"); - browser.commit(commit).unwrap(); + let rev: Rev = oid.into(); let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap(); - let nested_directory_tree_commit_id = browser - .last_commit(unsound::path::new( - "~/this/is/a/really/deeply/nested/directory/tree", - )) + let nested_directory_tree_commit_id = repo + .as_ref() + .last_commit( + unsound::path::new("~/this/is/a/really/deeply/nested/directory/tree"), + &rev, + ) .expect("Failed to get last commit") .map(|commit| commit.id); @@ -367,24 +342,24 @@ mod last_commit { fn can_get_last_commit_for_special_filenames() { let repo = Repository::new(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let mut browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); // Check that last commit is the actual last commit even if head commit differs. - let commit = + let oid = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").expect("Failed to parse SHA"); - browser.commit(commit).unwrap(); + let rev: Rev = oid.into(); let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap(); - let backslash_commit_id = browser - .last_commit(unsound::path::new("~/special/faux\\path")) + let backslash_commit_id = repo + .as_ref() + .last_commit(unsound::path::new("~/special/faux\\path"), &rev) .expect("Failed to get last commit") .map(|commit| commit.id); assert_eq!(backslash_commit_id, Some(expected_commit_id)); - let ogre_commit_id = browser - .last_commit(unsound::path::new("~/special/👹👹👹")) + let ogre_commit_id = repo + .as_ref() + .last_commit(unsound::path::new("~/special/👹👹👹"), &rev) .expect("Failed to get last commit") .map(|commit| commit.id); assert_eq!(ogre_commit_id, Some(expected_commit_id)); @@ -394,15 +369,20 @@ mod last_commit { fn root() { let repo = Repository::new(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let browser = - Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser"); - - let root_last_commit_id = browser - .last_commit(Path::root()) + let rev: Rev = Branch::local("master").into(); + let root_last_commit_id = repo + .as_ref() + .last_commit(Path::root(), &rev) .expect("Failed to get last commit") .map(|commit| commit.id); - assert_eq!(root_last_commit_id, Some(browser.get().first().id)); + let expected_oid = repo + .as_ref() + .get_history(Branch::local("master").into()) + .unwrap() + .first() + .id; + assert_eq!(root_last_commit_id, Some(expected_oid)); } } @@ -416,14 +396,13 @@ mod diff { fn test_initial_diff() -> Result<(), Error> { let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?; let repo = Repository::new(GIT_PLATINUM)?; - // let commit = repo.0.find_commit(oid).unwrap(); - let commit = repo.as_ref().get_git2_commit(oid).unwrap(); + let repo = repo.as_ref(); + let commit = repo.get_git2_commit(oid).unwrap(); assert!(commit.parents().count() == 0); assert!(commit.parent(0).is_err()); - let bro = Browser::new(&repo, Branch::local("master"))?; - let diff = bro.initial_diff(oid)?; + let diff = repo.initial_diff(oid)?; let expected_diff = Diff { created: vec![CreateFile { @@ -453,15 +432,12 @@ mod diff { #[test] fn test_diff() -> Result<(), Error> { let repo = Repository::new(GIT_PLATINUM)?; + let repo = repo.as_ref(); let commit = repo - .as_ref() .get_git2_commit(Oid::from_str("80bacafba303bf0cdf6142921f430ff265f25095")?) .unwrap(); let parent = commit.parent(0)?; - - let bro = Browser::new(&repo, Branch::local("master"))?; - - let diff = bro.diff(parent.id().into(), commit.id().into())?; + let diff = repo.diff(parent.id().into(), commit.id().into())?; let expected_diff = Diff { created: vec![], @@ -572,8 +548,7 @@ mod threading { fn basic_test() -> Result<(), Error> { let shared_repo = Mutex::new(Repository::new(GIT_PLATINUM)?); let locked_repo: MutexGuard = shared_repo.lock().unwrap(); - let bro = Browser::new(&*locked_repo, Branch::local("master"))?; - let mut branches = bro.list_branches(RefScope::All)?; + let mut branches = locked_repo.as_ref().list_branches(RefScope::All)?; branches.sort(); assert_eq!(