Merge remote-tracking branch 'han/ref-format'

* han/ref-format:
  Remove Ref, RefGlob and Rev, add Glob that uses git-ref-format.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Fintan Halpenny
2022-11-09 11:20:57 +00:00
17 changed files with 411 additions and 688 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ pub struct Options {
pub allow_pattern: bool,
}
#[derive(Debug, Error)]
#[derive(Debug, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum Error {
#[error("empty input")]
+9 -1
View File
@@ -32,7 +32,6 @@ base64 = "0.13"
either = "1.5"
nom = "6"
nonempty = "0.5"
radicle-git-ext = { path = "../radicle-git-ext", features = ["serde"] }
regex = ">= 1.5.5"
serde = { features = ["serde_derive"], optional = true, version = "1" }
thiserror = "1.0"
@@ -42,6 +41,15 @@ version = "0.15.0"
default-features = false
features = ["vendored-libgit2"]
[dependencies.git-ref-format]
version = "0.1.0"
path = "../git-ref-format"
[dependencies.radicle-git-ext]
version = "0.2.0"
path = "../radicle-git-ext"
features = ["serde"]
[dev-dependencies]
criterion = "0.3"
pretty_assertions = "1.3.0"
+2 -2
View File
@@ -18,13 +18,13 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use radicle_surf::{
file_system::{unsound, Path},
vcs::git::{Branch, Repository, Rev},
vcs::git::{Branch, Repository},
};
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 rev: Rev = Branch::local("master").into();
let rev = Branch::local("master");
let mut group = c.benchmark_group("Last Commit");
for path in [
+8 -9
View File
@@ -17,8 +17,6 @@
//! Represents a commit.
use std::convert::TryFrom as _;
#[cfg(feature = "serialize")]
use serde::{
ser::{SerializeStruct as _, Serializer},
@@ -28,9 +26,10 @@ use serde::{
use crate::{
diff,
file_system,
git::Glob,
person::Person,
revision::Revision,
vcs::git::{self, BranchName, RepositoryRef, Rev},
vcs::git::{self, BranchName, RepositoryRef},
};
use radicle_git_ext::Oid;
@@ -147,7 +146,7 @@ pub struct Commits {
///
/// Will return [`Error`] if the project doesn't exist or the surf interaction
/// fails.
pub fn commit(repo: &RepositoryRef, rev: &Rev) -> Result<Commit, Error> {
pub fn commit<R: git::Revision>(repo: &RepositoryRef, rev: R) -> Result<Commit, Error> {
let commit = repo.commit(rev)?;
let sha1 = commit.id;
let header = Header::from(&commit);
@@ -195,7 +194,7 @@ pub fn commit(repo: &RepositoryRef, rev: &Rev) -> Result<Commit, Error> {
}
let branches = repo
.revision_branches(&sha1)?
.revision_branches(&sha1, &Glob::heads("*")?.and_remotes("*")?)?
.into_iter()
.map(|b| b.name)
.collect();
@@ -235,11 +234,11 @@ pub fn commits<P>(
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
let rev: Rev = match maybe_revision {
let rev = match maybe_revision {
Some(revision) => revision,
None => repo.head_oid()?.into(),
None => Revision::Sha {
sha: repo.head_oid()?,
},
};
let stats = repo.get_commit_stats(&rev)?;
+1 -6
View File
@@ -18,10 +18,7 @@
//! Represents git object type 'blob', i.e. actual file contents.
//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.
use std::{
convert::TryFrom as _,
str::{self, FromStr as _},
};
use std::str::{self, FromStr as _};
#[cfg(feature = "serialize")]
use serde::{
@@ -35,7 +32,6 @@ use crate::{
git::RepositoryRef,
object::{Error, Info, ObjectType},
revision::Revision,
vcs::git::Rev,
};
#[cfg(feature = "syntax")]
@@ -139,7 +135,6 @@ where
P: ToString,
C: FnOnce(&[u8]) -> BlobContent,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
let revision = maybe_revision.unwrap();
let root = repo.snapshot(&revision)?;
let p = file_system::Path::from_str(path)?;
+5 -5
View File
@@ -18,7 +18,7 @@
//! Represents git object type 'tree', i.e. like directory entries in Unix.
//! See git [doc](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) for more details.
use std::{convert::TryFrom as _, str::FromStr as _};
use std::str::FromStr as _;
#[cfg(feature = "serialize")]
use serde::{
@@ -32,7 +32,6 @@ use crate::{
git::RepositoryRef,
object::{Error, Info, ObjectType},
revision::Revision,
vcs::git::{Branch, Rev},
};
/// Result of a directory listing, carries other trees and blobs.
@@ -94,12 +93,13 @@ pub fn tree<P>(
where
P: ToString,
{
let maybe_revision = maybe_revision.map(Rev::try_from).transpose()?;
let prefix = maybe_prefix.unwrap_or_default();
let rev = match maybe_revision {
Some(r) => r,
None => Branch::local("main").into(),
None => Revision::Branch {
name: "main".to_string(),
peer_id: None,
},
};
let path = if prefix == "/" || prefix.is_empty() {
+32 -21
View File
@@ -17,8 +17,6 @@
//! Represents revisions
use std::convert::TryFrom;
use nonempty::NonEmpty;
#[cfg(feature = "serialize")]
@@ -27,8 +25,8 @@ use serde::{Deserialize, Serialize};
use radicle_git_ext::Oid;
use crate::{
git::{BranchName, RepositoryRef},
vcs::git::{self, error::Error, RefScope, Rev, TagName},
git::{commit::ToCommit, BranchName, Glob, RepositoryRef},
vcs::git::{self, error::Error, TagName},
};
/// Types of a peer.
@@ -79,26 +77,38 @@ pub enum Revision<P> {
},
}
impl<P> TryFrom<Revision<P>> for Rev
impl<P> git::Revision for &Revision<P>
where
P: ToString,
{
type Error = Error;
fn try_from(other: Revision<P>) -> Result<Self, Self::Error> {
match other {
Revision::Tag { name } => Ok(git::TagName::new(&name).into()),
Revision::Branch { name, peer_id } => Ok(match peer_id {
Some(peer) => {
git::Branch::remote(&format!("heads/{}", name), &peer.to_string()).into()
},
None => git::Branch::local(&name).into(),
}),
Revision::Sha { sha } => Ok(sha.into()),
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
match self {
Revision::Tag { name } => {
repo.refname_to_oid(git::TagName::new(name)?.refname().as_str())
},
Revision::Branch { name, peer_id } => {
let refname = match peer_id {
Some(peer) => {
git::Branch::remote(&format!("heads/{}", name), &peer.to_string()).refname()
},
None => git::Branch::local(name).refname(),
};
repo.refname_to_oid(&refname)
},
Revision::Sha { sha } => Ok(*sha),
}
}
}
impl<P> ToCommit for &Revision<P>
where
P: ToString,
{
fn to_commit(self, repo: &RepositoryRef) -> Result<git::Commit, Error> {
repo.commit(self)
}
}
/// Bundled response to retrieve both [`BranchName`]es and [`TagName`]s for
/// a user's repo.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -114,7 +124,7 @@ pub struct Revisions<P, U> {
}
/// Provide the [`Revisions`] for the given `peer_id`, looking for the
/// branches as [`RefScope::Remote`].
/// remote branches.
///
/// If there are no branches then this returns `None`.
///
@@ -129,7 +139,8 @@ pub fn remote<P, U>(
where
P: Clone + ToString,
{
let remote_branches = repo.branch_names(Some(peer_id.clone()).into())?;
let remote_branches =
repo.branch_names(&Glob::remotes(&format!("{}/*", peer_id.to_string()))?)?;
Ok(
NonEmpty::from_vec(remote_branches).map(|branches| Revisions {
peer_id,
@@ -143,7 +154,7 @@ where
}
/// Provide the [`Revisions`] for the given `peer_id`, looking for the
/// branches as [`RefScope::Local`].
/// local branches.
///
/// If there are no branches then this returns `None`.
///
@@ -158,7 +169,7 @@ pub fn local<P, U>(
where
P: Clone + ToString,
{
let local_branches = repo.branch_names(RefScope::Local)?;
let local_branches = repo.branch_names(&Glob::heads("*")?)?;
let tags = repo.tag_names()?;
Ok(
NonEmpty::from_vec(local_branches).map(|branches| Revisions {
+6 -19
View File
@@ -69,13 +69,12 @@ use std::str::FromStr;
pub use git2::{self, Error as Git2Error, Time};
pub use radicle_git_ext::Oid;
/// Provides ways of selecting a particular reference/revision.
mod reference;
pub use reference::{ParseError, Ref, Rev};
mod repo;
pub use repo::{Repository, RepositoryRef};
mod glob;
pub use glob::Glob;
mod history;
pub use history::History;
@@ -177,21 +176,9 @@ impl Revision for &Tag {
}
}
impl Revision for &Rev {
impl Revision for &TagName {
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
match *self {
Rev::Oid(oid) => Ok(*oid),
Rev::Ref(r) => {
let r = match repo.which_namespace()? {
None => r.clone(),
Some(namespace) => match r {
Ref::Namespace { .. } => r.clone(),
_ => r.clone().namespaced(namespace),
},
};
let refname = format!("{}", r);
Ok(repo.repo_ref.refname_to_id(&refname).map(Oid::from)?)
},
}
let refname = repo.namespaced_refname(&self.refname())?;
Ok(repo.repo_ref.refname_to_id(&refname).map(Oid::from)?)
}
}
+5 -17
View File
@@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::vcs::git::{self, error::Error, ext, reference::Ref};
use crate::vcs::git::{self, error::Error, ext};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, convert::TryFrom, fmt, str};
@@ -56,7 +56,7 @@ impl From<git2::BranchType> for BranchType {
/// to fetch a branch.
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct BranchName(pub(crate) String);
pub struct BranchName(String);
impl fmt::Display for BranchName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -65,7 +65,7 @@ impl fmt::Display for BranchName {
}
impl TryFrom<&[u8]> for BranchName {
type Error = str::Utf8Error;
type Error = Error;
fn try_from(name: &[u8]) -> Result<Self, Self::Error> {
let name = str::from_utf8(name)?;
@@ -114,18 +114,6 @@ impl Ord for Branch {
}
}
impl From<Branch> for Ref {
fn from(other: Branch) -> Self {
match other.locality {
BranchType::Local => Self::LocalBranch { name: other.name },
BranchType::Remote { name } => Self::RemoteBranch {
name: other.name,
remote: name.unwrap_or_else(|| "**".to_string()),
},
}
}
}
impl Branch {
/// Helper to create a remote `Branch` with a name
pub fn remote(name: &str, remote: &str) -> Self {
@@ -147,11 +135,11 @@ impl Branch {
/// Get the name of the `Branch`.
pub fn refname(&self) -> String {
let branch_name = self.name.0.clone();
let branch_name = &self.name.0;
match self.locality {
BranchType::Local => format!("refs/heads/{}", branch_name),
BranchType::Remote { ref name } => match name {
None => branch_name,
None => branch_name.to_string(),
Some(remote_name) => format!("refs/remotes/{}/{}", remote_name, branch_name),
},
}
+2 -2
View File
@@ -17,7 +17,7 @@
use crate::{
file_system::{self, directory},
vcs::git::{error::Error, Branch, RepositoryRef, Rev, Tag},
vcs::git::{error::Error, Branch, RepositoryRef, Tag, TagName},
};
use radicle_git_ext::Oid;
use std::{convert::TryFrom, str};
@@ -216,7 +216,7 @@ impl ToCommit for &Tag {
}
}
impl ToCommit for &Rev {
impl ToCommit for &TagName {
fn to_commit(self, repo: &RepositoryRef) -> Result<Commit, Error> {
repo.commit(self)
}
+3
View File
@@ -88,4 +88,7 @@ pub enum Error {
/// A wrapper around the generic [`git2::Error`].
#[error(transparent)]
Git(#[from] git2::Error),
/// A wrapper around git-ref-format::Error
#[error(transparent)]
RefFormat(#[from] git_ref_format::Error),
}
+107
View File
@@ -0,0 +1,107 @@
// This file is part of radicle-git
// <https://github.com/radicle-dev/radicle-git>
//
// Copyright (C) 2022 The Radicle Team <dev@radicle.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 or
// later as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::vcs::git::Error;
use git_ref_format::refspec::PatternString;
use std::{convert::TryFrom, marker::PhantomData, str};
/// A collection of globs for T (a git reference type).
pub struct Glob<T> {
globs: Vec<PatternString>,
glob_type: PhantomData<T>, // To support different methods for different T.
}
impl<T> Glob<T> {
/// Returns the globs.
pub fn globs(&self) -> Vec<&str> {
self.globs.iter().map(|g| g.as_str()).collect()
}
}
impl<Namespace> Glob<Namespace> {
/// Creates a `Glob` for namespaces.
pub fn namespaces(glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/namespaces/{}", glob))?;
let globs = vec![pattern];
Ok(Self {
globs,
glob_type: PhantomData,
})
}
/// Adds namespaces patterns to existing `Glob`.
pub fn and(mut self, glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/namespaces/{}", glob))?;
self.globs.push(pattern);
Ok(self)
}
}
impl<Tag> Glob<Tag> {
/// Creates a `Glob` for local tags.
pub fn tags(glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/tags/{}", glob))?;
let globs = vec![pattern];
Ok(Self {
globs,
glob_type: PhantomData,
})
}
/// Updates a `Glob` to include other tags.
pub fn and_tags(mut self, glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/tags/{}", glob))?;
self.globs.push(pattern);
Ok(self)
}
}
impl<Branch> Glob<Branch> {
/// Creates a `Glob` for local branches.
pub fn heads(glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/heads/{}", glob))?;
let globs = vec![pattern];
Ok(Self {
globs,
glob_type: PhantomData,
})
}
/// Creates a `Glob` for remote branches.
pub fn remotes(glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/remotes/{}", glob))?;
let globs = vec![pattern];
Ok(Self {
globs,
glob_type: PhantomData,
})
}
/// Updates a `Glob` to include local branches.
pub fn and_heads(mut self, glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/heads/{}", glob))?;
self.globs.push(pattern);
Ok(self)
}
/// Updates a `Glob` to include remote branches.
pub fn and_remotes(mut self, glob: &str) -> Result<Self, Error> {
let pattern = PatternString::try_from(format!("refs/remotes/{}", glob))?;
self.globs.push(pattern);
Ok(self)
}
}
-191
View File
@@ -1,191 +0,0 @@
// This file is part of radicle-surf
// <https://github.com/radicle-dev/radicle-surf>
//
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 or
// later as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::{fmt, str};
use thiserror::Error;
use crate::vcs::git::{BranchName, Namespace, TagName};
use radicle_git_ext::Oid;
pub(super) mod glob;
/// A revision within the repository.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Rev {
/// A reference to a branch or tag.
Ref(Ref),
/// A particular commit identifier.
Oid(Oid),
}
impl<R> From<R> for Rev
where
R: Into<Ref>,
{
fn from(other: R) -> Self {
Self::Ref(other.into())
}
}
impl From<Oid> for Rev {
fn from(other: Oid) -> Self {
Self::Oid(other)
}
}
/// A structured way of referring to a git reference.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ref {
/// A git tag, which can be found under `.git/refs/tags/`.
Tag {
/// The name of the tag, e.g. `v1.0.0`.
name: TagName,
},
/// A git branch, which can be found under `.git/refs/heads/`.
LocalBranch {
/// The name of the branch, e.g. `master`.
name: BranchName,
},
/// A git branch, which can be found under `.git/refs/remotes/`.
RemoteBranch {
/// The remote name, e.g. `origin`.
remote: String,
/// The name of the branch, e.g. `master`.
name: BranchName,
},
/// A git namespace, which can be found under `.git/refs/namespaces/`.
///
/// Note that namespaces can be nested.
Namespace {
/// The name value of the namespace.
namespace: String,
/// The reference under that namespace, e.g. The
/// `refs/remotes/origin/master/ portion of `refs/namespaces/
/// moi/refs/remotes/origin/master`.
reference: Box<Ref>,
},
}
impl Ref {
/// Add a [`Namespace`] to a `Ref`.
pub fn namespaced(self, Namespace { values: namespaces }: Namespace) -> Self {
let mut ref_namespace = self;
for namespace in namespaces.into_iter().rev() {
ref_namespace = Self::Namespace {
namespace,
reference: Box::new(ref_namespace.clone()),
};
}
ref_namespace
}
}
impl fmt::Display for Ref {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tag { name } => write!(f, "refs/tags/{}", name),
Self::LocalBranch { name } => write!(f, "refs/heads/{}", name),
Self::RemoteBranch { remote, name } => write!(f, "refs/remotes/{}/{}", remote, name),
Self::Namespace {
namespace,
reference,
} => write!(f, "refs/namespaces/{}/{}", namespace, reference),
}
}
}
/// Error when parsing a ref.
#[derive(Debug, PartialEq, Eq, Error)]
pub enum ParseError {
/// The parsed ref is malformed.
#[error("the ref provided '{0}' was malformed")]
MalformedRef(String),
}
pub mod parser {
use nom::{bytes, named, tag, IResult};
use crate::vcs::git::{BranchName, TagName};
use super::Ref;
const HEADS: &str = "refs/heads/";
const REMOTES: &str = "refs/remotes/";
const TAGS: &str = "refs/tags/";
const NAMESPACES: &str = "refs/namespaces/";
named!(heads, tag!(HEADS));
named!(remotes, tag!(REMOTES));
named!(tags, tag!(TAGS));
named!(namsespaces, tag!(NAMESPACES));
type Error<'a> = nom::Err<nom::error::Error<&'a str>>;
pub fn component(s: &str) -> IResult<&str, &str> {
bytes::complete::take_till(|c| c == '/')(s).and_then(|(rest, component)| {
bytes::complete::take(1u8)(rest).map(|(rest, _)| (rest, component))
})
}
pub fn local(s: &str) -> Result<Ref, Error> {
bytes::complete::tag(HEADS)(s).map(|(name, _)| Ref::LocalBranch {
name: BranchName::new(name),
})
}
pub fn remote(s: &str) -> Result<Ref, Error> {
bytes::complete::tag(REMOTES)(s).and_then(|(rest, _)| {
component(rest).map(|(rest, remote)| Ref::RemoteBranch {
remote: remote.to_owned(),
name: BranchName::new(rest),
})
})
}
pub fn tag(s: &str) -> Result<Ref, Error> {
bytes::complete::tag(TAGS)(s).map(|(name, _)| Ref::Tag {
name: TagName::new(name),
})
}
pub fn namespace(s: &str) -> Result<Ref, Error> {
bytes::complete::tag(NAMESPACES)(s).and_then(|(rest, _)| {
component(rest).and_then(|(rest, namespace)| {
Ok(Ref::Namespace {
namespace: namespace.to_owned(),
reference: Box::new(parse(rest)?),
})
})
})
}
pub fn parse(s: &str) -> Result<Ref, nom::Err<nom::error::Error<&str>>> {
local(s)
.or_else(|_| remote(s))
.or_else(|_| tag(s))
.or_else(|_| namespace(s))
}
}
impl str::FromStr for Ref {
type Err = ParseError;
fn from_str(reference: &str) -> Result<Self, Self::Err> {
parser::parse(reference).map_err(|_| ParseError::MalformedRef(reference.to_owned()))
}
}
-183
View File
@@ -1,183 +0,0 @@
// This file is part of radicle-surf
// <https://github.com/radicle-dev/radicle-surf>
//
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 or
// later as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
git::RefScope,
vcs::git::{error, repo::RepositoryRef},
};
use either::Either;
use std::fmt::{self, Write as _};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RefGlob {
/// When calling [`RefGlob::references`] this will return the references via
/// the globs `refs/heads/*` and `refs/remotes/**/*`.
Branch,
/// When calling [`RefGlob::references`] this will return the references via
/// the glob `refs/heads/*`.
LocalBranch,
/// When calling [`RefGlob::references`] this will return the references via
/// either of the following globs:
/// * `refs/remotes/**/*`
/// * `refs/remotes/{remote}/*`
RemoteBranch {
/// If `remote` is `None` then the `**` wildcard will be used, otherwise
/// the provided remote name will be used.
remote: Option<String>,
},
/// When calling [`RefGlob::references`] this will return the references via
/// the globs `refs/tags/*` and `refs/remotes/*/tags`
Tag,
/// When calling [`RefGlob::references`] this will return the references via
/// the glob `refs/tags/*`.
LocalTag,
/// When calling [`RefGlob::references`] this will return the references via
/// either of the following globs:
/// * `refs/remotes/*/tags/*`
/// * `refs/remotes/{remote}/tags/*`
RemoteTag {
/// If `remote` is `None` then the `*` wildcard will be used, otherwise
/// the provided remote name will be used.
remote: Option<String>,
},
/// refs/namespaces/**
Namespace,
}
/// Iterator chaining multiple [`git2::References`]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct References<'a> {
inner: Vec<git2::References<'a>>,
}
impl<'a> References<'a> {
pub fn iter(self) -> impl Iterator<Item = Result<git2::Reference<'a>, git2::Error>> {
self.inner.into_iter().flatten()
}
}
impl RefGlob {
pub fn branch(scope: RefScope) -> Self {
match scope {
RefScope::All => Self::Branch,
RefScope::Local => Self::LocalBranch,
RefScope::Remote { name } => Self::RemoteBranch { remote: name },
}
}
pub fn tag(scope: RefScope) -> Self {
match scope {
RefScope::All => Self::Tag,
RefScope::Local => Self::LocalTag,
RefScope::Remote { name } => Self::RemoteTag { remote: name },
}
}
pub fn references<'a>(&self, repo: &RepositoryRef<'a>) -> Result<References<'a>, error::Error> {
let namespace = repo
.which_namespace()?
.map_or(Either::Left(std::iter::empty()), |namespace| {
Either::Right(namespace.values.into_iter())
});
self.with_namespace_glob(namespace, repo)
}
fn with_namespace_glob<'a>(
&self,
namespace: impl Iterator<Item = String>,
repo: &RepositoryRef<'a>,
) -> Result<References<'a>, error::Error> {
let mut namespace_glob = "".to_string();
for n in namespace {
let _ = write!(namespace_glob, "refs/namespaces/{n}/");
}
Ok(match self {
Self::Branch => {
let remotes = repo.repo_ref.references_glob(&format!(
"{}{}",
namespace_glob,
Self::RemoteBranch { remote: None }
))?;
let locals = repo.repo_ref.references_glob(&format!(
"{}{}",
namespace_glob,
&Self::LocalBranch
))?;
References {
inner: vec![remotes, locals],
}
},
Self::Tag => {
let remotes = repo.repo_ref.references_glob(&format!(
"{}{}",
namespace_glob,
Self::RemoteTag { remote: None }
))?;
let locals = repo.repo_ref.references_glob(&format!(
"{}{}",
namespace_glob,
&Self::LocalTag
))?;
References {
inner: vec![remotes, locals],
}
},
other => References {
inner: vec![repo
.repo_ref
.references_glob(&format!("{}{}", namespace_glob, other,))?],
},
})
}
}
impl fmt::Display for RefGlob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LocalBranch => write!(f, "refs/heads/*"),
Self::RemoteBranch { remote } => {
write!(f, "refs/remotes/")?;
match remote {
None => write!(f, "**/*"),
Some(remote) => write!(f, "{}/*", remote),
}
},
Self::LocalTag => write!(f, "refs/tags/*"),
Self::RemoteTag { remote } => {
let remote = match remote {
Some(remote) => remote.as_ref(),
None => "*",
};
write!(f, "refs/remotes/{}/tags/*", remote)
},
// Note: the glob below would be used, but libgit doesn't care for union globs.
// write!(f, "refs/{{remotes/**/*,heads/*}}")
Self::Branch | Self::Tag => {
panic!("{}",
"fatal: `Display` should not be called on `RefGlob::Branch` or `RefGlob::Tag` Since
this `enum` is private to the repository, it should not be called from the outside.
Unfortunately, libgit does not support union of globs otherwise this would display
refs/{remotes/**/*,heads/*}"
)
},
Self::Namespace => write!(f, "refs/namespaces/**"),
}
}
}
+133 -80
View File
@@ -21,13 +21,12 @@ use crate::{
file_system::{directory, DirectoryContents, Label},
vcs::git::{
error::*,
reference::{glob::RefGlob, Rev},
Branch,
BranchName,
Commit,
Glob,
History,
Namespace,
RefScope,
Revision,
Signature,
Stats,
@@ -38,7 +37,7 @@ use crate::{
use directory::Directory;
use radicle_git_ext::Oid;
use std::{
collections::{BTreeSet, HashSet},
collections::{btree_set, BTreeSet},
convert::TryFrom,
path::PathBuf,
str,
@@ -75,6 +74,67 @@ impl<'a> From<&'a git2::Repository> for RepositoryRef<'a> {
}
}
// I think the following `Tags` and `Branches` would be merged
// using Generic associated types supported in Rust 1.65.0.
/// An iterator for tags.
pub struct Tags<'a> {
references: Vec<git2::References<'a>>,
current: usize,
}
impl<'a> Iterator for Tags<'a> {
type Item = Result<Tag, Error>;
fn next(&mut self) -> Option<Self::Item> {
while self.current < self.references.len() {
match self.references.get_mut(self.current) {
Some(refs) => match refs.next() {
Some(res) => return Some(res.map_err(Error::Git).and_then(Tag::try_from)),
None => self.current += 1,
},
None => break,
}
}
None
}
}
/// An iterator for branches.
pub struct Branches<'a> {
references: Vec<git2::References<'a>>,
current: usize,
}
impl<'a> Iterator for Branches<'a> {
type Item = Result<Branch, Error>;
fn next(&mut self) -> Option<Self::Item> {
while self.current < self.references.len() {
match self.references.get_mut(self.current) {
Some(refs) => match refs.next() {
Some(res) => return Some(res.map_err(Error::Git).and_then(Branch::try_from)),
None => self.current += 1,
},
None => break,
}
}
None
}
}
/// An iterator for namespaces.
pub struct Namespaces {
namespaces: btree_set::IntoIter<Namespace>,
}
impl Iterator for Namespaces {
type Item = Namespace;
fn next(&mut self) -> Option<Self::Item> {
self.namespaces.next()
}
}
impl<'a> RepositoryRef<'a> {
/// What is the current namespace we're browsing in.
pub fn which_namespace(&self) -> Result<Option<Namespace>, Error> {
@@ -84,56 +144,52 @@ impl<'a> RepositoryRef<'a> {
.transpose()
}
/// List the branches within a repository, filtering out ones that do not
/// parse correctly.
///
/// # Errors
///
/// * [`Error::Git`]
pub fn list_branches(&self, scope: RefScope) -> Result<Vec<Branch>, Error> {
RefGlob::branch(scope)
.references(self)?
.iter()
.try_fold(vec![], |mut acc, reference| {
let branch = Branch::try_from(reference?)?;
acc.push(branch);
Ok(acc)
})
/// Returns an iterator of branches that match `pattern`.
pub fn branches(&self, pattern: &Glob<Branch>) -> Result<Branches, Error> {
let mut branches = Branches {
references: vec![],
current: 0,
};
for glob in pattern.globs().iter() {
let namespaced = self.namespaced_refname(glob)?;
let references = self.repo_ref.references_glob(&namespaced)?;
branches.references.push(references);
}
Ok(branches)
}
/// List the tags within a repository, filtering out ones that do not parse
/// correctly.
///
/// # Errors
///
/// * [`Error::Git`]
pub fn list_tags(&self, scope: RefScope) -> Result<Vec<Tag>, Error> {
RefGlob::tag(scope)
.references(self)?
.iter()
.try_fold(vec![], |mut acc, reference| {
let tag = Tag::try_from(reference?)?;
acc.push(tag);
Ok(acc)
})
/// Returns an iterator of tags that match `pattern`.
pub fn tags(&self, pattern: &Glob<Tag>) -> Result<Tags, Error> {
let mut tags = Tags {
references: vec![],
current: 0,
};
for glob in pattern.globs().iter() {
let namespaced = self.namespaced_refname(glob)?;
let references = self.repo_ref.references_glob(&namespaced)?;
tags.references.push(references);
}
Ok(tags)
}
/// List the namespaces within a repository, filtering out ones that do not
/// parse correctly.
///
/// # Errors
///
/// * [`Error::Git`]
pub fn list_namespaces(&self) -> Result<Vec<Namespace>, Error> {
let namespaces: Result<HashSet<Namespace>, Error> = RefGlob::Namespace
.references(self)?
.iter()
.try_fold(HashSet::new(), |mut acc, reference| {
let namespace = Namespace::try_from(reference?)?;
acc.insert(namespace);
Ok(acc)
});
Ok(namespaces?.into_iter().collect())
/// Returns an iterator of namespaces that match `pattern`.
pub fn namespaces(&self, pattern: &Glob<Namespace>) -> Result<Namespaces, Error> {
let mut set = BTreeSet::new();
for glob in pattern.globs().iter() {
let new_set = self
.repo_ref
.references_glob(glob)?
.map(|reference| {
reference
.map_err(Error::Git)
.and_then(|r| Namespace::try_from(r).map_err(|_| Error::EmptyNamespace))
})
.collect::<Result<BTreeSet<Namespace>, Error>>()?;
set.extend(new_set);
}
Ok(Namespaces {
namespaces: set.into_iter(),
})
}
/// Get the [`Diff`] between two commits.
@@ -174,7 +230,11 @@ impl<'a> RepositoryRef<'a> {
/// 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> {
pub fn last_commit<C: ToCommit>(
&self,
path: file_system::Path,
rev: C,
) -> Result<Option<Commit>, Error> {
let history = self.history(rev)?;
history.by_path(path).next().transpose()
}
@@ -190,7 +250,7 @@ impl<'a> RepositoryRef<'a> {
/// Gets stats of `commit`.
pub fn get_commit_stats<C: ToCommit>(&self, commit: C) -> Result<Stats, Error> {
let branches = self.list_branches(RefScope::Local)?.len();
let branches = self.branches(&Glob::heads("*")?)?.count();
let history = self.history(commit)?;
let mut commits = 0;
@@ -212,13 +272,10 @@ impl<'a> RepositoryRef<'a> {
}
/// 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>>();
pub fn branch_names(&self, filter: &Glob<Branch>) -> Result<Vec<BranchName>, Error> {
let branches: Result<Vec<BranchName>, Error> =
self.branches(filter)?.map(|b| b.map(|b| b.name)).collect();
let mut branches = branches?;
branches.sort();
Ok(branches)
@@ -226,12 +283,10 @@ impl<'a> RepositoryRef<'a> {
/// 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();
let mut tags = self
.tags(&Glob::tags("*")?)?
.map(|t| t.map(|t| t.name()))
.collect::<Result<Vec<TagName>, Error>>()?;
tags.sort();
Ok(tags)
@@ -258,6 +313,11 @@ impl<'a> RepositoryRef<'a> {
Ok(fullname)
}
pub(crate) fn refname_to_oid(&self, refname: &str) -> Result<Oid, Error> {
let oid = self.repo_ref.refname_to_id(refname)?;
Ok(oid.into())
}
/// Get a particular `git2::Commit` of `oid`.
pub(crate) fn get_git2_commit(&self, oid: Oid) -> Result<git2::Commit, Error> {
self.repo_ref.find_commit(oid.into()).map_err(Error::Git)
@@ -292,23 +352,16 @@ impl<'a> RepositoryRef<'a> {
}
/// 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());
pub fn revision_branches(&self, oid: &Oid, glob: &Glob<Branch>) -> Result<Vec<Branch>, Error> {
let mut contained_branches = vec![];
references.try_for_each(|reference| {
let reference = reference?;
self.reachable_from(&reference, oid).and_then(|contains| {
if contains {
let branch = Branch::try_from(reference)?;
contained_branches.push(branch);
}
Ok(())
})
})?;
for branch in self.branches(glob)? {
let branch = branch?;
let namespaced = self.namespaced_refname(&branch.refname())?;
let reference = self.repo_ref.find_reference(&namespaced)?;
if self.reachable_from(&reference, oid)? {
contained_branches.push(branch);
}
}
Ok(contained_branches)
}
+17 -15
View File
@@ -15,14 +15,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::vcs::git::{self, error::Error, reference::Ref, Author};
use crate::vcs::git::{self, error::Error, Author};
use git_ref_format::RefString;
use radicle_git_ext::Oid;
use std::{convert::TryFrom, fmt, str};
/// A newtype wrapper over `String` to separate out the fact that a caller wants
/// to fetch a tag.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct TagName(String);
pub struct TagName(RefString);
impl fmt::Display for TagName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -31,7 +32,7 @@ impl fmt::Display for TagName {
}
impl TryFrom<&[u8]> for TagName {
type Error = str::Utf8Error;
type Error = Error;
fn try_from(name: &[u8]) -> Result<Self, Self::Error> {
let name = str::from_utf8(name)?;
@@ -39,26 +40,27 @@ impl TryFrom<&[u8]> for TagName {
Ok(stripped) => stripped,
Err(original) => original,
};
Ok(Self(short_name))
}
}
impl From<TagName> for Ref {
fn from(other: TagName) -> Self {
Self::Tag { name: other }
let refstring = RefString::try_from(short_name)?;
Ok(Self(refstring))
}
}
impl TagName {
/// Create a new `TagName`.
pub fn new(name: &str) -> Self {
TagName(name.into())
pub fn new(name: &str) -> Result<Self, Error> {
let refstring = RefString::try_from(name)?;
Ok(Self(refstring))
}
/// Access the string value of the `TagName`.
pub fn name(&self) -> &str {
&self.0
}
/// Returns the full ref name of the tag.
pub fn refname(&self) -> String {
format!("refs/tags/{}", self.name())
}
}
/// The static information of a [`git2::Tag`].
@@ -109,12 +111,12 @@ impl Tag {
/// Returns the full ref name of the tag.
pub fn refname(&self) -> String {
format!("refs/tags/{}", self.name().name())
self.name().refname()
}
}
impl<'repo> TryFrom<git2::Tag<'repo>> for Tag {
type Error = str::Utf8Error;
type Error = Error;
fn try_from(tag: git2::Tag) -> Result<Self, Self::Error> {
let id = tag.id().into();
@@ -152,7 +154,7 @@ impl<'repo> TryFrom<git2::Reference<'repo>> for Tag {
let mut split = name.0.splitn(2, '/');
let remote = split.next().map(|x| x.to_owned());
let name = split.next().unwrap();
(remote, TagName(name.to_owned()))
(remote, TagName::new(name)?)
} else {
(None, name)
};
+80 -136
View File
@@ -8,7 +8,7 @@ use radicle_surf::git::{Author, BranchType, Commit};
use radicle_surf::{
diff::*,
file_system::{unsound, DirectoryContents, Path},
git::{error::Error, Branch, BranchName, Namespace, Oid, RefScope, Repository, Rev, TagName},
git::{error::Error, Branch, Glob, Namespace, Oid, Repository, TagName},
};
const GIT_PLATINUM: &str = "../data/git-platinum";
@@ -54,15 +54,17 @@ mod namespace {
assert_eq!(history.head(), history_feature.head());
let expected_branches: Vec<Branch> = vec![Branch::local("feature/#1194")];
let mut branches = repo.list_branches(RefScope::Local)?;
let mut branches = repo
.branches(&Glob::heads("*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(expected_branches, branches);
let expected_branches: Vec<Branch> = vec![Branch::remote("feature/#1194", "fein")];
let mut branches = repo.list_branches(RefScope::Remote {
name: Some("fein".to_string()),
})?;
let mut branches = repo
.branches(&Glob::remotes("fein/*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(expected_branches, branches);
@@ -89,7 +91,9 @@ mod namespace {
assert_eq!(history.head(), golden_history.head());
let expected_branches: Vec<Branch> = vec![Branch::local("banana"), Branch::local("master")];
let mut branches = repo.list_branches(RefScope::Local)?;
let mut branches = repo
.branches(&Glob::heads("*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(expected_branches, branches);
@@ -99,9 +103,9 @@ mod namespace {
Branch::remote("heelflip", "kickflip"),
Branch::remote("v0.1.0", "kickflip"),
];
let mut branches = repo.list_branches(RefScope::Remote {
name: Some("kickflip".to_string()),
})?;
let mut branches = repo
.branches(&Glob::remotes("kickflip/*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(expected_branches, branches);
@@ -126,7 +130,9 @@ mod namespace {
assert_ne!(history.head(), silver_history.head());
let expected_branches: Vec<Branch> = vec![Branch::local("master")];
let mut branches = repo.list_branches(RefScope::All)?;
let mut branches = repo
.branches(&Glob::heads("*")?.and_remotes("*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(expected_branches, branches);
@@ -180,8 +186,8 @@ mod rev {
fn commit() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev: Rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?.into();
let mut history = repo.history(&rev)?;
let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
let mut history = repo.history(rev)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(history.any(|commit| commit.unwrap().id == commit1));
@@ -193,8 +199,8 @@ mod rev {
fn commit_parents() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev: Rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?.into();
let history = repo.history(&rev)?;
let rev = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
let history = repo.history(rev)?;
let commit = history.head();
assert_eq!(
@@ -209,8 +215,8 @@ mod rev {
fn commit_short() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev: Rev = repo.oid("3873745c8")?.into();
let mut history = repo.history(&rev)?;
let rev = repo.oid("3873745c8")?;
let mut history = repo.history(rev)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(history.any(|commit| commit.unwrap().id == commit1));
@@ -222,7 +228,7 @@ mod rev {
fn tag() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let repo = repo.as_ref();
let rev: Rev = TagName::new("v0.2.0").into();
let rev = TagName::new("v0.2.0")?;
let history = repo.history(&rev)?;
let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?;
@@ -245,12 +251,11 @@ mod last_commit {
Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3").expect("Failed to parse SHA");
// memory.rs is commited later so it should not exist here.
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,
oid,
)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -260,7 +265,7 @@ mod last_commit {
// README.md exists in this commit.
let readme_last_commit = repo
.as_ref()
.last_commit(Path::with_root(&[unsound::label::new("README.md")]), &rev)
.last_commit(Path::with_root(&[unsound::label::new("README.md")]), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -274,13 +279,12 @@ mod last_commit {
// Check that last commit is the actual last commit even if head commit differs.
let oid =
Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Could not parse SHA");
let rev: Rev = oid.into();
let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
let folder_svelte = repo
.as_ref()
.last_commit(unsound::path::new("~/examples/Folder.svelte"), &rev)
.last_commit(unsound::path::new("~/examples/Folder.svelte"), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -294,7 +298,6 @@ mod last_commit {
// Check that last commit is the actual last commit even if head commit differs.
let oid =
Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Failed to parse SHA");
let rev: Rev = oid.into();
let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
@@ -302,7 +305,7 @@ mod last_commit {
.as_ref()
.last_commit(
unsound::path::new("~/this/is/a/really/deeply/nested/directory/tree"),
&rev,
oid,
)
.expect("Failed to get last commit")
.map(|commit| commit.id);
@@ -319,20 +322,19 @@ mod last_commit {
// Check that last commit is the actual last commit even if head commit differs.
let oid =
Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").expect("Failed to parse SHA");
let rev: Rev = oid.into();
let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
let backslash_commit_id = repo
.as_ref()
.last_commit(unsound::path::new("~/special/faux\\path"), &rev)
.last_commit(unsound::path::new("~/special/faux\\path"), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(backslash_commit_id, Some(expected_commit_id));
let ogre_commit_id = repo
.as_ref()
.last_commit(unsound::path::new("~/special/👹👹👹"), &rev)
.last_commit(unsound::path::new("~/special/👹👹👹"), oid)
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(ogre_commit_id, Some(expected_commit_id));
@@ -342,7 +344,7 @@ mod last_commit {
fn root() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let rev: Rev = Branch::local("master").into();
let rev = Branch::local("master");
let root_last_commit_id = repo
.as_ref()
.last_commit(Path::root(), &rev)
@@ -567,6 +569,8 @@ mod diff {
#[cfg(test)]
mod threading {
use radicle_surf::git::Glob;
use super::*;
use std::sync::{Mutex, MutexGuard};
@@ -574,17 +578,20 @@ 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 mut branches = locked_repo.as_ref().list_branches(RefScope::All)?;
let mut branches = locked_repo
.as_ref()
.branches(&Glob::heads("*")?.and_remotes("*")?)?
.collect::<Result<Vec<Branch>, Error>>()?;
branches.sort();
assert_eq!(
branches,
vec![
Branch::remote("HEAD", "origin"),
Branch::remote("dev", "origin"),
Branch::local("dev"),
Branch::remote("master", "origin"),
Branch::remote("dev", "origin"),
Branch::local("master"),
Branch::remote("master", "origin"),
Branch::remote("orange/pineapple", "banana"),
Branch::remote("pineapple", "banana"),
]
@@ -709,105 +716,52 @@ mod ext {
#[cfg(test)]
mod reference {
use super::*;
use radicle_surf::vcs::git::{ParseError, Ref};
use std::str::FromStr;
use radicle_surf::vcs::git::{Glob, Tag};
#[test]
fn parse_ref() -> Result<(), ParseError> {
assert_eq!(
Ref::from_str("refs/remotes/origin/master"),
Ok(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
);
fn test_branches() {
let repo = Repository::new(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let branches = repo.branches(&Glob::heads("*").unwrap()).unwrap();
for b in branches {
println!("{}", b.unwrap().name);
}
let branches = repo
.branches(&Glob::heads("*").unwrap().and_remotes("banana/*").unwrap())
.unwrap();
for b in branches {
println!("{}", b.unwrap().refname());
}
}
assert_eq!(
Ref::from_str("refs/heads/master"),
Ok(Ref::LocalBranch {
name: BranchName::new("master"),
})
);
#[test]
fn test_tag_snapshot() {
let repo = Repository::new(GIT_PLATINUM).unwrap();
let repo_ref = repo.as_ref();
let tags = repo_ref
.tags(&Glob::tags("*").unwrap())
.unwrap()
.collect::<Result<Vec<Tag>, Error>>()
.unwrap();
assert_eq!(tags.len(), 6);
let root_dir = repo_ref.snapshot(&tags[0]).unwrap();
assert_eq!(root_dir.contents().count(), 1);
}
assert_eq!(
Ref::from_str("refs/heads/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/heads/prefix/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("prefix/i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/tags/v0.0.1"),
Ok(Ref::Tag {
name: TagName::new("v0.0.1")
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/remotes/origin/master"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/namespaces/toi/refs/tags/v1.0.0"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::Namespace {
namespace: "toi".to_string(),
reference: Box::new(Ref::Tag {
name: TagName::new("v1.0.0")
})
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::LocalBranch {
name: BranchName::new("feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/remotes/fein/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "fein".to_string(),
name: BranchName::new("heads/feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/remotes/master"),
Err(ParseError::MalformedRef("refs/remotes/master".to_owned())),
);
assert_eq!(
Ref::from_str("refs/namespaces/refs/remotes/origin/master"),
Err(ParseError::MalformedRef(
"refs/namespaces/refs/remotes/origin/master".to_owned()
)),
);
Ok(())
#[test]
fn test_namespaces() {
let repo = Repository::new(GIT_PLATINUM).unwrap();
let repo = repo.as_ref();
let namespaces = repo.namespaces(&Glob::namespaces("*").unwrap()).unwrap();
assert_eq!(namespaces.count(), 3);
let namespaces = repo
.namespaces(&Glob::namespaces("golden/*").unwrap())
.unwrap();
assert_eq!(namespaces.count(), 2);
let namespaces = repo
.namespaces(&Glob::namespaces("golden/*").unwrap().and("me/*").unwrap())
.unwrap();
assert_eq!(namespaces.count(), 3);
}
}
@@ -840,16 +794,6 @@ mod code_browsing {
}
}
#[test]
fn test_tag_snapshot() {
let repo = Repository::new(GIT_PLATINUM).unwrap();
let repo_ref = repo.as_ref();
let tags = repo_ref.list_tags(RefScope::Local).unwrap();
assert_eq!(tags.len(), 6);
let root_dir = repo_ref.snapshot(&tags[0]).unwrap();
assert_eq!(root_dir.contents().count(), 1);
}
#[test]
fn test_file_history() {
let repo = Repository::new(GIT_PLATINUM).unwrap();