mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-18 19:10:31 +08:00
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 <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
@@ -4,7 +4,7 @@ description = "A code surfing library for VCS file systems"
|
||||
readme = "README.md"
|
||||
version = "0.8.0"
|
||||
authors = ["The Radicle Team <dev@radicle.xyz>"]
|
||||
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"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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 [
|
||||
|
||||
@@ -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<BranchName>,
|
||||
pub branches: Vec<RefString>,
|
||||
}
|
||||
|
||||
/// Representation of a code commit.
|
||||
@@ -195,7 +196,7 @@ pub fn commit<R: git::Revision>(repo: &RepositoryRef, rev: R) -> Result<Commit,
|
||||
let branches = repo
|
||||
.revision_branches(&sha1, &Glob::heads("*")?.and_remotes("*")?)?
|
||||
.into_iter()
|
||||
.map(|b| b.name)
|
||||
.map(|b| b.refname().into())
|
||||
.collect();
|
||||
|
||||
Ok(Commit {
|
||||
@@ -226,13 +227,7 @@ pub fn header(repo: &RepositoryRef, sha1: Oid) -> Result<Header, Error> {
|
||||
///
|
||||
/// Will return [`Error`] if the project doesn't exist or the surf interaction
|
||||
/// fails.
|
||||
pub fn commits<P>(
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
) -> Result<Commits, Error>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
pub fn commits(repo: &RepositoryRef, maybe_revision: Option<Revision>) -> Result<Commits, Error> {
|
||||
let rev = match maybe_revision {
|
||||
Some(revision) => revision,
|
||||
None => Revision::Sha {
|
||||
|
||||
+29
-9
@@ -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<Oid, Error>;
|
||||
}
|
||||
|
||||
impl Revision for RefString {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
repo.refname_to_oid(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Revision for &RefString {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
repo.refname_to_oid(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Revision for Qualified<'_> {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
repo.refname_to_oid(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Revision for &Qualified<'_> {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
repo.refname_to_oid(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Revision for Oid {
|
||||
fn object_id(&self, _repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
Ok(*self)
|
||||
@@ -147,9 +170,6 @@ impl Revision for &Tag {
|
||||
}
|
||||
}
|
||||
|
||||
impl Revision for &TagName {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
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::<RefString>()
|
||||
}
|
||||
|
||||
+284
-164
@@ -1,186 +1,306 @@
|
||||
// This file is part of radicle-surf
|
||||
// <https://github.com/radicle-dev/radicle-surf>
|
||||
//
|
||||
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 or
|
||||
// later as published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use std::{
|
||||
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/<name>/*` if the name is
|
||||
/// provided, otherwise `refs/remotes/**/*`.
|
||||
Remote {
|
||||
/// Name of the remote.
|
||||
name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<BranchType> for git2::BranchType {
|
||||
fn from(other: BranchType) -> Self {
|
||||
match other {
|
||||
BranchType::Local => git2::BranchType::Local,
|
||||
BranchType::Remote { .. } => git2::BranchType::Remote,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<git2::BranchType> 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<Self, Self::Error> {
|
||||
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<Ordering> {
|
||||
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<R>(name: R) -> Self
|
||||
where
|
||||
R: AsRef<RefStr>,
|
||||
{
|
||||
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/<remote>/<name>`.
|
||||
pub fn remote<R>(remote: Component<'_>, name: R) -> Self
|
||||
where
|
||||
R: AsRef<RefStr>,
|
||||
{
|
||||
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<git2::Reference<'repo>> for Branch {
|
||||
type Error = Error;
|
||||
impl TryFrom<&git2::Reference<'_>> for Branch {
|
||||
type Error = error::Branch;
|
||||
|
||||
fn try_from(reference: git2::Reference) -> Result<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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, Self::Error> {
|
||||
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<Self, Self::Err> {
|
||||
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::<RefString>())))
|
||||
} 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<R>(name: R) -> Self
|
||||
where
|
||||
R: AsRef<RefStr>,
|
||||
{
|
||||
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<Self, Self::Error> {
|
||||
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, Self::Error> {
|
||||
Self::from_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Local {
|
||||
type Err = error::Local;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
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<R>(remote: Component, name: R) -> Self
|
||||
where
|
||||
R: AsRef<RefStr>,
|
||||
{
|
||||
Self {
|
||||
name: name.as_ref().to_ref_string(),
|
||||
remote: remote.to_ref_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the `name` from the form `refs/remotes/<remote>/<rest>`.
|
||||
///
|
||||
/// If the `name` is not of this form, then `None` is returned.
|
||||
pub fn from_refs_remotes<R>(name: R) -> Option<Self>
|
||||
where
|
||||
R: AsRef<RefStr>,
|
||||
{
|
||||
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<Self, Self::Error> {
|
||||
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, Self::Error> {
|
||||
Self::from_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Remote {
|
||||
type Err = error::Remote;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
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::<RefString>()))
|
||||
} 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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Commit, Error> {
|
||||
repo.commit(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCommit for &Qualified<'_> {
|
||||
fn to_commit(self, repo: &RepositoryRef) -> Result<Commit, Error> {
|
||||
repo.commit(self)
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// This file is part of radicle-surf
|
||||
// <https://github.com/radicle-dev/radicle-surf>
|
||||
//
|
||||
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 or
|
||||
// later as published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#![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<String, String> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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<git2::References<'a>>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Tags<'a> {
|
||||
type Item = Result<Tag, Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current < self.references.len() {
|
||||
match self.references.get_mut(self.current) {
|
||||
Some(refs) => match refs.next() {
|
||||
Some(res) => return Some(res.map_err(Error::Git).and_then(Tag::try_from)),
|
||||
None => self.current += 1,
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator for branches.
|
||||
pub struct Branches<'a> {
|
||||
references: Vec<git2::References<'a>>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Branches<'a> {
|
||||
type Item = Result<Branch, Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current < self.references.len() {
|
||||
match self.references.get_mut(self.current) {
|
||||
Some(refs) => match refs.next() {
|
||||
Some(res) => return Some(res.map_err(Error::Git).and_then(Branch::try_from)),
|
||||
None => self.current += 1,
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator for namespaces.
|
||||
pub struct Namespaces {
|
||||
namespaces: btree_set::IntoIter<Namespace>,
|
||||
}
|
||||
|
||||
impl Iterator for Namespaces {
|
||||
type Item = Namespace;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.namespaces.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RepositoryRef<'a> {
|
||||
/// What is the current namespace we're browsing in.
|
||||
pub fn which_namespace(&self) -> Result<Option<Namespace>, Error> {
|
||||
@@ -146,28 +78,22 @@ impl<'a> RepositoryRef<'a> {
|
||||
|
||||
/// Returns an iterator of branches that match `pattern`.
|
||||
pub fn branches(&self, pattern: &Glob<Branch>) -> Result<Branches, Error> {
|
||||
let mut branches = Branches {
|
||||
references: vec![],
|
||||
current: 0,
|
||||
};
|
||||
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<Tag>) -> Result<Tags, Error> {
|
||||
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::<Result<BTreeSet<Namespace>, 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<Branch>) -> Result<Vec<BranchName>, Error> {
|
||||
let branches: Result<Vec<BranchName>, Error> =
|
||||
self.branches(filter)?.map(|b| b.map(|b| b.name)).collect();
|
||||
let mut branches = branches?;
|
||||
pub fn branch_names(&self, filter: &Glob<Branch>) -> Result<Vec<RefString>, Error> {
|
||||
let mut branches = self
|
||||
.branches(filter)?
|
||||
.map(|b| b.map(|b| b.refname().into()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
branches.sort();
|
||||
|
||||
Ok(branches)
|
||||
}
|
||||
|
||||
/// Lists tag names in the local RefScope.
|
||||
pub fn tag_names(&self) -> Result<Vec<TagName>, Error> {
|
||||
pub fn tag_names(&self) -> Result<Vec<RefString>, Error> {
|
||||
let mut tags = self
|
||||
.tags(&Glob::tags("*")?)?
|
||||
.map(|t| t.map(|t| t.name()))
|
||||
.collect::<Result<Vec<TagName>, Error>>()?;
|
||||
.map(|t| t.map_err(Error::from).map(|t| t.refname().into()))
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
tags.sort();
|
||||
|
||||
Ok(tags)
|
||||
|
||||
@@ -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<git2::References<'a>>,
|
||||
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<Tag, error::Tag>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current < self.references.len() {
|
||||
match self.references.get_mut(self.current) {
|
||||
Some(refs) => match refs.next() {
|
||||
Some(res) => {
|
||||
return Some(
|
||||
res.map_err(error::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<git2::References<'a>>,
|
||||
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<Branch, error::Branch>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current < self.references.len() {
|
||||
match self.references.get_mut(self.current) {
|
||||
Some(refs) => match refs.next() {
|
||||
Some(res) => {
|
||||
return Some(
|
||||
res.map_err(error::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<Namespace>,
|
||||
}
|
||||
|
||||
impl Namespaces {
|
||||
pub(super) fn new(namespaces: BTreeSet<Namespace>) -> Self {
|
||||
Self {
|
||||
namespaces: namespaces.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Namespaces {
|
||||
type Item = Namespace;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
+80
-114
@@ -1,67 +1,9 @@
|
||||
// This file is part of radicle-surf
|
||||
// <https://github.com/radicle-dev/radicle-surf>
|
||||
//
|
||||
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 3 or
|
||||
// later as published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use std::{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<Self, Self::Error> {
|
||||
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<Self, Error> {
|
||||
let refstring = RefString::try_from(name)?;
|
||||
Ok(Self(refstring))
|
||||
}
|
||||
|
||||
/// Access the string value of the `TagName`.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Returns the full ref name of the tag.
|
||||
pub fn refname(&self) -> String {
|
||||
format!("refs/tags/{}", self.name())
|
||||
}
|
||||
}
|
||||
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<String>,
|
||||
/// 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<Author>,
|
||||
/// The message with this `Tag`, if the `Tag` was annotated.
|
||||
message: Option<String>,
|
||||
/// If the tag is provided this holds the remote’s name.
|
||||
remote: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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<git2::Tag<'repo>> for Tag {
|
||||
type Error = Error;
|
||||
pub mod error {
|
||||
use std::str;
|
||||
|
||||
fn try_from(tag: git2::Tag) -> Result<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<git2::Tag<'repo>> for Tag {
|
||||
|
||||
Ok(Tag::Annotated {
|
||||
id,
|
||||
target_id,
|
||||
target,
|
||||
name,
|
||||
tagger,
|
||||
message,
|
||||
remote: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'repo> TryFrom<git2::Reference<'repo>> for Tag {
|
||||
type Error = Error;
|
||||
impl TryFrom<&git2::Reference<'_>> for Tag {
|
||||
type Error = error::FromReference;
|
||||
|
||||
fn try_from(reference: git2::Reference) -> Result<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<P>(
|
||||
pub fn blob(
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
maybe_revision: Option<Revision>,
|
||||
path: &str,
|
||||
) -> Result<Blob, Error>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
) -> Result<Blob, Error> {
|
||||
make_blob(repo, maybe_revision, path, content)
|
||||
}
|
||||
|
||||
fn make_blob<P, C>(
|
||||
fn make_blob<C>(
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
maybe_revision: Option<Revision>,
|
||||
path: &str,
|
||||
content: C,
|
||||
) -> Result<Blob, Error>
|
||||
where
|
||||
P: ToString,
|
||||
C: FnOnce(&[u8]) -> BlobContent,
|
||||
{
|
||||
let revision = maybe_revision.unwrap();
|
||||
|
||||
@@ -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<P>(
|
||||
pub fn tree(
|
||||
repo: &RepositoryRef,
|
||||
maybe_revision: Option<Revision<P>>,
|
||||
maybe_revision: Option<Revision>,
|
||||
maybe_prefix: Option<String>,
|
||||
) -> Result<Tree, Error>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
) -> Result<Tree, Error> {
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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<P, U> {
|
||||
@@ -51,20 +52,20 @@ pub enum Category<P, U> {
|
||||
serde(rename_all = "camelCase", tag = "type")
|
||||
)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Revision<P> {
|
||||
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<P>,
|
||||
remote: Option<RefString>,
|
||||
},
|
||||
/// Select a SHA1 under the name provided.
|
||||
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
|
||||
@@ -74,39 +75,31 @@ pub enum Revision<P> {
|
||||
},
|
||||
}
|
||||
|
||||
impl<P> git::Revision for &Revision<P>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
impl git::Revision for &Revision {
|
||||
fn object_id(&self, repo: &RepositoryRef) -> Result<Oid, Error> {
|
||||
match self {
|
||||
Revision::Tag { name } => {
|
||||
repo.refname_to_oid(git::TagName::new(name)?.refname().as_str())
|
||||
Revision::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<P> ToCommit for &Revision<P>
|
||||
where
|
||||
P: ToString,
|
||||
{
|
||||
impl ToCommit for &Revision {
|
||||
fn to_commit(self, repo: &RepositoryRef) -> Result<git::Commit, Error> {
|
||||
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<P, U> {
|
||||
@@ -114,10 +107,10 @@ pub struct Revisions<P, U> {
|
||||
pub peer_id: P,
|
||||
/// The user who owns these revisions.
|
||||
pub user: U,
|
||||
/// List of [`git::BranchName`].
|
||||
pub branches: NonEmpty<BranchName>,
|
||||
/// List of [`git::TagName`].
|
||||
pub tags: Vec<TagName>,
|
||||
/// List of branch reference names.
|
||||
pub branches: NonEmpty<RefString>,
|
||||
/// List of tag reference names.
|
||||
pub tags: Vec<RefString>,
|
||||
}
|
||||
|
||||
/// Provide the [`Revisions`] for the given `peer_id`, looking for the
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+98
-64
@@ -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<Branch> = vec![Branch::local("feature/#1194")];
|
||||
let expected_branches: Vec<Branch> = vec![Branch::local(refname!("feature/#1194"))];
|
||||
let mut branches = repo
|
||||
.branches(&Glob::heads("*")?)?
|
||||
.collect::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
branches.sort();
|
||||
|
||||
assert_eq!(expected_branches, branches);
|
||||
|
||||
let expected_branches: Vec<Branch> = vec![Branch::remote("feature/#1194", "fein")];
|
||||
let expected_branches: Vec<Branch> = vec![Branch::remote(
|
||||
component!("fein"),
|
||||
refname!("heads/feature/#1194"),
|
||||
)];
|
||||
let mut branches = repo
|
||||
.branches(&Glob::remotes("fein/*")?)?
|
||||
.collect::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
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<Branch> = vec![Branch::local("banana"), Branch::local("master")];
|
||||
let expected_branches: Vec<Branch> = vec![
|
||||
Branch::local(refname!("banana")),
|
||||
Branch::local(refname!("master")),
|
||||
];
|
||||
let mut branches = repo
|
||||
.branches(&Glob::heads("*")?)?
|
||||
.collect::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
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<Branch> = 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::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
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<Branch> = vec![Branch::local("master")];
|
||||
let expected_branches: Vec<Branch> = vec![Branch::local(refname!("master"))];
|
||||
let mut branches = repo
|
||||
.branches(&Glob::heads("*")?.and_remotes("*")?)?
|
||||
.collect::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
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::<Result<Vec<Branch>, Error>>()?;
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
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<Value = Branch> {
|
||||
fn gen_branch() -> impl Strategy<Value = Branch> {
|
||||
prop_oneof![
|
||||
any::<String>().prop_map(|name| Branch {
|
||||
name: BranchName::new(&name),
|
||||
locality: BranchType::Local
|
||||
}),
|
||||
(any::<String>(), any::<String>()).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::<Result<Vec<Tag>, Error>>()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.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();
|
||||
|
||||
Reference in New Issue
Block a user