diff --git a/git-ref-format/core/src/check.rs b/git-ref-format/core/src/check.rs index a706254..9509cb4 100644 --- a/git-ref-format/core/src/check.rs +++ b/git-ref-format/core/src/check.rs @@ -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")] diff --git a/radicle-surf/Cargo.toml b/radicle-surf/Cargo.toml index bbba54c..242bdef 100644 --- a/radicle-surf/Cargo.toml +++ b/radicle-surf/Cargo.toml @@ -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" diff --git a/radicle-surf/benches/last_commit.rs b/radicle-surf/benches/last_commit.rs index fef5f92..7062939 100644 --- a/radicle-surf/benches/last_commit.rs +++ b/radicle-surf/benches/last_commit.rs @@ -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 [ diff --git a/radicle-surf/src/commit.rs b/radicle-surf/src/commit.rs index 8956220..cc41a90 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/commit.rs @@ -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 { +pub fn commit(repo: &RepositoryRef, rev: R) -> Result { 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 { } 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

( 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)?; diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/object/blob.rs index 31d1690..bf2f576 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/object/blob.rs @@ -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)?; diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index 842190a..c9931cc 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -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

( 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() { diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/revision.rs index 3998b1f..bbea2ed 100644 --- a/radicle-surf/src/revision.rs +++ b/radicle-surf/src/revision.rs @@ -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

{ }, } -impl

TryFrom> for Rev +impl

git::Revision for &Revision

where P: ToString, { - type Error = Error; - - fn try_from(other: Revision

) -> Result { - 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 { + 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

ToCommit for &Revision

+where + P: ToString, +{ + fn to_commit(self, repo: &RepositoryRef) -> Result { + 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 { } /// 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( 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( 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 { diff --git a/radicle-surf/src/vcs/git.rs b/radicle-surf/src/vcs/git.rs index e8db56c..ac8ed28 100644 --- a/radicle-surf/src/vcs/git.rs +++ b/radicle-surf/src/vcs/git.rs @@ -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 { - 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)?) } } diff --git a/radicle-surf/src/vcs/git/branch.rs b/radicle-surf/src/vcs/git/branch.rs index 5f72ef5..881517a 100644 --- a/radicle-surf/src/vcs/git/branch.rs +++ b/radicle-surf/src/vcs/git/branch.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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 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 { let name = str::from_utf8(name)?; @@ -114,18 +114,6 @@ impl Ord for Branch { } } -impl From 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), }, } diff --git a/radicle-surf/src/vcs/git/commit.rs b/radicle-surf/src/vcs/git/commit.rs index 2216085..9fded41 100644 --- a/radicle-surf/src/vcs/git/commit.rs +++ b/radicle-surf/src/vcs/git/commit.rs @@ -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 { repo.commit(self) } diff --git a/radicle-surf/src/vcs/git/error.rs b/radicle-surf/src/vcs/git/error.rs index 474b09c..a1088ed 100644 --- a/radicle-surf/src/vcs/git/error.rs +++ b/radicle-surf/src/vcs/git/error.rs @@ -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), } diff --git a/radicle-surf/src/vcs/git/glob.rs b/radicle-surf/src/vcs/git/glob.rs new file mode 100644 index 0000000..067a121 --- /dev/null +++ b/radicle-surf/src/vcs/git/glob.rs @@ -0,0 +1,107 @@ +// This file is part of radicle-git +// +// +// Copyright (C) 2022 The Radicle Team +// +// 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 . + +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 { + globs: Vec, + glob_type: PhantomData, // To support different methods for different T. +} + +impl Glob { + /// Returns the globs. + pub fn globs(&self) -> Vec<&str> { + self.globs.iter().map(|g| g.as_str()).collect() + } +} + +impl Glob { + /// Creates a `Glob` for namespaces. + pub fn namespaces(glob: &str) -> Result { + 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 { + let pattern = PatternString::try_from(format!("refs/namespaces/{}", glob))?; + self.globs.push(pattern); + Ok(self) + } +} + +impl Glob { + /// Creates a `Glob` for local tags. + pub fn tags(glob: &str) -> Result { + 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 { + let pattern = PatternString::try_from(format!("refs/tags/{}", glob))?; + self.globs.push(pattern); + Ok(self) + } +} + +impl Glob { + /// Creates a `Glob` for local branches. + pub fn heads(glob: &str) -> Result { + 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 { + 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 { + 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 { + let pattern = PatternString::try_from(format!("refs/remotes/{}", glob))?; + self.globs.push(pattern); + Ok(self) + } +} diff --git a/radicle-surf/src/vcs/git/reference.rs b/radicle-surf/src/vcs/git/reference.rs deleted file mode 100644 index 60d1a53..0000000 --- a/radicle-surf/src/vcs/git/reference.rs +++ /dev/null @@ -1,191 +0,0 @@ -// This file is part of radicle-surf -// -// -// Copyright (C) 2019-2020 The Radicle Team -// -// 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 . - -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 From for Rev -where - R: Into, -{ - fn from(other: R) -> Self { - Self::Ref(other.into()) - } -} - -impl From 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, - }, -} - -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>; - - 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 { - bytes::complete::tag(HEADS)(s).map(|(name, _)| Ref::LocalBranch { - name: BranchName::new(name), - }) - } - - pub fn remote(s: &str) -> Result { - 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 { - bytes::complete::tag(TAGS)(s).map(|(name, _)| Ref::Tag { - name: TagName::new(name), - }) - } - - pub fn namespace(s: &str) -> Result { - 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>> { - 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 { - parser::parse(reference).map_err(|_| ParseError::MalformedRef(reference.to_owned())) - } -} diff --git a/radicle-surf/src/vcs/git/reference/glob.rs b/radicle-surf/src/vcs/git/reference/glob.rs deleted file mode 100644 index 9b85508..0000000 --- a/radicle-surf/src/vcs/git/reference/glob.rs +++ /dev/null @@ -1,183 +0,0 @@ -// This file is part of radicle-surf -// -// -// Copyright (C) 2019-2020 The Radicle Team -// -// 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 . - -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, - }, - /// 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, - }, - /// refs/namespaces/** - Namespace, -} - -/// Iterator chaining multiple [`git2::References`] -#[must_use = "iterators are lazy and do nothing unless consumed"] -pub struct References<'a> { - inner: Vec>, -} - -impl<'a> References<'a> { - pub fn iter(self) -> impl Iterator, 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, 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, - repo: &RepositoryRef<'a>, - ) -> Result, 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/**"), - } - } -} diff --git a/radicle-surf/src/vcs/git/repo.rs b/radicle-surf/src/vcs/git/repo.rs index 43d7351..16681e8 100644 --- a/radicle-surf/src/vcs/git/repo.rs +++ b/radicle-surf/src/vcs/git/repo.rs @@ -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>, + current: usize, +} + +impl<'a> Iterator for Tags<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + 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>, + current: usize, +} + +impl<'a> Iterator for Branches<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + 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, +} + +impl Iterator for Namespaces { + type Item = Namespace; + fn next(&mut self) -> Option { + self.namespaces.next() + } +} + impl<'a> RepositoryRef<'a> { /// What is the current namespace we're browsing in. pub fn which_namespace(&self) -> Result, 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, 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) -> Result { + 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, 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) -> Result { + 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, Error> { - let namespaces: Result, 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) -> Result { + 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::, 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, Error> { + pub fn last_commit( + &self, + path: file_system::Path, + rev: C, + ) -> Result, 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(&self, commit: C) -> Result { - 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, Error> { - let mut branches = self - .list_branches(filter)? - .into_iter() - .map(|b| b.name) - .collect::>(); - + pub fn branch_names(&self, filter: &Glob) -> Result, Error> { + let branches: Result, 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, Error> { - let tag_names = self.list_tags(RefScope::Local)?; - let mut tags: Vec = 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::, 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 { + 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 { 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, 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) -> Result, 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) } diff --git a/radicle-surf/src/vcs/git/tag.rs b/radicle-surf/src/vcs/git/tag.rs index fe56275..daab1a3 100644 --- a/radicle-surf/src/vcs/git/tag.rs +++ b/radicle-surf/src/vcs/git/tag.rs @@ -15,14 +15,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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 { 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 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 { + 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> for Tag { - type Error = str::Utf8Error; + type Error = Error; fn try_from(tag: git2::Tag) -> Result { let id = tag.id().into(); @@ -152,7 +154,7 @@ impl<'repo> TryFrom> 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) }; diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index b48fbb0..d8883c4 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -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 = vec![Branch::local("feature/#1194")]; - let mut branches = repo.list_branches(RefScope::Local)?; + let mut branches = repo + .branches(&Glob::heads("*")?)? + .collect::, Error>>()?; branches.sort(); assert_eq!(expected_branches, branches); let expected_branches: Vec = 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::, 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 = vec![Branch::local("banana"), Branch::local("master")]; - let mut branches = repo.list_branches(RefScope::Local)?; + let mut branches = repo + .branches(&Glob::heads("*")?)? + .collect::, 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::, 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 = vec![Branch::local("master")]; - let mut branches = repo.list_branches(RefScope::All)?; + let mut branches = repo + .branches(&Glob::heads("*")?.and_remotes("*")?)? + .collect::, 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 = 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::, 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::, 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();