From 42a8855dcd13bec5f7b949f2e163ba331b470fe7 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 10 Nov 2022 10:11:49 +0000 Subject: [PATCH 1/4] radicle-surf: remove deny pragma This pragma gets in the way more than it solves anything. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index 34dcbbe..d9760fd 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -15,8 +15,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#![deny(missing_docs, unused_import_braces, unused_qualifications, warnings)] - //! Welcome to `radicle-surf`! //! //! `radicle-surf` is a system to describe a file-system in a VCS world. From 1649ff3e71f4439c3ef9bb81e1d27df4ec86b28f Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 10 Nov 2022 10:15:19 +0000 Subject: [PATCH 2/4] radicle-surf: remove RefScope RefScope is no longer used, so it's removed. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/src/vcs/git.rs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/radicle-surf/src/vcs/git.rs b/radicle-surf/src/vcs/git.rs index ac8ed28..c5a73c0 100644 --- a/radicle-surf/src/vcs/git.rs +++ b/radicle-surf/src/vcs/git.rs @@ -115,41 +115,14 @@ impl From for Signature { } } -/// Determines whether to look for local or remote references or both. -pub enum RefScope { - /// List all branches by default. - All, - /// List only local branches. - Local, - /// List only remote branches. - Remote { - /// Name of the remote. If `None`, then get the reference from all - /// remotes. - name: Option, - }, -} - -/// Turn an `Option

` into a [`RefScope`]. If the `P` is present then -/// this is set as the remote of the `RefScope`. Otherwise, it's local -/// branch. -impl

From> for RefScope -where - P: ToString, -{ - fn from(peer_id: Option

) -> Self { - peer_id.map_or(RefScope::Local, |peer_id| RefScope::Remote { - // We qualify the remotes as the PeerId + heads, otherwise we would grab the tags too. - name: Some(format!("{}/heads", peer_id.to_string())), - }) - } -} - /// Supports various ways to specify a revision used in Git. pub trait Revision { /// Returns the object id of this revision in `repo`. fn object_id(&self, repo: &RepositoryRef) -> Result; } + + impl Revision for Oid { fn object_id(&self, _repo: &RepositoryRef) -> Result { Ok(*self) From c6c8fe316490d1f2f95332b00c7fa20f8e0fc80d Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 10 Nov 2022 10:21:37 +0000 Subject: [PATCH 3/4] radicle-surf: move vcs::git to git Other VCS systems are no longer expected to be supported. Remove the git hierarchy up to the top-level and remove the vcs hierarchy. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/benches/last_commit.rs | 2 +- radicle-surf/examples/diff.rs | 2 +- radicle-surf/src/commit.rs | 3 +- radicle-surf/src/diff.rs | 2 +- radicle-surf/src/file_system/directory.rs | 2 +- radicle-surf/src/{vcs => }/git.rs | 2 - radicle-surf/src/{vcs => }/git/branch.rs | 2 +- radicle-surf/src/{vcs => }/git/commit.rs | 2 +- radicle-surf/src/{vcs => }/git/error.rs | 6 +-- radicle-surf/src/{vcs => }/git/ext.rs | 0 radicle-surf/src/{vcs => }/git/glob.rs | 2 +- radicle-surf/src/{vcs => }/git/history.rs | 2 +- radicle-surf/src/{vcs => }/git/namespace.rs | 2 +- radicle-surf/src/{vcs => }/git/repo.rs | 2 +- radicle-surf/src/{vcs => }/git/stats.rs | 0 radicle-surf/src/{vcs => }/git/tag.rs | 2 +- radicle-surf/src/lib.rs | 4 +- radicle-surf/src/revision.rs | 5 +-- radicle-surf/src/vcs.rs | 20 --------- radicle-surf/t/src/git.rs | 47 +-------------------- 20 files changed, 18 insertions(+), 91 deletions(-) rename radicle-surf/src/{vcs => }/git.rs (99%) rename radicle-surf/src/{vcs => }/git/branch.rs (99%) rename radicle-surf/src/{vcs => }/git/commit.rs (99%) rename radicle-surf/src/{vcs => }/git/error.rs (97%) rename radicle-surf/src/{vcs => }/git/ext.rs (100%) rename radicle-surf/src/{vcs => }/git/glob.rs (99%) rename radicle-surf/src/{vcs => }/git/history.rs (98%) rename radicle-surf/src/{vcs => }/git/namespace.rs (98%) rename radicle-surf/src/{vcs => }/git/repo.rs (99%) rename radicle-surf/src/{vcs => }/git/stats.rs (100%) rename radicle-surf/src/{vcs => }/git/tag.rs (99%) delete mode 100644 radicle-surf/src/vcs.rs diff --git a/radicle-surf/benches/last_commit.rs b/radicle-surf/benches/last_commit.rs index c419664..409cf11 100644 --- a/radicle-surf/benches/last_commit.rs +++ b/radicle-surf/benches/last_commit.rs @@ -18,7 +18,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use radicle_surf::{ file_system::{unsound, Path}, - vcs::git::{Branch, Repository}, + git::{Branch, Repository}, }; fn last_commit_comparison(c: &mut Criterion) { diff --git a/radicle-surf/examples/diff.rs b/radicle-surf/examples/diff.rs index 79fd292..9455880 100644 --- a/radicle-surf/examples/diff.rs +++ b/radicle-surf/examples/diff.rs @@ -20,7 +20,7 @@ extern crate radicle_surf; use std::{env::Args, str::FromStr, time::Instant}; use radicle_git_ext::Oid; -use radicle_surf::{diff::Diff, vcs::git}; +use radicle_surf::{diff::Diff, git}; fn main() { let options = get_options_or_exit(); diff --git a/radicle-surf/src/commit.rs b/radicle-surf/src/commit.rs index cc41a90..41436b3 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/commit.rs @@ -26,10 +26,9 @@ use serde::{ use crate::{ diff, file_system, - git::Glob, + git::{self, BranchName, Glob, RepositoryRef}, person::Person, revision::Revision, - vcs::git::{self, BranchName, RepositoryRef}, }; use radicle_git_ext::Oid; diff --git a/radicle-surf/src/diff.rs b/radicle-surf/src/diff.rs index 1f7a402..31fe1df 100644 --- a/radicle-surf/src/diff.rs +++ b/radicle-surf/src/diff.rs @@ -24,7 +24,7 @@ use serde::{ser, Serialize, Serializer}; use crate::{ file_system::{Directory, Path}, - vcs::git::{Error, RepositoryRef}, + git::{Error, RepositoryRef}, }; pub mod git; diff --git a/radicle-surf/src/file_system/directory.rs b/radicle-surf/src/file_system/directory.rs index 042a439..e1fe45e 100644 --- a/radicle-surf/src/file_system/directory.rs +++ b/radicle-surf/src/file_system/directory.rs @@ -22,7 +22,7 @@ use crate::{ file_system::{error::LabelError, path::*, Error}, - vcs::git::{self, RepositoryRef, Revision}, + git::{self, RepositoryRef, Revision}, }; use git2::Blob; use radicle_git_ext::Oid; diff --git a/radicle-surf/src/vcs/git.rs b/radicle-surf/src/git.rs similarity index 99% rename from radicle-surf/src/vcs/git.rs rename to radicle-surf/src/git.rs index c5a73c0..1979f23 100644 --- a/radicle-surf/src/vcs/git.rs +++ b/radicle-surf/src/git.rs @@ -121,8 +121,6 @@ pub trait Revision { fn object_id(&self, repo: &RepositoryRef) -> Result; } - - impl Revision for Oid { fn object_id(&self, _repo: &RepositoryRef) -> Result { Ok(*self) diff --git a/radicle-surf/src/vcs/git/branch.rs b/radicle-surf/src/git/branch.rs similarity index 99% rename from radicle-surf/src/vcs/git/branch.rs rename to radicle-surf/src/git/branch.rs index 881517a..6bfac0d 100644 --- a/radicle-surf/src/vcs/git/branch.rs +++ b/radicle-surf/src/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}; +use crate::git::{self, error::Error, ext}; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, convert::TryFrom, fmt, str}; diff --git a/radicle-surf/src/vcs/git/commit.rs b/radicle-surf/src/git/commit.rs similarity index 99% rename from radicle-surf/src/vcs/git/commit.rs rename to radicle-surf/src/git/commit.rs index 9f04068..3164ff1 100644 --- a/radicle-surf/src/vcs/git/commit.rs +++ b/radicle-surf/src/git/commit.rs @@ -17,7 +17,7 @@ use crate::{ file_system::{self, directory}, - vcs::git::{error::Error, Branch, RepositoryRef, Tag, TagName}, + git::{error::Error, Branch, RepositoryRef, Tag, TagName}, }; use radicle_git_ext::Oid; use std::{convert::TryFrom, str}; diff --git a/radicle-surf/src/vcs/git/error.rs b/radicle-surf/src/git/error.rs similarity index 97% rename from radicle-surf/src/vcs/git/error.rs rename to radicle-surf/src/git/error.rs index a1088ed..31f855d 100644 --- a/radicle-surf/src/vcs/git/error.rs +++ b/radicle-surf/src/git/error.rs @@ -16,17 +16,17 @@ // along with this program. If not, see . //! Collection of errors and helper instances that can occur when performing -//! operations from [`crate::vcs::git`]. +//! operations from [`crate::git`]. use crate::{ diff, file_system, - vcs::git::{BranchName, Namespace, TagName}, + git::{BranchName, Namespace, TagName}, }; use std::str; use thiserror::Error; -/// Enumeration of errors that can occur in operations from [`crate::vcs::git`]. +/// Enumeration of errors that can occur in operations from [`crate::git`]. #[derive(Debug, PartialEq, Error)] #[non_exhaustive] pub enum Error { diff --git a/radicle-surf/src/vcs/git/ext.rs b/radicle-surf/src/git/ext.rs similarity index 100% rename from radicle-surf/src/vcs/git/ext.rs rename to radicle-surf/src/git/ext.rs diff --git a/radicle-surf/src/vcs/git/glob.rs b/radicle-surf/src/git/glob.rs similarity index 99% rename from radicle-surf/src/vcs/git/glob.rs rename to radicle-surf/src/git/glob.rs index 067a121..4ccf3a7 100644 --- a/radicle-surf/src/vcs/git/glob.rs +++ b/radicle-surf/src/git/glob.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::Error; +use crate::git::Error; use git_ref_format::refspec::PatternString; use std::{convert::TryFrom, marker::PhantomData, str}; diff --git a/radicle-surf/src/vcs/git/history.rs b/radicle-surf/src/git/history.rs similarity index 98% rename from radicle-surf/src/vcs/git/history.rs rename to radicle-surf/src/git/history.rs index c90696a..d3dde30 100644 --- a/radicle-surf/src/vcs/git/history.rs +++ b/radicle-surf/src/git/history.rs @@ -17,7 +17,7 @@ use crate::{ file_system, - vcs::git::{commit::ToCommit, Commit, Error, RepositoryRef}, + git::{commit::ToCommit, Commit, Error, RepositoryRef}, }; use std::convert::TryFrom; diff --git a/radicle-surf/src/vcs/git/namespace.rs b/radicle-surf/src/git/namespace.rs similarity index 98% rename from radicle-surf/src/vcs/git/namespace.rs rename to radicle-surf/src/git/namespace.rs index 285efe7..80bb92d 100644 --- a/radicle-surf/src/vcs/git/namespace.rs +++ b/radicle-surf/src/git/namespace.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::error::Error; +use crate::git::error::Error; use nonempty::NonEmpty; pub use radicle_git_ext::Oid; use std::{convert::TryFrom, fmt, str}; diff --git a/radicle-surf/src/vcs/git/repo.rs b/radicle-surf/src/git/repo.rs similarity index 99% rename from radicle-surf/src/vcs/git/repo.rs rename to radicle-surf/src/git/repo.rs index 67c3b85..6d4ab51 100644 --- a/radicle-surf/src/vcs/git/repo.rs +++ b/radicle-surf/src/git/repo.rs @@ -19,7 +19,7 @@ use crate::{ diff::*, file_system, file_system::{directory, DirectoryEntry, Label}, - vcs::git::{ + git::{ error::*, Branch, BranchName, diff --git a/radicle-surf/src/vcs/git/stats.rs b/radicle-surf/src/git/stats.rs similarity index 100% rename from radicle-surf/src/vcs/git/stats.rs rename to radicle-surf/src/git/stats.rs diff --git a/radicle-surf/src/vcs/git/tag.rs b/radicle-surf/src/git/tag.rs similarity index 99% rename from radicle-surf/src/vcs/git/tag.rs rename to radicle-surf/src/git/tag.rs index daab1a3..1d23957 100644 --- a/radicle-surf/src/vcs/git/tag.rs +++ b/radicle-surf/src/git/tag.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, Author}; +use crate::git::{self, error::Error, Author}; use git_ref_format::RefString; use radicle_git_ext::Oid; use std::{convert::TryFrom, fmt, str}; diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index d9760fd..dfa3db9 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -82,7 +82,7 @@ //! ``` pub mod diff; pub mod file_system; -pub mod vcs; +pub mod git; pub mod commit; pub use commit::{commit, commits, Commit}; @@ -103,5 +103,3 @@ pub use syntax::SYNTAX_SET; // Private modules mod nonempty; - -pub use crate::vcs::git; diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/revision.rs index bbea2ed..8805983 100644 --- a/radicle-surf/src/revision.rs +++ b/radicle-surf/src/revision.rs @@ -24,10 +24,7 @@ use serde::{Deserialize, Serialize}; use radicle_git_ext::Oid; -use crate::{ - git::{commit::ToCommit, BranchName, Glob, RepositoryRef}, - vcs::git::{self, error::Error, TagName}, -}; +use crate::git::{self, commit::ToCommit, error::Error, BranchName, Glob, RepositoryRef, TagName}; /// Types of a peer. pub enum Category { diff --git a/radicle-surf/src/vcs.rs b/radicle-surf/src/vcs.rs deleted file mode 100644 index d8fa0f5..0000000 --- a/radicle-surf/src/vcs.rs +++ /dev/null @@ -1,20 +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 . - -//! A model of a general VCS. - -pub mod git; diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index 46d3940..9d4ec9e 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -668,55 +668,10 @@ mod branch { } } -#[cfg(test)] -mod ext { - use radicle_surf::vcs::git::ext::*; - - #[test] - fn test_try_extract_refname() { - assert_eq!(try_extract_refname("refs/heads/dev"), Ok("dev".to_string())); - - assert_eq!( - try_extract_refname("refs/heads/master"), - Ok("master".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/remotes/banana/pineapple"), - Ok("banana/pineapple".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/remotes/origin/master"), - Ok("origin/master".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/namespaces/golden/refs/heads/banana"), - Ok("banana".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/namespaces/golden/refs/tags/v0.1.0"), - Ok("v0.1.0".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/namespaces/golden/refs/namespaces/silver/refs/heads/master"), - Ok("master".to_string()) - ); - - assert_eq!( - try_extract_refname("refs/namespaces/golden/refs/remotes/kickflip/heads/heelflip"), - Ok("kickflip/heelflip".to_string()) - ); - } -} - #[cfg(test)] mod reference { use super::*; - use radicle_surf::vcs::git::{Glob, Tag}; + use radicle_surf::git::{Glob, Tag}; #[test] fn test_branches() { From 9b5ea4352f5be1846bf3341b40155520fde008cc Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Thu, 10 Nov 2022 10:16:49 +0000 Subject: [PATCH 4/4] radicle-surf: rework to use git-ref-format The aim of this commit is to better represent the reference names using git-ref-format. The first places to change were the Branch and Tag types. For Branch, the previous types were simply wrappers around the names. They stay this way, using RefString instead. For Tag, the `name` field is replaced with RefString. Note: The parsing logic becomes easier here by utilising the Namespaced and Qualified types. The consequences of these changes meant that areas that used BranchName or TagName are changed to use RefString. Signed-off-by: Fintan Halpenny X-Clacks-Overhead: GNU Terry Pratchett --- radicle-surf/Cargo.toml | 6 +- radicle-surf/benches/last_commit.rs | 3 +- radicle-surf/src/commit.rs | 15 +- radicle-surf/src/git.rs | 38 ++- radicle-surf/src/git/branch.rs | 448 ++++++++++++++++++---------- radicle-surf/src/git/commit.rs | 11 +- radicle-surf/src/git/error.rs | 25 +- radicle-surf/src/git/ext.rs | 68 ----- radicle-surf/src/git/repo.rs | 119 ++------ radicle-surf/src/git/repo/iter.rs | 122 ++++++++ radicle-surf/src/git/tag.rs | 194 +++++------- radicle-surf/src/lib.rs | 5 + radicle-surf/src/object/blob.rs | 14 +- radicle-surf/src/object/tree.rs | 14 +- radicle-surf/src/revision.rs | 49 ++- radicle-surf/t/Cargo.toml | 10 +- radicle-surf/t/src/file_system.rs | 5 +- radicle-surf/t/src/git.rs | 162 ++++++---- 18 files changed, 708 insertions(+), 600 deletions(-) delete mode 100644 radicle-surf/src/git/ext.rs create mode 100644 radicle-surf/src/git/repo/iter.rs diff --git a/radicle-surf/Cargo.toml b/radicle-surf/Cargo.toml index 242bdef..498dfa5 100644 --- a/radicle-surf/Cargo.toml +++ b/radicle-surf/Cargo.toml @@ -4,7 +4,7 @@ description = "A code surfing library for VCS file systems" readme = "README.md" version = "0.8.0" authors = ["The Radicle Team "] -edition = "2018" +edition = "2021" homepage = "https://github.com/radicle-dev/radicle-surf" repository = "https://github.com/radicle-dev/radicle-surf" license = "GPL-3.0-or-later" @@ -44,6 +44,7 @@ features = ["vendored-libgit2"] [dependencies.git-ref-format] version = "0.1.0" path = "../git-ref-format" +features = ["macro", "serde"] [dependencies.radicle-git-ext] version = "0.2.0" @@ -56,9 +57,6 @@ pretty_assertions = "1.3.0" proptest = "0.9" serde_json = "1" -[dev-dependencies.test-helpers] -path = "../test/test-helpers" - [build-dependencies] anyhow = "1.0" flate2 = "1" diff --git a/radicle-surf/benches/last_commit.rs b/radicle-surf/benches/last_commit.rs index 409cf11..2b35100 100644 --- a/radicle-surf/benches/last_commit.rs +++ b/radicle-surf/benches/last_commit.rs @@ -16,6 +16,7 @@ // along with this program. If not, see . use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use git_ref_format::refname; use radicle_surf::{ file_system::{unsound, Path}, git::{Branch, Repository}, @@ -24,7 +25,7 @@ use radicle_surf::{ fn last_commit_comparison(c: &mut Criterion) { let repo = Repository::open("./data/git-platinum") .expect("Could not retrieve ./data/git-platinum as git repository"); - let rev = Branch::local("master"); + let rev = Branch::local(refname!("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 41436b3..68af3d7 100644 --- a/radicle-surf/src/commit.rs +++ b/radicle-surf/src/commit.rs @@ -17,6 +17,7 @@ //! Represents a commit. +use git_ref_format::RefString; #[cfg(feature = "serialize")] use serde::{ ser::{SerializeStruct as _, Serializer}, @@ -26,7 +27,7 @@ use serde::{ use crate::{ diff, file_system, - git::{self, BranchName, Glob, RepositoryRef}, + git::{self, Glob, RepositoryRef}, person::Person, revision::Revision, }; @@ -54,7 +55,7 @@ pub struct Commit { /// The changeset introduced by this commit. pub diff: diff::Diff, /// The list of branches this commit belongs to. - pub branches: Vec, + pub branches: Vec, } /// Representation of a code commit. @@ -195,7 +196,7 @@ pub fn commit(repo: &RepositoryRef, rev: R) -> Result Result { /// /// Will return [`Error`] if the project doesn't exist or the surf interaction /// fails. -pub fn commits

( - repo: &RepositoryRef, - maybe_revision: Option>, -) -> Result -where - P: ToString, -{ +pub fn commits(repo: &RepositoryRef, maybe_revision: Option) -> Result { let rev = match maybe_revision { Some(revision) => revision, None => Revision::Sha { diff --git a/radicle-surf/src/git.rs b/radicle-surf/src/git.rs index 1979f23..f3482de 100644 --- a/radicle-surf/src/git.rs +++ b/radicle-surf/src/git.rs @@ -67,6 +67,7 @@ use std::str::FromStr; // Re-export git2 as sub-module pub use git2::{self, Error as Git2Error, Time}; +use git_ref_format::{name::Components, Component, Qualified, RefString}; pub use radicle_git_ext::Oid; mod repo; @@ -81,15 +82,13 @@ pub use history::History; pub mod error; pub use error::Error; -pub mod ext; - /// Provides the data for talking about branches. pub mod branch; -pub use branch::{Branch, BranchName, BranchType}; +pub use branch::{Branch, Local, Remote}; /// Provides the data for talking about tags. pub mod tag; -pub use tag::{Tag, TagName}; +pub use tag::Tag; /// Provides the data for talking about commits. pub mod commit; @@ -121,6 +120,30 @@ pub trait Revision { fn object_id(&self, repo: &RepositoryRef) -> Result; } +impl Revision for RefString { + fn object_id(&self, repo: &RepositoryRef) -> Result { + repo.refname_to_oid(self.as_str()) + } +} + +impl Revision for &RefString { + fn object_id(&self, repo: &RepositoryRef) -> Result { + repo.refname_to_oid(self.as_str()) + } +} + +impl Revision for Qualified<'_> { + fn object_id(&self, repo: &RepositoryRef) -> Result { + repo.refname_to_oid(self.as_str()) + } +} + +impl Revision for &Qualified<'_> { + fn object_id(&self, repo: &RepositoryRef) -> Result { + repo.refname_to_oid(self.as_str()) + } +} + impl Revision for Oid { fn object_id(&self, _repo: &RepositoryRef) -> Result { Ok(*self) @@ -147,9 +170,6 @@ impl Revision for &Tag { } } -impl Revision for &TagName { - fn object_id(&self, repo: &RepositoryRef) -> Result { - let refname = repo.namespaced_refname(&self.refname())?; - Ok(repo.repo_ref.refname_to_id(&refname).map(Oid::from)?) - } +pub(crate) fn refstr_join<'a>(c: Component<'a>, cs: Components<'a>) -> RefString { + std::iter::once(c).chain(cs).collect::() } diff --git a/radicle-surf/src/git/branch.rs b/radicle-surf/src/git/branch.rs index 6bfac0d..988f667 100644 --- a/radicle-surf/src/git/branch.rs +++ b/radicle-surf/src/git/branch.rs @@ -1,186 +1,306 @@ -// 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::{ + convert::TryFrom, + str::{self, FromStr}, +}; -use crate::git::{self, error::Error, ext}; -#[cfg(feature = "serialize")] -use serde::{Deserialize, Serialize}; -use std::{cmp::Ordering, convert::TryFrom, fmt, str}; +use crate::git::refstr_join; +use git_ref_format::{component, lit, Component, Qualified, RefStr, RefString}; -/// The branch type we want to filter on. -#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub enum BranchType { - /// Local branches that are under `refs/heads/*` - Local, - /// Remote branches that are under `refs/remotes//*` if the name is - /// provided, otherwise `refs/remotes/**/*`. - Remote { - /// Name of the remote. - name: Option, - }, -} - -impl From for git2::BranchType { - fn from(other: BranchType) -> Self { - match other { - BranchType::Local => git2::BranchType::Local, - BranchType::Remote { .. } => git2::BranchType::Remote, - } - } -} - -impl From for BranchType { - fn from(other: git2::BranchType) -> Self { - match other { - git2::BranchType::Local => BranchType::Local, - git2::BranchType::Remote => BranchType::Remote { name: None }, - } - } -} - -/// A newtype wrapper over `String` to separate out the fact that a caller wants -/// to fetch a branch. -#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BranchName(String); - -impl fmt::Display for BranchName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl TryFrom<&[u8]> for BranchName { - type Error = Error; - - fn try_from(name: &[u8]) -> Result { - let name = str::from_utf8(name)?; - let short_name = match git::ext::try_extract_refname(name) { - Ok(stripped) => stripped, - Err(original) => original, - }; - Ok(Self(short_name)) - } -} - -impl BranchName { - /// Create a new `BranchName`. - pub fn new(name: &str) -> Self { - Self(name.into()) - } - - /// Access the string value of the `BranchName`. - pub fn name(&self) -> &str { - &self.0 - } -} - -/// The static information of a `git2::Branch`. +/// A `Branch` represents any git branch. This can either be a reference +/// that is under the `refs/heads` or `refs/remotes` namespace. /// -/// **Note**: The `PartialOrd` and `Ord` implementations compare on `BranchName` -/// only. -#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Branch { - /// Name identifier of the `Branch`. - pub name: BranchName, - /// Whether the `Branch` is `Remote` or `Local`. - pub locality: BranchType, -} - -impl PartialOrd for Branch { - fn partial_cmp(&self, other: &Branch) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Branch { - fn cmp(&self, other: &Branch) -> Ordering { - self.name.cmp(&other.name) - } +/// Note that if a `Branch` is created from a [`git2::Reference`] then +/// any `refs/namespaces` will be stripped. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Branch { + Local(Local), + Remote(Remote), } impl Branch { - /// Helper to create a remote `Branch` with a name - pub fn remote(name: &str, remote: &str) -> Self { - Self { - name: BranchName(name.to_string()), - locality: BranchType::Remote { - name: Some(remote.to_string()), - }, - } + /// Construct a [`Local`] branch. + pub fn local(name: R) -> Self + where + R: AsRef, + { + Self::Local(Local::new(name)) } - /// Helper to create a remote `Branch` with a name - pub fn local(name: &str) -> Self { - Self { - name: BranchName(name.to_string()), - locality: BranchType::Local, - } + /// Construct a [`Remote`] branch. + /// The `remote` is the remote name of the reference name while + /// the `name` is the suffix, i.e. `refs/remotes//`. + pub fn remote(remote: Component<'_>, name: R) -> Self + where + R: AsRef, + { + Self::Remote(Remote::new(remote, name)) } - /// Get the name of the `Branch`. - pub fn refname(&self) -> String { - 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.to_string(), - Some(remote_name) => format!("refs/remotes/{}/{}", remote_name, branch_name), - }, + pub fn refname(&self) -> Qualified { + match self { + Branch::Local(local) => local.refname(), + Branch::Remote(remote) => remote.refname(), } } } -impl<'repo> TryFrom> for Branch { - type Error = Error; +impl TryFrom<&git2::Reference<'_>> for Branch { + type Error = error::Branch; - fn try_from(reference: git2::Reference) -> Result { - let is_remote = ext::is_remote(&reference); - let is_tag = reference.is_tag(); - let is_note = reference.is_note(); - let name = BranchName::try_from(reference.name_bytes())?; + fn try_from(reference: &git2::Reference<'_>) -> Result { + let name = str::from_utf8(reference.name_bytes())?; + Self::from_str(name) + } +} - // Best effort to not return tags or notes. Assuming everything after that is a - // branch. - if is_tag || is_note { - return Err(Error::NotBranch(name)); - } +impl TryFrom<&str> for Branch { + type Error = error::Branch; - if is_remote { - let mut split = name.0.splitn(2, '/'); - let remote_name = split - .next() - .ok_or_else(|| Error::ParseRemoteBranch(name.clone()))?; - let name = split - .next() - .ok_or_else(|| Error::ParseRemoteBranch(name.clone()))?; + fn try_from(name: &str) -> Result { + Self::from_str(name) + } +} - Ok(Self { - name: BranchName(name.to_string()), - locality: BranchType::Remote { - name: Some(remote_name.to_string()), - }, - }) +impl FromStr for Branch { + type Err = error::Branch; + + fn from_str(name: &str) -> Result { + let name = RefStr::try_from_str(name)?; + let name = match name.to_namespaced() { + None => name + .qualified() + .ok_or_else(|| error::Branch::NotQualified(name.to_string()))?, + Some(name) => name.strip_namespace_recursive(), + }; + + let (_ref, category, c, cs) = name.non_empty_components(); + + if category == component::HEADS { + Ok(Self::Local(Local::new(refstr_join(c, cs)))) + } else if category == component::REMOTES { + Ok(Self::Remote(Remote::new(c, cs.collect::()))) } else { - Ok(Self { - name, - locality: BranchType::Local, - }) + Err(error::Branch::NotBranch(name.into())) } } } + +/// A `Local` represents a local branch, i.e. it is a reference under +/// `refs/heads`. +/// +/// Note that if a `Local` is created from a [`git2::Reference`] then +/// any `refs/namespaces` will be stripped. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Local { + name: RefString, +} + +impl Local { + /// Construct a new `Local` with the given `name`. + /// + /// If the name is qualified with `refs/heads`, this will be + /// shortened to the suffix. To get the `Qualified` name again, + /// use [`Local::refname`]. + pub fn new(name: R) -> Self + where + R: AsRef, + { + match name.as_ref().qualified() { + None => Self { + name: name.as_ref().to_ref_string(), + }, + Some(qualified) => { + let (_refs, heads, c, cs) = qualified.non_empty_components(); + if heads == component::HEADS { + Self { + name: refstr_join(c, cs), + } + } else { + Self { + name: name.as_ref().to_ref_string(), + } + } + }, + } + } + + /// Return the fully qualified `Local` refname, + /// e.g. `refs/heads/fix/ref-format`. + pub fn refname(&self) -> Qualified { + lit::refs_heads(&self.name).into() + } +} + +impl TryFrom<&git2::Reference<'_>> for Local { + type Error = error::Local; + + fn try_from(reference: &git2::Reference) -> Result { + let name = str::from_utf8(reference.name_bytes())?; + Self::from_str(name) + } +} + +impl TryFrom<&str> for Local { + type Error = error::Local; + + fn try_from(name: &str) -> Result { + Self::from_str(name) + } +} + +impl FromStr for Local { + type Err = error::Local; + + fn from_str(name: &str) -> Result { + let name = RefStr::try_from_str(name)?; + let name = match name.to_namespaced() { + None => name + .qualified() + .ok_or_else(|| error::Local::NotQualified(name.to_string()))?, + Some(name) => name.strip_namespace_recursive(), + }; + + let (_ref, heads, c, cs) = name.non_empty_components(); + if heads == component::HEADS { + Ok(Self::new(refstr_join(c, cs))) + } else { + Err(error::Local::NotHeads(name.into())) + } + } +} + +/// A `Remote` represents a remote branch, i.e. it is a reference under +/// `refs/remotes`. +/// +/// Note that if a `Remote` is created from a [`git2::Reference`] then +/// any `refs/namespaces` will be stripped. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Remote { + remote: RefString, + name: RefString, +} + +impl Remote { + /// Construct a new `Remote` with the given `name` and `remote`. + /// + /// ## Note + /// `name` is expected to be in short form, i.e. not begin with + /// `refs`. + /// + /// If you are creating a `Remote` with a name that begins with + /// `refs/remotes`, use [`Remote::from_refs_remotes`] instead. + /// + /// To get the `Qualified` name, use [`Remote::refname`]. + pub fn new(remote: Component, name: R) -> Self + where + R: AsRef, + { + Self { + name: name.as_ref().to_ref_string(), + remote: remote.to_ref_string(), + } + } + + /// Parse the `name` from the form `refs/remotes//`. + /// + /// If the `name` is not of this form, then `None` is returned. + pub fn from_refs_remotes(name: R) -> Option + where + R: AsRef, + { + let qualified = name.as_ref().qualified()?; + let (_refs, remotes, remote, cs) = qualified.non_empty_components(); + (remotes == component::REMOTES).then_some(Self { + name: cs.collect(), + remote: remote.to_ref_string(), + }) + } + + /// Give back the fully qualified `Remote` refname, + /// e.g. `refs/remotes/origin/fix/ref-format`. + pub fn refname(&self) -> Qualified { + lit::refs_remotes(self.remote.join(&self.name)).into() + } +} + +impl TryFrom<&git2::Reference<'_>> for Remote { + type Error = error::Remote; + + fn try_from(reference: &git2::Reference) -> Result { + let name = str::from_utf8(reference.name_bytes())?; + Self::from_str(name) + } +} + +impl TryFrom<&str> for Remote { + type Error = error::Remote; + + fn try_from(name: &str) -> Result { + Self::from_str(name) + } +} + +impl FromStr for Remote { + type Err = error::Remote; + + fn from_str(name: &str) -> Result { + let name = RefStr::try_from_str(name)?; + let name = match name.to_namespaced() { + None => name + .qualified() + .ok_or_else(|| error::Remote::NotQualified(name.to_string()))?, + Some(name) => name.strip_namespace_recursive(), + }; + + let (_ref, remotes, remote, cs) = name.non_empty_components(); + if remotes == component::REMOTES { + Ok(Self::new(remote, cs.collect::())) + } else { + Err(error::Remote::NotRemotes(name.into())) + } + } +} + +pub mod error { + use git_ref_format::RefString; + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum Branch { + #[error("the refname '{0}' did not begin with 'refs/heads' or 'refs/remotes'")] + NotBranch(RefString), + #[error("the refname '{0}' did not begin with 'refs/heads' or 'refs/remotes'")] + NotQualified(String), + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] std::str::Utf8Error), + } + + #[derive(Debug, Error)] + pub enum Local { + #[error("the refname '{0}' did not begin with 'refs/heads'")] + NotHeads(RefString), + #[error("the refname '{0}' did not begin with 'refs/heads'")] + NotQualified(String), + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] std::str::Utf8Error), + } + + #[derive(Debug, Error)] + pub enum Remote { + #[error("the refname '{0}' did not begin with 'refs/remotes'")] + NotQualified(String), + #[error("the refname '{0}' did not begin with 'refs/remotes'")] + NotRemotes(RefString), + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] std::str::Utf8Error), + } +} diff --git a/radicle-surf/src/git/commit.rs b/radicle-surf/src/git/commit.rs index 3164ff1..0fdfd6c 100644 --- a/radicle-surf/src/git/commit.rs +++ b/radicle-surf/src/git/commit.rs @@ -17,8 +17,9 @@ use crate::{ file_system::{self, directory}, - git::{error::Error, Branch, RepositoryRef, Tag, TagName}, + git::{error::Error, Branch, RepositoryRef, Tag}, }; +use git_ref_format::{Qualified, RefString}; use radicle_git_ext::Oid; use std::{convert::TryFrom, str}; @@ -216,7 +217,13 @@ impl ToCommit for &Tag { } } -impl ToCommit for &TagName { +impl ToCommit for &RefString { + fn to_commit(self, repo: &RepositoryRef) -> Result { + repo.commit(self) + } +} + +impl ToCommit for &Qualified<'_> { fn to_commit(self, repo: &RepositoryRef) -> Result { repo.commit(self) } diff --git a/radicle-surf/src/git/error.rs b/radicle-surf/src/git/error.rs index 31f855d..565fcf1 100644 --- a/radicle-surf/src/git/error.rs +++ b/radicle-surf/src/git/error.rs @@ -18,31 +18,18 @@ //! Collection of errors and helper instances that can occur when performing //! operations from [`crate::git`]. -use crate::{ - diff, - file_system, - git::{BranchName, Namespace, TagName}, -}; +use crate::{diff, file_system, git, git::Namespace}; use std::str; use thiserror::Error; /// Enumeration of errors that can occur in operations from [`crate::git`]. -#[derive(Debug, PartialEq, Error)] +#[derive(Debug, Error)] #[non_exhaustive] pub enum Error { - /// The user tried to fetch a branch, but the name provided does not - /// exist as a branch. This could mean that the branch does not exist - /// or that a tag or commit was provided by accident. - #[error("provided branch name does not exist: {0}")] - NotBranch(BranchName), - /// We tried to convert a name into its remote and branch name parts. - #[error("could not parse '{0}' into a remote name and branch name")] - ParseRemoteBranch(BranchName), - /// The user tried to fetch a tag, but the name provided does not - /// exist as a tag. This could mean that the tag does not exist - /// or that a branch or commit was provided by accident. - #[error("provided tag name does not exist: {0}")] - NotTag(TagName), + #[error(transparent)] + BranchIter(#[from] git::repo::iter::error::Branch), + #[error(transparent)] + TagIter(#[from] git::repo::iter::error::Tag), /// A `revspec` was provided that could not be parsed into a branch, tag, or /// commit object. #[error("provided revspec '{rev}' could not be parsed into a git object")] diff --git a/radicle-surf/src/git/ext.rs b/radicle-surf/src/git/ext.rs deleted file mode 100644 index d065881..0000000 --- a/radicle-surf/src/git/ext.rs +++ /dev/null @@ -1,68 +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 . - -#![allow(missing_docs)] - -/// Try to strip any refs/namespaces, refs/heads, refs/remotes, and -/// refs/tags. If this fails we return the original string. -pub fn try_extract_refname(spec: &str) -> Result { - let re = regex::Regex::new(r"(refs/namespaces/.*?/)*refs/(remotes/(.*?)/)?(heads/|tags/)?(.*)") - .unwrap(); - - re.captures(spec) - .and_then(|c| { - let mut result = String::new(); - if let Some(remote) = c.get(3).map(|m| m.as_str()) { - result.push_str(remote); - result.push('/'); - } - result.push_str(c.get(5).map(|m| m.as_str())?); - Some(result) - }) - .ok_or_else(|| spec.to_string()) -} - -/// [`git2::Reference::is_tag`] just does a check for the prefix of `tags/`. -/// The issue with that is, as soon as we're in 'namespaces' ref that -/// is a tag it will say that it's not a tag. Instead we do a regex check on -/// `refs/tags/.*`. -pub fn is_tag(reference: &git2::Reference) -> bool { - let re = regex::Regex::new(r"refs/tags/.*").unwrap(); - // If we couldn't parse the name we say it's not a tag. - match reference.name() { - Some(name) => re.is_match(name), - None => false, - } -} - -pub fn is_branch(reference: &git2::Reference) -> bool { - let re = regex::Regex::new(r"refs/heads/.*|refs/remotes/.*/.*").unwrap(); - // If we couldn't parse the name we say it's not a branch. - match reference.name() { - Some(name) => re.is_match(name), - None => false, - } -} - -pub fn is_remote(reference: &git2::Reference) -> bool { - let re = regex::Regex::new(r"refs/remotes/.*/.*").unwrap(); - // If we couldn't parse the name we say it's not a remote branch. - match reference.name() { - Some(name) => re.is_match(name), - None => false, - } -} diff --git a/radicle-surf/src/git/repo.rs b/radicle-surf/src/git/repo.rs index 6d4ab51..5457265 100644 --- a/radicle-surf/src/git/repo.rs +++ b/radicle-surf/src/git/repo.rs @@ -18,26 +18,16 @@ use crate::{ diff::*, file_system, - file_system::{directory, DirectoryEntry, Label}, - git::{ - error::*, - Branch, - BranchName, - Commit, - Glob, - History, - Namespace, - Revision, - Signature, - Stats, - Tag, - TagName, + file_system::{ + directory::{self, Directory, DirectoryEntry, FileContent}, + Label, }, + git::{error::*, Branch, Commit, Glob, History, Namespace, Revision, Signature, Stats, Tag}, }; -use directory::{Directory, FileContent}; +use git_ref_format::RefString; use radicle_git_ext::Oid; use std::{ - collections::{btree_set, BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet}, convert::TryFrom, path::PathBuf, str, @@ -45,6 +35,9 @@ use std::{ use super::commit::ToCommit; +pub mod iter; +pub use iter::{Branches, Namespaces, Tags}; + /// Wrapper around the `git2`'s `git2::Repository` type. /// This is to to limit the functionality that we can do /// on the underlying object. @@ -74,67 +67,6 @@ 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> { @@ -146,28 +78,22 @@ impl<'a> RepositoryRef<'a> { /// Returns an iterator of branches that match `pattern`. pub fn branches(&self, pattern: &Glob) -> Result { - let mut branches = Branches { - references: vec![], - current: 0, - }; + let mut branches = Branches::default(); for glob in pattern.globs().iter() { let namespaced = self.namespaced_refname(glob)?; let references = self.repo_ref.references_glob(&namespaced)?; - branches.references.push(references); + branches.push(references); } Ok(branches) } /// Returns an iterator of tags that match `pattern`. pub fn tags(&self, pattern: &Glob) -> Result { - let mut tags = Tags { - references: vec![], - current: 0, - }; + let mut tags = Tags::default(); for glob in pattern.globs().iter() { let namespaced = self.namespaced_refname(glob)?; let references = self.repo_ref.references_glob(&namespaced)?; - tags.references.push(references); + tags.push(references); } Ok(tags) } @@ -187,9 +113,7 @@ impl<'a> RepositoryRef<'a> { .collect::, Error>>()?; set.extend(new_set); } - Ok(Namespaces { - namespaces: set.into_iter(), - }) + Ok(Namespaces::new(set)) } /// Get the [`Diff`] between two commits. @@ -341,21 +265,22 @@ impl<'a> RepositoryRef<'a> { } /// Lists branch names with `filter`. - 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?; + pub fn branch_names(&self, filter: &Glob) -> Result, Error> { + let mut branches = self + .branches(filter)? + .map(|b| b.map(|b| b.refname().into())) + .collect::, _>>()?; branches.sort(); Ok(branches) } /// Lists tag names in the local RefScope. - pub fn tag_names(&self) -> Result, Error> { + pub fn tag_names(&self) -> Result, Error> { let mut tags = self .tags(&Glob::tags("*")?)? - .map(|t| t.map(|t| t.name())) - .collect::, Error>>()?; + .map(|t| t.map_err(Error::from).map(|t| t.refname().into())) + .collect::, Error>>()?; tags.sort(); Ok(tags) diff --git a/radicle-surf/src/git/repo/iter.rs b/radicle-surf/src/git/repo/iter.rs new file mode 100644 index 0000000..9bd17b9 --- /dev/null +++ b/radicle-surf/src/git/repo/iter.rs @@ -0,0 +1,122 @@ +// I think the following `Tags` and `Branches` would be merged +// using Generic associated types supported in Rust 1.65.0. + +use std::{ + collections::{btree_set, BTreeSet}, + convert::TryFrom as _, +}; + +use crate::git::{Branch, Namespace, Tag}; + +/// An iterator for tags. +#[derive(Default)] +pub struct Tags<'a> { + references: Vec>, + current: usize, +} + +impl<'a> Tags<'a> { + pub(super) fn push(&mut self, references: git2::References<'a>) { + self.references.push(references) + } +} + +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::Tag::from) + .and_then(|r| Tag::try_from(&r).map_err(error::Tag::from)), + ); + }, + None => self.current += 1, + }, + None => break, + } + } + None + } +} + +/// An iterator for branches. +#[derive(Default)] +pub struct Branches<'a> { + references: Vec>, + current: usize, +} + +impl<'a> Branches<'a> { + pub(super) fn push(&mut self, references: git2::References<'a>) { + self.references.push(references) + } +} + +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::Branch::from) + .and_then(|r| Branch::try_from(&r).map_err(error::Branch::from)), + ) + }, + None => self.current += 1, + }, + None => break, + } + } + None + } +} + +// TODO: not sure this buys us much +/// An iterator for namespaces. +pub struct Namespaces { + namespaces: btree_set::IntoIter, +} + +impl Namespaces { + pub(super) fn new(namespaces: BTreeSet) -> Self { + Self { + namespaces: namespaces.into_iter(), + } + } +} + +impl Iterator for Namespaces { + type Item = Namespace; + fn next(&mut self) -> Option { + self.namespaces.next() + } +} + +pub mod error { + use thiserror::Error; + + use crate::git::{branch, tag}; + + #[derive(Debug, Error)] + pub enum Branch { + #[error(transparent)] + Git(#[from] git2::Error), + #[error(transparent)] + Branch(#[from] branch::error::Branch), + } + + #[derive(Debug, Error)] + pub enum Tag { + #[error(transparent)] + Git(#[from] git2::Error), + #[error(transparent)] + Tag(#[from] tag::error::FromReference), + } +} diff --git a/radicle-surf/src/git/tag.rs b/radicle-surf/src/git/tag.rs index 1d23957..743599b 100644 --- a/radicle-surf/src/git/tag.rs +++ b/radicle-surf/src/git/tag.rs @@ -1,67 +1,9 @@ -// 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::{convert::TryFrom, str}; -use crate::git::{self, error::Error, Author}; -use git_ref_format::RefString; -use radicle_git_ext::Oid; -use std::{convert::TryFrom, fmt, str}; +use git_ext::Oid; +use git_ref_format::{component, lit, Qualified, RefStr, RefString}; -/// 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(RefString); - -impl fmt::Display for TagName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl TryFrom<&[u8]> for TagName { - type Error = Error; - - fn try_from(name: &[u8]) -> Result { - let name = str::from_utf8(name)?; - let short_name = match git::ext::try_extract_refname(name) { - Ok(stripped) => stripped, - Err(original) => original, - }; - let refstring = RefString::try_from(short_name)?; - Ok(Self(refstring)) - } -} - -impl TagName { - /// Create a new `TagName`. - 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()) - } -} +use crate::git::{refstr_join, Author}; /// The static information of a [`git2::Tag`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -70,25 +12,21 @@ pub enum Tag { Light { /// The Object ID for the `Tag`, i.e the SHA1 digest. id: Oid, - /// The name that references this `Tag`. - name: TagName, - /// If the tag is provided this holds the remote’s name. - remote: Option, + /// The reference name for this `Tag`. + name: RefString, }, /// An annotated git tag. Annotated { /// The Object ID for the `Tag`, i.e the SHA1 digest. id: Oid, /// The Object ID for the object that is tagged. - target_id: Oid, - /// The name that references this `Tag`. - name: TagName, + target: Oid, + /// The reference name for this `Tag`. + name: RefString, /// The named author of this `Tag`, if the `Tag` was annotated. tagger: Option, /// The message with this `Tag`, if the `Tag` was annotated. message: Option, - /// If the tag is provided this holds the remote’s name. - remote: Option, }, } @@ -101,32 +39,62 @@ impl Tag { } } - /// Get the `TagName` of the tag, regardless of its type. - pub fn name(&self) -> TagName { - match self { - Self::Light { name, .. } => name.clone(), - Self::Annotated { name, .. } => name.clone(), - } + /// Return the fully qualified `Tag` refname, + /// e.g. `refs/tags/release/v1`. + pub fn refname(&self) -> Qualified { + lit::refs_tags(self.name()).into() } - /// Returns the full ref name of the tag. - pub fn refname(&self) -> String { - self.name().refname() + fn name(&self) -> &RefString { + match &self { + Tag::Light { name, .. } => name, + Tag::Annotated { name, .. } => name, + } } } -impl<'repo> TryFrom> for Tag { - type Error = Error; +pub mod error { + use std::str; - fn try_from(tag: git2::Tag) -> Result { + use git_ref_format::RefString; + use thiserror::Error; + + #[derive(Debug, Error)] + pub enum FromTag { + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] str::Utf8Error), + } + + #[derive(Debug, Error)] + pub enum FromReference { + #[error(transparent)] + FromTag(#[from] FromTag), + #[error(transparent)] + Git(#[from] git2::Error), + #[error("the refname '{0}' did not begin with 'refs/tags'")] + NotQualified(String), + #[error("the refname '{0}' did not begin with 'refs/tags'")] + NotTag(RefString), + #[error(transparent)] + RefFormat(#[from] git_ref_format::Error), + #[error(transparent)] + Utf8(#[from] str::Utf8Error), + } +} + +impl TryFrom<&git2::Tag<'_>> for Tag { + type Error = error::FromTag; + + fn try_from(tag: &git2::Tag) -> Result { let id = tag.id().into(); - - let target_id = tag.target_id().into(); - - let name = TagName::try_from(tag.name_bytes())?; - + let target = tag.target_id().into(); + let name = { + let name = str::from_utf8(tag.name_bytes())?; + RefStr::try_from_str(name)?.to_ref_string() + }; let tagger = tag.tagger().map(Author::try_from).transpose()?; - let message = tag .message_bytes() .map(str::from_utf8) @@ -135,49 +103,47 @@ impl<'repo> TryFrom> for Tag { Ok(Tag::Annotated { id, - target_id, + target, name, tagger, message, - remote: None, }) } } -impl<'repo> TryFrom> for Tag { - type Error = Error; +impl TryFrom<&git2::Reference<'_>> for Tag { + type Error = error::FromReference; - fn try_from(reference: git2::Reference) -> Result { - let name = TagName::try_from(reference.name_bytes())?; - - let (remote, name) = if git::ext::is_remote(&reference) { - let mut split = name.0.splitn(2, '/'); - let remote = split.next().map(|x| x.to_owned()); - let name = split.next().unwrap(); - (remote, TagName::new(name)?) - } else { - (None, name) + fn try_from(reference: &git2::Reference) -> Result { + let name = { + let name = str::from_utf8(reference.name_bytes())?; + RefStr::try_from_str(name)? + .qualified() + .ok_or_else(|| error::FromReference::NotQualified(name.to_string()))? }; - match reference.peel_to_tag() { - Ok(tag) => Ok(Tag::try_from(tag)?), - Err(err) => { + let (_refs, tags, c, cs) = name.non_empty_components(); + + if tags == component::TAGS { + match reference.peel_to_tag() { + Ok(tag) => Tag::try_from(&tag).map_err(error::FromReference::from), // If we get an error peeling to a tag _BUT_ we also have confirmed the // reference is a tag, that means we have a lightweight tag, // i.e. a commit SHA and name. - if err.class() == git2::ErrorClass::Object - && err.code() == git2::ErrorCode::InvalidSpec + Err(err) + if err.class() == git2::ErrorClass::Object + && err.code() == git2::ErrorCode::InvalidSpec => { let commit = reference.peel_to_commit()?; Ok(Tag::Light { id: commit.id().into(), - name, - remote, + name: refstr_join(c, cs), }) - } else { - Err(err.into()) - } - }, + }, + Err(err) => Err(err.into()), + } + } else { + Err(error::FromReference::NotTag(name.into())) } } } diff --git a/radicle-surf/src/lib.rs b/radicle-surf/src/lib.rs index dfa3db9..24a6139 100644 --- a/radicle-surf/src/lib.rs +++ b/radicle-surf/src/lib.rs @@ -80,6 +80,11 @@ //! # Ok(()) //! # } //! ``` + +pub extern crate git_ref_format; + +extern crate radicle_git_ext as git_ext; + pub mod diff; pub mod file_system; pub mod git; diff --git a/radicle-surf/src/object/blob.rs b/radicle-surf/src/object/blob.rs index 756df96..4d61beb 100644 --- a/radicle-surf/src/object/blob.rs +++ b/radicle-surf/src/object/blob.rs @@ -114,25 +114,21 @@ impl Serialize for BlobContent { /// /// Will return [`Error`] if the project doesn't exist or a surf interaction /// fails. -pub fn blob

( +pub fn blob( repo: &RepositoryRef, - maybe_revision: Option>, + maybe_revision: Option, path: &str, -) -> Result -where - P: ToString, -{ +) -> Result { make_blob(repo, maybe_revision, path, content) } -fn make_blob( +fn make_blob( repo: &RepositoryRef, - maybe_revision: Option>, + maybe_revision: Option, path: &str, content: C, ) -> Result where - P: ToString, C: FnOnce(&[u8]) -> BlobContent, { let revision = maybe_revision.unwrap(); diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index 2077527..aaac499 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -20,6 +20,7 @@ use std::str::FromStr as _; +use git_ref_format::refname; #[cfg(feature = "serialize")] use serde::{ ser::{SerializeStruct as _, Serializer}, @@ -85,20 +86,17 @@ impl Serialize for TreeEntry { /// # Errors /// /// Will return [`Error`] if any of the surf interactions fail. -pub fn tree

( +pub fn tree( repo: &RepositoryRef, - maybe_revision: Option>, + maybe_revision: Option, maybe_prefix: Option, -) -> Result -where - P: ToString, -{ +) -> Result { let prefix = maybe_prefix.unwrap_or_default(); let rev = match maybe_revision { Some(r) => r, None => Revision::Branch { - name: "main".to_string(), - peer_id: None, + name: refname!("main"), + remote: None, }, }; diff --git a/radicle-surf/src/revision.rs b/radicle-surf/src/revision.rs index 8805983..8f3ae76 100644 --- a/radicle-surf/src/revision.rs +++ b/radicle-surf/src/revision.rs @@ -17,6 +17,7 @@ //! Represents revisions +use git_ref_format::{lit, Qualified, RefString}; use nonempty::NonEmpty; #[cfg(feature = "serialize")] @@ -24,7 +25,7 @@ use serde::{Deserialize, Serialize}; use radicle_git_ext::Oid; -use crate::git::{self, commit::ToCommit, error::Error, BranchName, Glob, RepositoryRef, TagName}; +use crate::git::{self, commit::ToCommit, error::Error, Glob, RepositoryRef}; /// Types of a peer. pub enum Category { @@ -51,20 +52,20 @@ pub enum Category { serde(rename_all = "camelCase", tag = "type") )] #[derive(Debug, Clone)] -pub enum Revision

{ +pub enum Revision { /// Select a tag under the name provided. #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] Tag { /// Name of the tag. - name: String, + name: RefString, }, /// Select a branch under the name provided. #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] Branch { /// Name of the branch. - name: String, + name: RefString, /// The remote peer, if specified. - peer_id: Option

, + remote: Option, }, /// Select a SHA1 under the name provided. #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] @@ -74,39 +75,31 @@ pub enum Revision

{ }, } -impl

git::Revision for &Revision

-where - P: ToString, -{ +impl git::Revision for &Revision { fn object_id(&self, repo: &RepositoryRef) -> Result { match self { - Revision::Tag { name } => { - repo.refname_to_oid(git::TagName::new(name)?.refname().as_str()) + Revision::Tag { name } => match name.qualified() { + None => Qualified::from(lit::refs_tags(name)).object_id(repo), + Some(name) => name.object_id(repo), }, - 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::Branch { name, remote } => match remote { + Some(remote) => { + Qualified::from(lit::refs_remotes(remote.join(name))).object_id(repo) + }, + None => git::Branch::local(name).refname().object_id(repo), }, Revision::Sha { sha } => Ok(*sha), } } } -impl

ToCommit for &Revision

-where - P: ToString, -{ +impl ToCommit for &Revision { fn to_commit(self, repo: &RepositoryRef) -> Result { repo.commit(self) } } -/// Bundled response to retrieve both [`BranchName`]es and [`TagName`]s for +/// Bundled response to retrieve both branches and tags for /// a user's repo. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Revisions { @@ -114,10 +107,10 @@ pub struct Revisions { pub peer_id: P, /// The user who owns these revisions. pub user: U, - /// List of [`git::BranchName`]. - pub branches: NonEmpty, - /// List of [`git::TagName`]. - pub tags: Vec, + /// List of branch reference names. + pub branches: NonEmpty, + /// List of tag reference names. + pub tags: Vec, } /// Provide the [`Revisions`] for the given `peer_id`, looking for the diff --git a/radicle-surf/t/Cargo.toml b/radicle-surf/t/Cargo.toml index 19ce3f2..efda92d 100644 --- a/radicle-surf/t/Cargo.toml +++ b/radicle-surf/t/Cargo.toml @@ -16,7 +16,7 @@ test = [] [dev-dependencies] nonempty = "0.5" pretty_assertions = "1.3.0" -proptest = "0.9" +proptest = "1" serde_json = "1" [dev-dependencies.git2] @@ -24,6 +24,14 @@ version = "0.15.0" default-features = false features = ["vendored-libgit2"] +[dev-dependencies.git-ref-format] +path = "../../git-ref-format" +features = ["macro"] + +[dev-dependencies.git-ref-format-test] +path = "../../git-ref-format/t" +features = ["test"] + [dev-dependencies.radicle-git-ext] path = "../../radicle-git-ext" diff --git a/radicle-surf/t/src/file_system.rs b/radicle-surf/t/src/file_system.rs index 4cef3d8..2d807a3 100644 --- a/radicle-surf/t/src/file_system.rs +++ b/radicle-surf/t/src/file_system.rs @@ -30,6 +30,7 @@ mod path { #[cfg(test)] mod directory { + use git_ref_format::refname; use radicle_surf::{ file_system::DirectoryEntry, git::{Branch, Repository}, @@ -42,7 +43,7 @@ mod directory { fn directory_get_path() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let repo = repo.as_ref(); - let root = repo.root_dir(&Branch::local("master")).unwrap(); + let root = repo.root_dir(&Branch::local(refname!("master"))).unwrap(); // get_path for a file. let path = Path::new("src/memory.rs"); @@ -79,7 +80,7 @@ mod directory { fn directory_size() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let repo = repo.as_ref(); - let root = repo.root_dir(&Branch::local("master")).unwrap(); + let root = repo.root_dir(&Branch::local(refname!("master"))).unwrap(); /* git-platinum (master) $ ls -l src diff --git a/radicle-surf/t/src/git.rs b/radicle-surf/t/src/git.rs index 9d4ec9e..0c86e75 100644 --- a/radicle-surf/t/src/git.rs +++ b/radicle-surf/t/src/git.rs @@ -1,14 +1,14 @@ // Copyright © 2022 The Radicle Git Contributors // SPDX-License-Identifier: GPL-3.0-or-later -//! Unit tests for radicle_surf::vcs::git and its submodules. +//! Unit tests for radicle_surf::git and its submodules. #[cfg(feature = "serialize")] -use radicle_surf::git::{Author, BranchType, Commit}; +use radicle_surf::git::{Author, Commit}; use radicle_surf::{ diff::*, file_system::{unsound, DirectoryEntry, Path}, - git::{error::Error, Branch, Glob, Namespace, Oid, Repository, TagName}, + git::{error::Error, Branch, Glob, Namespace, Oid, Repository}, }; const GIT_PLATINUM: &str = "../data/git-platinum"; @@ -17,22 +17,27 @@ const GIT_PLATINUM: &str = "../data/git-platinum"; #[test] // An issue with submodules, see: https://github.com/radicle-dev/radicle-surf/issues/54 fn test_submodule_failure() { + use git_ref_format::refname; + let repo = Repository::discover(".").unwrap(); - repo.as_ref().root_dir(&Branch::local("main")).unwrap(); + repo.as_ref() + .root_dir(&Branch::local(refname!("main"))) + .unwrap(); } #[cfg(test)] mod namespace { use super::*; + use git_ref_format::{name::component, refname}; use pretty_assertions::{assert_eq, assert_ne}; #[test] fn switch_to_banana() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let history_master = repo.history(&Branch::local("master"))?; + let history_master = repo.history(&Branch::local(refname!("master")))?; repo.switch_namespace("golden")?; - let history_banana = repo.history(&Branch::local("banana"))?; + let history_banana = repo.history(&Branch::local(refname!("banana")))?; assert_ne!(history_master.head(), history_banana.head()); @@ -43,28 +48,34 @@ mod namespace { fn me_namespace() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let history = repo.history(&Branch::local("master"))?; + let history = repo.history(&Branch::local(refname!("master")))?; - assert_eq!(repo.which_namespace(), Ok(None)); + assert_eq!(repo.which_namespace().unwrap(), None); repo.switch_namespace("me")?; - assert_eq!(repo.which_namespace(), Ok(Some(Namespace::try_from("me")?))); + assert_eq!( + repo.which_namespace().unwrap(), + Some(Namespace::try_from("me")?) + ); - let history_feature = repo.history(&Branch::local("feature/#1194"))?; + let history_feature = repo.history(&Branch::local(refname!("feature/#1194")))?; assert_eq!(history.head(), history_feature.head()); - let expected_branches: Vec = vec![Branch::local("feature/#1194")]; + let expected_branches: Vec = vec![Branch::local(refname!("feature/#1194"))]; let mut branches = repo .branches(&Glob::heads("*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); assert_eq!(expected_branches, branches); - let expected_branches: Vec = vec![Branch::remote("feature/#1194", "fein")]; + let expected_branches: Vec = vec![Branch::remote( + component!("fein"), + refname!("heads/feature/#1194"), + )]; let mut branches = repo .branches(&Glob::remotes("fein/*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); assert_eq!(expected_branches, branches); @@ -76,36 +87,42 @@ mod namespace { fn golden_namespace() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let history = repo.history(&Branch::local("master"))?; + let history = repo.history(&Branch::local(refname!("master")))?; - assert_eq!(repo.which_namespace(), Ok(None)); + assert_eq!(repo.which_namespace().unwrap(), None); repo.switch_namespace("golden")?; assert_eq!( - repo.which_namespace(), - Ok(Some(Namespace::try_from("golden")?)) + repo.which_namespace().unwrap(), + Some(Namespace::try_from("golden")?) ); - let golden_history = repo.history(&Branch::local("master"))?; + let golden_history = repo.history(&Branch::local(refname!("master")))?; assert_eq!(history.head(), golden_history.head()); - let expected_branches: Vec = vec![Branch::local("banana"), Branch::local("master")]; + let expected_branches: Vec = vec![ + Branch::local(refname!("banana")), + Branch::local(refname!("master")), + ]; let mut branches = repo .branches(&Glob::heads("*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); assert_eq!(expected_branches, branches); + // NOTE: these tests used to remove the categories, i.e. heads & tags, but that + // was specialised logic based on the radicle-link storage layout. + let remote = component!("kickflip"); let expected_branches: Vec = vec![ - Branch::remote("fakie/bigspin", "kickflip"), - Branch::remote("heelflip", "kickflip"), - Branch::remote("v0.1.0", "kickflip"), + Branch::remote(remote.clone(), refname!("heads/fakie/bigspin")), + Branch::remote(remote.clone(), refname!("heads/heelflip")), + Branch::remote(remote, refname!("tags/v0.1.0")), ]; let mut branches = repo .branches(&Glob::remotes("kickflip/*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); assert_eq!(expected_branches, branches); @@ -117,22 +134,22 @@ mod namespace { fn silver_namespace() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let history = repo.history(&Branch::local("master"))?; + let history = repo.history(&Branch::local(refname!("master")))?; - assert_eq!(repo.which_namespace(), Ok(None)); + assert_eq!(repo.which_namespace().unwrap(), None); repo.switch_namespace("golden/silver")?; assert_eq!( - repo.which_namespace(), - Ok(Some(Namespace::try_from("golden/silver")?)) + repo.which_namespace().unwrap(), + Some(Namespace::try_from("golden/silver")?) ); - let silver_history = repo.history(&Branch::local("master"))?; + let silver_history = repo.history(&Branch::local(refname!("master")))?; assert_ne!(history.head(), silver_history.head()); - let expected_branches: Vec = vec![Branch::local("master")]; + let expected_branches: Vec = vec![Branch::local(refname!("master"))]; let mut branches = repo .branches(&Glob::heads("*")?.and_remotes("*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); assert_eq!(expected_branches, branches); @@ -143,6 +160,8 @@ mod namespace { #[cfg(test)] mod rev { + use git_ref_format::{name::component, refname}; + use super::*; use std::str::FromStr; @@ -161,7 +180,8 @@ mod rev { fn _master() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let mut history = repo.history(&Branch::remote("master", "origin"))?; + let mut history = + repo.history(&Branch::remote(component!("origin"), refname!("master")))?; let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?; assert!( @@ -228,7 +248,7 @@ mod rev { fn tag() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let rev = TagName::new("v0.2.0")?; + let rev = refname!("refs/tags/v0.2.0"); let history = repo.history(&rev)?; let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?; @@ -240,6 +260,8 @@ mod rev { #[cfg(test)] mod last_commit { + use git_ref_format::refname; + use super::*; use std::str::FromStr; @@ -344,7 +366,7 @@ mod last_commit { fn root() { let repo = Repository::open(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); - let rev = Branch::local("master"); + let rev = Branch::local(refname!("master")); let root_last_commit_id = repo .as_ref() .last_commit(Path::root(), &rev) @@ -353,7 +375,7 @@ mod last_commit { let expected_oid = repo .as_ref() - .history(&Branch::local("master")) + .history(&Branch::local(refname!("master"))) .unwrap() .head() .id; @@ -365,7 +387,7 @@ mod last_commit { let repo = Repository::open(GIT_PLATINUM) .expect("Could not retrieve ./data/git-platinum as git repository"); let repo = repo.as_ref(); - let history = repo.history(&Branch::local("dev")).unwrap(); + let history = repo.history(&Branch::local(refname!("dev"))).unwrap(); let file_commit = history.by_path(unsound::path::new("~/bin/cat")).next(); assert!(file_commit.is_some()); println!("file commit: {:?}", &file_commit); @@ -375,6 +397,7 @@ mod last_commit { #[cfg(test)] mod diff { use super::*; + use git_ref_format::refname; use pretty_assertions::assert_eq; use std::str::FromStr; @@ -463,7 +486,10 @@ mod diff { fn test_branch_diff() -> Result<(), Error> { let repo = Repository::open(GIT_PLATINUM)?; let repo = repo.as_ref(); - let diff = repo.diff(&Branch::local("master"), &Branch::local("dev"))?; + let diff = repo.diff( + &Branch::local(refname!("master")), + &Branch::local(refname!("dev")), + )?; println!("Diff two branches: master -> dev"); println!( @@ -569,6 +595,7 @@ mod diff { #[cfg(test)] mod threading { + use git_ref_format::{name::component, refname}; use radicle_surf::git::Glob; use super::*; @@ -581,19 +608,21 @@ mod threading { let mut branches = locked_repo .as_ref() .branches(&Glob::heads("*")?.and_remotes("*")?)? - .collect::, Error>>()?; + .collect::, _>>()?; branches.sort(); + let origin = component!("origin"); + let banana = component!("banana"); assert_eq!( branches, vec![ - Branch::remote("HEAD", "origin"), - Branch::local("dev"), - Branch::remote("dev", "origin"), - Branch::local("master"), - Branch::remote("master", "origin"), - Branch::remote("orange/pineapple", "banana"), - Branch::remote("pineapple", "banana"), + Branch::local(refname!("dev")), + Branch::local(refname!("master")), + Branch::remote(banana.clone(), refname!("orange/pineapple")), + Branch::remote(banana, refname!("pineapple")), + Branch::remote(origin.clone(), refname!("HEAD")), + Branch::remote(origin.clone(), refname!("dev")), + Branch::remote(origin, refname!("master")), ] ); @@ -641,28 +670,29 @@ mod commit { #[cfg(feature = "serialize")] #[cfg(test)] mod branch { - use super::*; + use git_ref_format::{RefStr, RefString}; + use git_ref_format_test::gen; use proptest::prelude::*; use test_helpers::roundtrip; + use super::*; + proptest! { #[test] - fn prop_test_branch(branch in branch_strategy()) { + fn prop_test_branch(branch in gen_branch()) { roundtrip::json(branch) } } - fn branch_strategy() -> impl Strategy { + fn gen_branch() -> impl Strategy { prop_oneof![ - any::().prop_map(|name| Branch { - name: BranchName::new(&name), - locality: BranchType::Local - }), - (any::(), any::()).prop_map(|(name, remote_name)| Branch { - name: BranchName::new(&name), - locality: BranchType::Remote { - name: Some(remote_name), - }, + gen::valid().prop_map(|name| Branch::local(RefString::try_from(name).unwrap())), + (gen::valid(), gen::valid()).prop_map(|(remote, name): (String, String)| { + let remote = + RefStr::try_from_str(&remote).expect("BUG: reference strings should be valid"); + let name = + RefStr::try_from_str(&name).expect("BUG: reference strings should be valid"); + Branch::remote(remote.head(), name) }) ] } @@ -671,7 +701,7 @@ mod branch { #[cfg(test)] mod reference { use super::*; - use radicle_surf::git::{Glob, Tag}; + use radicle_surf::git::Glob; #[test] fn test_branches() { @@ -679,7 +709,7 @@ mod reference { let repo = repo.as_ref(); let branches = repo.branches(&Glob::heads("*").unwrap()).unwrap(); for b in branches { - println!("{}", b.unwrap().name); + println!("{}", b.unwrap().refname()); } let branches = repo .branches(&Glob::heads("*").unwrap().and_remotes("banana/*").unwrap()) @@ -696,7 +726,7 @@ mod reference { let tags = repo_ref .tags(&Glob::tags("*").unwrap()) .unwrap() - .collect::, Error>>() + .collect::, _>>() .unwrap(); assert_eq!(tags.len(), 6); let root_dir = repo_ref.root_dir(&tags[0]).unwrap(); @@ -722,14 +752,18 @@ mod reference { mod code_browsing { use super::*; + + use git_ref_format::refname; use radicle_surf::{file_system::Directory, git::RepositoryRef}; #[test] fn iterate_root_dir_recursive() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let repo = repo.as_ref(); - let root_dir = repo.root_dir(&Branch::local("master")).unwrap(); + + let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap(); let count = println_dir(&root_dir, &repo, 0); + assert_eq!(count, 36); // Check total file count. /// Prints items in `dir` with `indent_level`. @@ -753,7 +787,7 @@ mod code_browsing { fn browse_repo_lazily() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let repo = repo.as_ref(); - let root_dir = repo.root_dir(&Branch::local("master")).unwrap(); + let root_dir = repo.root_dir(&Branch::local(refname!("master"))).unwrap(); let count = root_dir.contents(&repo).unwrap().iter().count(); assert_eq!(count, 8); let count = traverse(&root_dir, &repo); @@ -775,7 +809,7 @@ mod code_browsing { fn test_file_history() { let repo = Repository::open(GIT_PLATINUM).unwrap(); let repo = repo.as_ref(); - let history = repo.history(&Branch::local("dev")).unwrap(); + let history = repo.history(&Branch::local(refname!("dev"))).unwrap(); let path = unsound::path::new("README.md"); let mut file_history = history.by_path(path); let commit = file_history.next().unwrap().unwrap();