mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-24 13:50:36 +08:00
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.
This commit is contained in:
@@ -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))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
@@ -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 {
|
||||
|
||||
+21
-25
@@ -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<Commit, Error> {
|
||||
browser.commit(sha1)?;
|
||||
|
||||
let history = browser.get();
|
||||
let commit = history.first();
|
||||
|
||||
pub fn commit(repo: &RepositoryRef, sha1: Oid) -> Result<Commit, Error> {
|
||||
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<Commit, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Commit, Error> {
|
||||
///
|
||||
/// Will return [`Error`] if the project doesn't exist or the surf interaction
|
||||
/// fails.
|
||||
pub fn header(browser: &mut Browser<'_>, sha1: Oid) -> Result<Header, Error> {
|
||||
browser.commit(sha1)?;
|
||||
|
||||
let history = browser.get();
|
||||
let commit = history.first();
|
||||
|
||||
Ok(Header::from(commit))
|
||||
pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result<Header, Error> {
|
||||
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<Header, Error> {
|
||||
/// Will return [`Error`] if the project doesn't exist or the surf interaction
|
||||
/// fails.
|
||||
pub fn commits<P>(
|
||||
browser: &mut Browser<'_>,
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
) -> Result<Commits, Error>
|
||||
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 })
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<P>(
|
||||
browser: &mut Browser,
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
path: &str,
|
||||
) -> Result<Blob, Error>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
make_blob(browser, maybe_revision, path, content)
|
||||
make_blob(repo, maybe_revision, path, content)
|
||||
}
|
||||
|
||||
fn make_blob<P, C>(
|
||||
browser: &mut Browser,
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
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();
|
||||
|
||||
|
||||
@@ -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<P>(
|
||||
browser: &mut Browser<'_>,
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
maybe_prefix: Option<String>,
|
||||
) -> Result<Tree, Error>
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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<P, U> {
|
||||
///
|
||||
/// * If we cannot get the branches from the `Browser`
|
||||
pub fn remote<P, U>(
|
||||
browser: &Browser,
|
||||
repo: &RepositoryRef,
|
||||
peer_id: P,
|
||||
user: U,
|
||||
) -> Result<Option<Revisions<P, U>>, 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<P, U>(browser: &Browser, peer_id: P, user: U) -> Result<Option<Revisions<P, U>>, Error>
|
||||
pub fn local<P, U>(
|
||||
repo: &RepositoryRef,
|
||||
peer_id: P,
|
||||
user: U,
|
||||
) -> Result<Option<Revisions<P, U>>, 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<P, U>(
|
||||
browser: &Browser,
|
||||
repo: &RepositoryRef,
|
||||
peer: Category<P, U>,
|
||||
) -> Result<Option<Revisions<P, U>>, 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),
|
||||
}
|
||||
}
|
||||
|
||||
+3
-79
@@ -15,10 +15,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<A> History<A> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Snapshot is a function that renders a `Directory` given
|
||||
/// the `Repo` object and a `History` of artifacts.
|
||||
type Snapshot<A, Repo, Error> = Box<dyn Fn(&Repo, &History<A>) -> Result<Directory, Error>>;
|
||||
|
||||
/// A `Browser` is a way of rendering a `History` into a
|
||||
/// `Directory` snapshot, and the current `History` it is
|
||||
/// viewing.
|
||||
pub struct Browser<Repo, A, Error> {
|
||||
snapshot: Snapshot<A, Repo, Error>,
|
||||
history: History<A>,
|
||||
repository: Repo,
|
||||
}
|
||||
|
||||
impl<Repo, A, Error> Browser<Repo, A, Error> {
|
||||
/// Get the current `History` the `Browser` is viewing.
|
||||
pub fn get(&self) -> History<A>
|
||||
where
|
||||
A: Clone,
|
||||
{
|
||||
self.history.clone()
|
||||
}
|
||||
|
||||
/// Get the current `History` the `Browser` is viewing as a ref.
|
||||
pub fn as_history(&self) -> &History<A> {
|
||||
&self.history
|
||||
}
|
||||
|
||||
/// Set the `History` the `Browser` should view.
|
||||
pub fn set(&mut self, history: History<A>) {
|
||||
self.history = history;
|
||||
}
|
||||
|
||||
/// Render the `Directory` for this `Browser`.
|
||||
pub fn get_directory(&self) -> Result<Directory, Error> {
|
||||
(self.snapshot)(&self.repository, &self.history)
|
||||
}
|
||||
|
||||
/// Modify the `History` in this `Browser`.
|
||||
pub fn modify<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&History<A>) -> History<A>,
|
||||
{
|
||||
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<F>(&mut self, default_history: History<A>, f: F)
|
||||
where
|
||||
A: Clone,
|
||||
F: Fn(&History<A>) -> Option<History<A>>,
|
||||
{
|
||||
self.modify(|history| f(history).unwrap_or_else(|| default_history.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Repo, A, Error> Vcs<A, Error> for Browser<Repo, A, Error>
|
||||
where
|
||||
Repo: Vcs<A, Error>,
|
||||
{
|
||||
type HistoryId = Repo::HistoryId;
|
||||
type ArtefactId = Repo::ArtefactId;
|
||||
|
||||
fn get_history(&self, identifier: Self::HistoryId) -> Result<History<A>, Error> {
|
||||
self.repository.get_history(identifier)
|
||||
}
|
||||
|
||||
fn get_histories(&self) -> Result<Vec<History<A>>, Error> {
|
||||
self.repository.get_histories()
|
||||
}
|
||||
|
||||
fn get_identifier(artifact: &A) -> Self::ArtefactId {
|
||||
Repo::get_identifier(artifact)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait GetVcs<Error>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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<directory::Directory, Error> {
|
||||
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<Option<Commit>, 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<Commit, Error> {
|
||||
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<Stats, Error> {
|
||||
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::<BTreeSet<_>>();
|
||||
|
||||
Ok(Stats {
|
||||
branches,
|
||||
commits,
|
||||
contributors: contributors.len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Lists branch names with `filter`.
|
||||
pub fn branch_names(&self, filter: RefScope) -> Result<Vec<BranchName>, Error> {
|
||||
let mut branches = self
|
||||
.list_branches(filter)?
|
||||
.into_iter()
|
||||
.map(|b| b.name)
|
||||
.collect::<Vec<BranchName>>();
|
||||
|
||||
branches.sort();
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
/// Lists tag names in the local RefScope.
|
||||
pub fn tag_names(&self) -> Result<Vec<TagName>, Error> {
|
||||
let tag_names = self.list_tags(RefScope::Local)?;
|
||||
let mut tags: Vec<TagName> = 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<Oid, Error> {
|
||||
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<git2::Commit, Error> {
|
||||
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<History, Error> {
|
||||
pub fn head_history(&self) -> Result<History, Error> {
|
||||
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<Vec<Branch>, Error> {
|
||||
/// Lists branches that are reachable from `oid`.
|
||||
pub fn revision_branches(&self, oid: &Oid) -> Result<Vec<Branch>, 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<file_system::Path, NonEmpty<(file_system::Label, directory::File)>>,
|
||||
) {
|
||||
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<HashMap<file_system::Path, NonEmpty<(file_system::Label, directory::File)>>, Error>
|
||||
{
|
||||
let mut file_paths_or_error: Result<
|
||||
HashMap<file_system::Path, NonEmpty<(file_system::Label, directory::File)>>,
|
||||
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<Commit, Error> for RepositoryRef<'a> {
|
||||
|
||||
+110
-135
@@ -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<Branch> = 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<Branch> = 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<Branch> = 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<Branch> = 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<Repository> = 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!(
|
||||
|
||||
Reference in New Issue
Block a user