Radish alpha
r
rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt
Git libraries for Radicle
Radicle
Git
radicle-git-ext: merge in git-commit
Han Xu committed 3 years ago
commit c8446051216b50a330611a8ba320aa17b8e81dcb
parent a3a63e4
24 files changed +890 -2169
modified Cargo.toml
@@ -6,8 +6,6 @@ members = [
  "git-trailers",
  "link-git",
  "radicle-git-ext",
-
  "radicle-git-types",
-
  "radicle-macros",
  "radicle-std-ext",
  "radicle-surf",
  # TODO: port gitd-lib over
modified git-storage/src/glob.rs
@@ -5,7 +5,6 @@

use std::path::Path;

-
use git_ext as ext;
use git_ref_format::refspec::PatternString;

pub trait Pattern {
@@ -27,12 +26,6 @@ impl Pattern for globset::GlobSet {
#[derive(Clone, Debug)]
pub struct RefspecMatcher(globset::GlobMatcher);

-
impl From<ext::RefspecPattern> for RefspecMatcher {
-
    fn from(pat: ext::RefspecPattern) -> Self {
-
        Self(globset::Glob::new(pat.as_str()).unwrap().compile_matcher())
-
    }
-
}
-

impl From<PatternString> for RefspecMatcher {
    fn from(pat: PatternString) -> Self {
        Self(globset::Glob::new(pat.as_str()).unwrap().compile_matcher())
modified git-storage/t/Cargo.toml
@@ -35,7 +35,7 @@ path = "../../test/test-helpers"
version = "1"
features = ["v4"]

-
[dev-dependencies.git-ext-test]
+
[dev-dependencies.radicle-git-ext-test]
path = "../../radicle-git-ext/t"
features = ["test"]

added radicle-git-ext/src/author.rs
@@ -0,0 +1,129 @@
+
use std::{
+
    fmt,
+
    num::ParseIntError,
+
    str::{self, FromStr},
+
};
+

+
use thiserror::Error;
+

+
/// The data for indicating authorship of an action within a
+
/// [`crate::commit::Commit`].
+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+
pub struct Author {
+
    /// Name corresponding to `user.name` in the git config.
+
    ///
+
    /// Note: this must not contain `<` or `>`.
+
    pub name: String,
+
    /// Email corresponding to `user.email` in the git config.
+
    ///
+
    /// Note: this must not contain `<` or `>`.
+
    pub email: String,
+
    /// The time of this author's action.
+
    pub time: Time,
+
}
+

+
/// The time of a [`Author`]'s action.
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+
pub struct Time {
+
    seconds: i64,
+
    offset: i32,
+
}
+

+
impl Time {
+
    pub fn new(seconds: i64, offset: i32) -> Self {
+
        Self { seconds, offset }
+
    }
+

+
    /// Return the time, in seconds, since the epoch.
+
    pub fn seconds(&self) -> i64 {
+
        self.seconds
+
    }
+

+
    /// Return the timezone offset, in minutes.
+
    pub fn offset(&self) -> i32 {
+
        self.offset
+
    }
+
}
+

+
impl From<Time> for git2::Time {
+
    fn from(t: Time) -> Self {
+
        Self::new(t.seconds, t.offset)
+
    }
+
}
+

+
impl From<git2::Time> for Time {
+
    fn from(t: git2::Time) -> Self {
+
        Self::new(t.seconds(), t.offset_minutes())
+
    }
+
}
+

+
impl fmt::Display for Time {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        let sign = if self.offset.is_negative() { '-' } else { '+' };
+
        write!(f, "{} {}{:0>4}", self.seconds, sign, self.offset.abs())
+
    }
+
}
+

+
impl fmt::Display for Author {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        write!(f, "{} <{}> {}", self.name, self.email, self.time,)
+
    }
+
}
+

+
impl TryFrom<&Author> for git2::Signature<'_> {
+
    type Error = git2::Error;
+

+
    fn try_from(person: &Author) -> Result<Self, Self::Error> {
+
        let time = git2::Time::new(person.time.seconds, person.time.offset);
+
        git2::Signature::new(&person.name, &person.email, &time)
+
    }
+
}
+

+
impl<'a> TryFrom<&git2::Signature<'a>> for Author {
+
    type Error = str::Utf8Error;
+

+
    fn try_from(value: &git2::Signature<'a>) -> Result<Self, Self::Error> {
+
        Ok(Self {
+
            name: str::from_utf8(value.name_bytes())?.to_string(),
+
            email: str::from_utf8(value.email_bytes())?.to_string(),
+
            time: value.when().into(),
+
        })
+
    }
+
}
+

+
#[derive(Debug, Error)]
+
pub enum ParseError {
+
    #[error("missing '{0}' while parsing person signature")]
+
    Missing(&'static str),
+
    #[error("offset was incorrect format while parsing person signature")]
+
    Offset(#[source] ParseIntError),
+
    #[error("time was incorrect format while parsing person signature")]
+
    Time(#[source] ParseIntError),
+
    #[error("time offset is expected to be '+'/'-' for a person siganture")]
+
    UnknownOffset,
+
}
+

+
impl FromStr for Author {
+
    type Err = ParseError;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        let mut components = s.split(' ');
+
        let offset = match components.next_back() {
+
            None => return Err(ParseError::Missing("offset")),
+
            Some(offset) => offset.parse::<i32>().map_err(ParseError::Offset)?,
+
        };
+
        let time = match components.next_back() {
+
            None => return Err(ParseError::Missing("time")),
+
            Some(time) => time.parse::<i64>().map_err(ParseError::Time)?,
+
        };
+
        let time = Time::new(time, offset);
+

+
        let email = components
+
            .next_back()
+
            .ok_or(ParseError::Missing("email"))?
+
            .trim_matches(|c| c == '<' || c == '>')
+
            .to_owned();
+
        let name = components.collect::<Vec<_>>().join(" ");
+
        Ok(Self { name, email, time })
+
    }
+
}
added radicle-git-ext/src/commit.rs
@@ -0,0 +1,294 @@
+
//! The `git-commit` crate provides parsing a displaying of a [git
+
//! commit][git-commit].
+
//!
+
//! The [`Commit`] data can be constructed using the `FromStr`
+
//! implementation, or by converting from a `git2::Buf`.
+
//!
+
//! The [`Headers`] can be accessed via [`Commit::headers`]. If the
+
//! signatures of the commit are of particular interest, the
+
//! [`Commit::signatures`] method can be used, which returns a series of
+
//! [`Signature`]s.
+
//!
+
//! [git-commit]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
+

+
pub mod headers;
+
pub mod trailers;
+

+
use std::{
+
    fmt::Write as _,
+
    str::{self, FromStr},
+
};
+

+
use git2::{ObjectType, Oid};
+

+
use headers::{Headers, Signature};
+
use trailers::{OwnedTrailer, Trailer, Trailers};
+

+
use crate::author::Author;
+

+
/// A git commit in its object description form, i.e. the output of
+
/// `git cat-file` for a commit object.
+
#[derive(Debug)]
+
pub struct Commit {
+
    tree: Oid,
+
    parents: Vec<Oid>,
+
    author: Author,
+
    committer: Author,
+
    headers: Headers,
+
    message: String,
+
    trailers: Vec<OwnedTrailer>,
+
}
+

+
impl Commit {
+
    pub fn new<I, T>(
+
        tree: Oid,
+
        parents: Vec<Oid>,
+
        author: Author,
+
        committer: Author,
+
        headers: Headers,
+
        message: String,
+
        trailers: I,
+
    ) -> Self
+
    where
+
        I: IntoIterator<Item = T>,
+
        OwnedTrailer: From<T>,
+
    {
+
        let trailers = trailers.into_iter().map(OwnedTrailer::from).collect();
+
        Self {
+
            tree,
+
            parents,
+
            author,
+
            committer,
+
            headers,
+
            message,
+
            trailers,
+
        }
+
    }
+

+
    /// Read the [`Commit`] from the `repo` that is expected to be found at
+
    /// `oid`.
+
    pub fn read(repo: &git2::Repository, oid: Oid) -> Result<Self, error::Read> {
+
        let odb = repo.odb()?;
+
        let object = odb.read(oid)?;
+
        Ok(Commit::try_from(object.data())?)
+
    }
+

+
    /// Write the given [`Commit`] to the `repo`. The resulting `Oid`
+
    /// is the identifier for this commit.
+
    pub fn write(&self, repo: &git2::Repository) -> Result<Oid, git2::Error> {
+
        let odb = repo.odb()?;
+
        odb.write(ObjectType::Commit, self.to_string().as_bytes())
+
    }
+

+
    /// The tree [`Oid`] this commit points to.
+
    pub fn tree(&self) -> Oid {
+
        self.tree
+
    }
+

+
    /// The parent [`Oid`]s of this commit.
+
    pub fn parents(&self) -> impl Iterator<Item = Oid> + '_ {
+
        self.parents.iter().copied()
+
    }
+

+
    /// The author of this commit, i.e. the header corresponding to `author`.
+
    pub fn author(&self) -> &Author {
+
        &self.author
+
    }
+

+
    /// The committer of this commit, i.e. the header corresponding to
+
    /// `committer`.
+
    pub fn committer(&self) -> &Author {
+
        &self.committer
+
    }
+

+
    /// The message body of this commit.
+
    pub fn message(&self) -> &str {
+
        &self.message
+
    }
+

+
    /// The [`Signature`]s found in this commit, i.e. the headers corresponding
+
    /// to `gpgsig`.
+
    pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
+
        self.headers.signatures()
+
    }
+

+
    /// The [`Headers`] found in this commit.
+
    ///
+
    /// Note: these do not include `tree`, `parent`, `author`, and `committer`.
+
    pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
+
        self.headers.iter()
+
    }
+

+
    /// Iterate over the [`Headers`] values that match the provided `name`.
+
    pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + '_ {
+
        self.headers.values(name)
+
    }
+

+
    /// Push a header to the end of the headers section.
+
    pub fn push_header(&mut self, name: &str, value: &str) {
+
        self.headers.push(name, value.trim());
+
    }
+

+
    pub fn trailers(&self) -> impl Iterator<Item = &OwnedTrailer> {
+
        self.trailers.iter()
+
    }
+
}
+

+
pub mod error {
+
    use std::str;
+

+
    use thiserror::Error;
+

+
    use crate::author;
+

+
    #[derive(Debug, Error)]
+
    pub enum Read {
+
        #[error(transparent)]
+
        Git(#[from] git2::Error),
+
        #[error(transparent)]
+
        Parse(#[from] Parse),
+
    }
+

+
    #[derive(Debug, Error)]
+
    pub enum Parse {
+
        #[error(transparent)]
+
        Author(#[from] author::ParseError),
+
        #[error("invalid '{header}'")]
+
        InvalidHeader {
+
            header: &'static str,
+
            #[source]
+
            err: git2::Error,
+
        },
+
        #[error("invalid git commit object format")]
+
        InvalidFormat,
+
        #[error("missing '{0}' while parsing commit")]
+
        Missing(&'static str),
+
        #[error("error occurred while checking for git-trailers: {0}")]
+
        Trailers(#[source] git2::Error),
+
        #[error(transparent)]
+
        Utf8(#[from] str::Utf8Error),
+
    }
+
}
+

+
impl TryFrom<git2::Buf> for Commit {
+
    type Error = error::Parse;
+

+
    fn try_from(value: git2::Buf) -> Result<Self, Self::Error> {
+
        value.as_str().ok_or(error::Parse::InvalidFormat)?.parse()
+
    }
+
}
+

+
impl TryFrom<&[u8]> for Commit {
+
    type Error = error::Parse;
+

+
    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
+
        Commit::from_str(str::from_utf8(data)?)
+
    }
+
}
+

+
impl FromStr for Commit {
+
    type Err = error::Parse;
+

+
    fn from_str(buffer: &str) -> Result<Self, Self::Err> {
+
        let (header, message) = buffer
+
            .split_once("\n\n")
+
            .ok_or(error::Parse::InvalidFormat)?;
+
        let mut lines = header.lines();
+

+
        let tree = match lines.next() {
+
            Some(tree) => tree
+
                .strip_prefix("tree ")
+
                .map(git2::Oid::from_str)
+
                .transpose()
+
                .map_err(|err| error::Parse::InvalidHeader {
+
                    header: "tree",
+
                    err,
+
                })?
+
                .ok_or(error::Parse::Missing("tree"))?,
+
            None => return Err(error::Parse::Missing("tree")),
+
        };
+

+
        let mut parents = Vec::new();
+
        let mut author: Option<Author> = None;
+
        let mut committer: Option<Author> = None;
+
        let mut headers = Headers::new();
+

+
        for line in lines {
+
            // Check if a signature is still being parsed
+
            if let Some(rest) = line.strip_prefix(' ') {
+
                let value: &mut String = headers
+
                    .0
+
                    .last_mut()
+
                    .map(|(_, v)| v)
+
                    .ok_or(error::Parse::InvalidFormat)?;
+
                value.push('\n');
+
                value.push_str(rest);
+
                continue;
+
            }
+

+
            if let Some((name, value)) = line.split_once(' ') {
+
                match name {
+
                    "parent" => parents.push(git2::Oid::from_str(value).map_err(|err| {
+
                        error::Parse::InvalidHeader {
+
                            header: "parent",
+
                            err,
+
                        }
+
                    })?),
+
                    "author" => author = Some(value.parse::<Author>()?),
+
                    "committer" => committer = Some(value.parse::<Author>()?),
+
                    _ => headers.push(name, value),
+
                }
+
                continue;
+
            }
+
        }
+

+
        let trailers = Trailers::parse(message).map_err(error::Parse::Trailers)?;
+

+
        let message = message
+
            .strip_suffix(&trailers.to_string(": "))
+
            .unwrap_or(message)
+
            .to_string();
+

+
        let trailers = trailers.iter().map(OwnedTrailer::from).collect();
+

+
        Ok(Self {
+
            tree,
+
            parents,
+
            author: author.ok_or(error::Parse::Missing("author"))?,
+
            committer: committer.ok_or(error::Parse::Missing("committer"))?,
+
            headers,
+
            message,
+
            trailers,
+
        })
+
    }
+
}
+

+
impl ToString for Commit {
+
    fn to_string(&self) -> String {
+
        let mut buf = String::new();
+

+
        writeln!(buf, "tree {}", self.tree).ok();
+

+
        for parent in &self.parents {
+
            writeln!(buf, "parent {parent}").ok();
+
        }
+

+
        writeln!(buf, "author {}", self.author).ok();
+
        writeln!(buf, "committer {}", self.committer).ok();
+

+
        for (name, value) in self.headers.iter() {
+
            writeln!(buf, "{name} {}", value.replace('\n', "\n ")).ok();
+
        }
+
        writeln!(buf).ok();
+
        write!(buf, "{}", self.message.trim()).ok();
+
        writeln!(buf).ok();
+

+
        if !self.trailers.is_empty() {
+
            writeln!(buf).ok();
+
        }
+
        for trailer in self.trailers.iter() {
+
            writeln!(buf, "{}", Trailer::from(trailer).display(": ")).ok();
+
        }
+
        buf
+
    }
+
}
added radicle-git-ext/src/commit/headers.rs
@@ -0,0 +1,71 @@
+
use std::borrow::Cow;
+

+
const BEGIN_SSH: &str = "-----BEGIN SSH SIGNATURE-----\n";
+
const BEGIN_PGP: &str = "-----BEGIN PGP SIGNATURE-----\n";
+

+
/// A collection of headers stored in a [`crate::commit::Commit`].
+
///
+
/// Note: these do not include `tree`, `parent`, `author`, and `committer`.
+
#[derive(Clone, Debug, Default)]
+
pub struct Headers(pub(super) Vec<(String, String)>);
+

+
/// A `gpgsig` signature stored in a [`crate::commit::Commit`].
+
pub enum Signature<'a> {
+
    /// A PGP signature, i.e. starts with `-----BEGIN PGP SIGNATURE-----`.
+
    Pgp(Cow<'a, str>),
+
    /// A SSH signature, i.e. starts with `-----BEGIN SSH SIGNATURE-----`.
+
    Ssh(Cow<'a, str>),
+
}
+

+
impl<'a> Signature<'a> {
+
    fn from_str(s: &'a str) -> Result<Self, UnknownScheme> {
+
        if s.starts_with(BEGIN_SSH) {
+
            Ok(Signature::Ssh(Cow::Borrowed(s)))
+
        } else if s.starts_with(BEGIN_PGP) {
+
            Ok(Signature::Pgp(Cow::Borrowed(s)))
+
        } else {
+
            Err(UnknownScheme)
+
        }
+
    }
+
}
+

+
pub struct UnknownScheme;
+

+
impl<'a> ToString for Signature<'a> {
+
    fn to_string(&self) -> String {
+
        match self {
+
            Signature::Pgp(pgp) => pgp.to_string(),
+
            Signature::Ssh(ssh) => ssh.to_string(),
+
        }
+
    }
+
}
+

+
impl Headers {
+
    pub fn new() -> Self {
+
        Headers(Vec::new())
+
    }
+

+
    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
+
        self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
+
    }
+

+
    pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + '_ {
+
        self.iter()
+
            .filter_map(move |(k, v)| (k == name).then_some(v))
+
    }
+

+
    pub fn signatures(&self) -> impl Iterator<Item = Signature> + '_ {
+
        self.0.iter().filter_map(|(k, v)| {
+
            if k == "gpgsig" {
+
                Signature::from_str(v).ok()
+
            } else {
+
                None
+
            }
+
        })
+
    }
+

+
    /// Push a header to the end of the headers section.
+
    pub fn push(&mut self, name: &str, value: &str) {
+
        self.0.push((name.to_owned(), value.trim().to_owned()));
+
    }
+
}
added radicle-git-ext/src/commit/trailers.rs
@@ -0,0 +1,214 @@
+
use std::{borrow::Cow, fmt, fmt::Write, ops::Deref, str::FromStr};
+

+
use git2::{MessageTrailersStrs, MessageTrailersStrsIterator};
+

+
/// A Git commit's set of trailers that are left in the commit's
+
/// message.
+
///
+
/// Trailers are key/value pairs in the last paragraph of a message,
+
/// not including any patches or conflicts that may be present.
+
///
+
/// # Usage
+
///
+
/// To construct `Trailers`, you can use [`Trailers::parse`] or its
+
/// `FromStr` implementation.
+
///
+
/// To iterate over the trailers, you can use [`Trailers::iter`].
+
///
+
/// To render the trailers to a `String`, you can use
+
/// [`Trailers::to_string`] or its `Display` implementation (note that
+
/// it will default to using `": "` as the separator.
+
///
+
/// # Examples
+
///
+
/// ```text
+
/// Add new functionality
+
///
+
/// Making code better with new functionality.
+
///
+
/// X-Signed-Off-By: Alex Sellier
+
/// X-Co-Authored-By: Fintan Halpenny
+
/// ```
+
///
+
/// The trailers in the above example are:
+
///
+
/// ```text
+
/// X-Signed-Off-By: Alex Sellier
+
/// X-Co-Authored-By: Fintan Halpenny
+
/// ```
+
pub struct Trailers {
+
    inner: MessageTrailersStrs,
+
}
+

+
impl Trailers {
+
    pub fn parse(message: &str) -> Result<Self, git2::Error> {
+
        Ok(Self {
+
            inner: git2::message_trailers_strs(message)?,
+
        })
+
    }
+

+
    pub fn iter(&self) -> Iter<'_> {
+
        Iter {
+
            inner: self.inner.iter(),
+
        }
+
    }
+

+
    pub fn to_string<'a, S>(&self, sep: S) -> String
+
    where
+
        S: Separator<'a>,
+
    {
+
        let mut buf = String::new();
+
        for (i, trailer) in self.iter().enumerate() {
+
            if i > 0 {
+
                writeln!(buf).ok();
+
            }
+

+
            write!(buf, "{}", trailer.display(sep.sep_for(&trailer.token))).ok();
+
        }
+
        writeln!(buf).ok();
+
        buf
+
    }
+
}
+

+
impl fmt::Display for Trailers {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        f.write_str(&self.to_string(": "))
+
    }
+
}
+

+
pub trait Separator<'a> {
+
    fn sep_for(&self, token: &Token) -> &'a str;
+
}
+

+
impl<'a> Separator<'a> for &'a str {
+
    fn sep_for(&self, _: &Token) -> &'a str {
+
        self
+
    }
+
}
+

+
impl<'a, F> Separator<'a> for F
+
where
+
    F: Fn(&Token) -> &'a str,
+
{
+
    fn sep_for(&self, token: &Token) -> &'a str {
+
        self(token)
+
    }
+
}
+

+
impl FromStr for Trailers {
+
    type Err = git2::Error;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        Self::parse(s)
+
    }
+
}
+

+
pub struct Iter<'a> {
+
    inner: MessageTrailersStrsIterator<'a>,
+
}
+

+
impl<'a> Iterator for Iter<'a> {
+
    type Item = Trailer<'a>;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        let (token, value) = self.inner.next()?;
+
        Some(Trailer {
+
            token: Token(token),
+
            value: Cow::Borrowed(value),
+
        })
+
    }
+
}
+

+
#[derive(Debug, Clone, Eq, PartialEq)]
+
pub struct Token<'a>(&'a str);
+

+
impl Deref for Token<'_> {
+
    type Target = str;
+

+
    fn deref(&self) -> &Self::Target {
+
        self.0
+
    }
+
}
+

+
pub struct Display<'a> {
+
    trailer: &'a Trailer<'a>,
+
    separator: &'a str,
+
}
+

+
impl<'a> fmt::Display for Display<'a> {
+
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+
        write!(
+
            f,
+
            "{}{}{}",
+
            self.trailer.token.deref(),
+
            self.separator,
+
            self.trailer.value,
+
        )
+
    }
+
}
+

+
/// A trailer is a key/value pair found in the last paragraph of a Git
+
/// commit message, not including any patches or conflicts that may be
+
/// present.
+
#[derive(Debug, Clone, Eq, PartialEq)]
+
pub struct Trailer<'a> {
+
    pub token: Token<'a>,
+
    pub value: Cow<'a, str>,
+
}
+

+
impl<'a> Trailer<'a> {
+
    pub fn display(&'a self, separator: &'a str) -> Display<'a> {
+
        Display {
+
            trailer: self,
+
            separator,
+
        }
+
    }
+

+
    pub fn to_owned(&self) -> OwnedTrailer {
+
        OwnedTrailer::from(self)
+
    }
+
}
+

+
/// A version of the [`Trailer`] which owns its token and
+
/// value. Useful for when you need to carry trailers around in a long
+
/// lived data structure.
+
#[derive(Debug)]
+
pub struct OwnedTrailer {
+
    pub token: OwnedToken,
+
    pub value: String,
+
}
+

+
#[derive(Debug)]
+
pub struct OwnedToken(String);
+

+
impl Deref for OwnedToken {
+
    type Target = str;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.0
+
    }
+
}
+

+
impl<'a> From<&Trailer<'a>> for OwnedTrailer {
+
    fn from(t: &Trailer<'a>) -> Self {
+
        OwnedTrailer {
+
            token: OwnedToken(t.token.0.to_string()),
+
            value: t.value.to_string(),
+
        }
+
    }
+
}
+

+
impl<'a> From<Trailer<'a>> for OwnedTrailer {
+
    fn from(t: Trailer<'a>) -> Self {
+
        (&t).into()
+
    }
+
}
+

+
impl<'a> From<&'a OwnedTrailer> for Trailer<'a> {
+
    fn from(t: &'a OwnedTrailer) -> Self {
+
        Trailer {
+
            token: Token(t.token.0.as_str()),
+
            value: Cow::from(&t.value),
+
        }
+
    }
+
}
modified radicle-git-ext/src/lib.rs
@@ -5,10 +5,11 @@

//! Extensions and wrappers for `git2` types

+
pub mod author;
pub mod blob;
+
pub mod commit;
pub mod error;
pub mod oid;
-
pub mod reference;
pub mod revwalk;
pub mod transport;
pub mod tree;
@@ -16,7 +17,6 @@ pub mod tree;
pub use blob::*;
pub use error::*;
pub use oid::*;
-
pub use reference::*;
pub use revwalk::*;
pub use transport::*;
pub use tree::Tree;
deleted radicle-git-ext/src/reference.rs
@@ -1,26 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::convert::TryFrom as _;
-

-
mod iter;
-
pub use iter::{ReferenceNames, References};
-

-
pub mod name;
-
pub use name::{OneLevel, Qualified, RefLike, RefspecPattern};
-

-
pub mod check {
-
    pub use git_ref_format::{check_ref_format as ref_format, Error, Options};
-
}
-

-
pub fn peeled(head: git2::Reference) -> Option<(String, git2::Oid)> {
-
    head.name()
-
        .and_then(|name| head.target().map(|target| (name.to_owned(), target)))
-
}
-

-
pub fn refined((name, oid): (&str, git2::Oid)) -> Result<(OneLevel, crate::Oid), name::Error> {
-
    let name = RefLike::try_from(name)?;
-
    Ok((OneLevel::from(name), oid.into()))
-
}
deleted radicle-git-ext/src/reference/iter.rs
@@ -1,87 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
/// Iterator chaining multiple [`git2::References`]
-
#[must_use = "iterators are lazy and do nothing unless consumed"]
-
pub struct References<'a> {
-
    inner: Vec<git2::References<'a>>,
-
}
-

-
impl<'a> References<'a> {
-
    pub fn new(refs: impl IntoIterator<Item = git2::References<'a>>) -> Self {
-
        Self {
-
            inner: refs.into_iter().collect(),
-
        }
-
    }
-

-
    pub fn from_globs(
-
        repo: &'a git2::Repository,
-
        globs: impl IntoIterator<Item = impl AsRef<str>>,
-
    ) -> Result<Self, git2::Error> {
-
        let globs = globs.into_iter();
-
        let mut iters = globs
-
            .size_hint()
-
            .1
-
            .map(Vec::with_capacity)
-
            .unwrap_or_else(Vec::new);
-
        for glob in globs {
-
            let iter = repo.references_glob(glob.as_ref())?;
-
            iters.push(iter);
-
        }
-

-
        Ok(Self::new(iters))
-
    }
-

-
    pub fn names<'b>(&'b mut self) -> ReferenceNames<'a, 'b> {
-
        ReferenceNames {
-
            inner: self.inner.iter_mut().map(|refs| refs.names()).collect(),
-
        }
-
    }
-

-
    pub fn peeled(self) -> impl Iterator<Item = (String, git2::Oid)> + 'a {
-
        self.filter_map(|reference| {
-
            reference.ok().and_then(|head| {
-
                head.name().and_then(|name| {
-
                    head.target()
-
                        .map(|target| (name.to_owned(), target.to_owned()))
-
                })
-
            })
-
        })
-
    }
-
}
-

-
impl<'a> Iterator for References<'a> {
-
    type Item = Result<git2::Reference<'a>, git2::Error>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        self.inner.pop().and_then(|mut iter| match iter.next() {
-
            None => self.next(),
-
            Some(item) => {
-
                self.inner.push(iter);
-
                Some(item)
-
            },
-
        })
-
    }
-
}
-

-
/// Iterator chaining multiple [`git2::ReferenceNames`]
-
#[must_use = "iterators are lazy and do nothing unless consumed"]
-
pub struct ReferenceNames<'repo, 'references> {
-
    inner: Vec<git2::ReferenceNames<'repo, 'references>>,
-
}
-

-
impl<'a, 'b> Iterator for ReferenceNames<'a, 'b> {
-
    type Item = Result<&'b str, git2::Error>;
-

-
    fn next(&mut self) -> Option<Self::Item> {
-
        self.inner.pop().and_then(|mut iter| match iter.next() {
-
            None => self.next(),
-
            Some(item) => {
-
                self.inner.push(iter);
-
                Some(item)
-
            },
-
        })
-
    }
-
}
deleted radicle-git-ext/src/reference/name.rs
@@ -1,637 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    convert::TryFrom,
-
    fmt::{self, Display},
-
    iter::FromIterator,
-
    ops::Deref,
-
    path::Path,
-
    str::{self, FromStr},
-
};
-

-
pub use percent_encoding::PercentEncode;
-
use thiserror::Error;
-

-
use super::check;
-

-
#[derive(Debug, Error)]
-
#[non_exhaustive]
-
pub enum Error {
-
    #[error("invalid utf8")]
-
    Utf8,
-

-
    #[error("not a valid git ref name or pattern")]
-
    RefFormat(#[from] check::Error),
-
}
-

-
impl Error {
-
    pub const fn empty() -> Self {
-
        Self::RefFormat(check::Error::Empty)
-
    }
-
}
-

-
#[derive(Debug, Error)]
-
#[non_exhaustive]
-
pub enum StripPrefixError {
-
    #[error("prefix is equal to path")]
-
    ImproperPrefix,
-

-
    #[error("not prefixed by given path")]
-
    NotPrefix,
-
}
-

-
/// An owned path-like value which is a valid git refname.
-
///
-
/// See [`git-check-ref-format`] for what the rules for refnames are --
-
/// conversion functions behave as if `--allow-onelevel` was given.
-
/// Additionally, we impose the rule that the name must consist of valid utf8.
-
///
-
/// Note that refspec patterns (eg. "refs/heads/*") are not allowed (see
-
/// [`RefspecPattern`]), and that the maximum length of the name is 1024 bytes.
-
///
-
/// [`git-check-ref-format`]: https://git-scm.com/docs/git-check-ref-format
-
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
-
#[cfg_attr(
-
    feature = "serde",
-
    derive(serde::Serialize, serde::Deserialize),
-
    serde(into = "String", try_from = "String")
-
)]
-
pub struct RefLike(String);
-

-
impl RefLike {
-
    /// Append the path in `Other` to `self.
-
    pub fn join<Other: Into<Self>>(&self, other: Other) -> Self {
-
        Self(format!("{}/{}", self.0, other.into().0))
-
    }
-

-
    /// Append a [`RefspecPattern`], yielding a [`RefspecPattern`]
-
    pub fn with_pattern_suffix<Suf: Into<RefspecPattern>>(&self, suf: Suf) -> RefspecPattern {
-
        RefspecPattern(format!("{}/{}", self.0, suf.into().0))
-
    }
-

-
    /// Returns a [`RefLike`] that, when joined onto `base`, yields `self`.
-
    ///
-
    /// # Errors
-
    ///
-
    /// If `base` is not a prefix of `self`, or `base` equals the path in `self`
-
    /// (ie. the result would be the empty path, which is not a valid
-
    /// [`RefLike`]).
-
    pub fn strip_prefix<P: AsRef<str>>(&self, base: P) -> Result<Self, StripPrefixError> {
-
        let base = base.as_ref();
-
        let base = format!("{}/", base.strip_suffix('/').unwrap_or(base));
-
        self.0
-
            .strip_prefix(&base)
-
            .ok_or(StripPrefixError::NotPrefix)
-
            .and_then(|path| {
-
                if path.is_empty() {
-
                    Err(StripPrefixError::ImproperPrefix)
-
                } else {
-
                    Ok(Self(path.into()))
-
                }
-
            })
-
    }
-

-
    pub fn as_str(&self) -> &str {
-
        self.as_ref()
-
    }
-

-
    pub fn percent_encode(&self) -> PercentEncode {
-
        /// https://url.spec.whatwg.org/#fragment-percent-encode-set
-
        const FRAGMENT_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet =
-
            &percent_encoding::CONTROLS
-
                .add(b' ')
-
                .add(b'"')
-
                .add(b'<')
-
                .add(b'>')
-
                .add(b'`');
-

-
        /// https://url.spec.whatwg.org/#path-percent-encode-set
-
        const PATH_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &FRAGMENT_PERCENT_ENCODE_SET
-
            .add(b'#')
-
            .add(b'?')
-
            .add(b'{')
-
            .add(b'}');
-

-
        percent_encoding::utf8_percent_encode(self.as_str(), PATH_PERCENT_ENCODE_SET)
-
    }
-
}
-

-
impl Deref for RefLike {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl AsRef<str> for RefLike {
-
    fn as_ref(&self) -> &str {
-
        &self.0
-
    }
-
}
-

-
impl TryFrom<&str> for RefLike {
-
    type Error = Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        check::ref_format(
-
            check::Options {
-
                allow_onelevel: true,
-
                allow_pattern: false,
-
            },
-
            s,
-
        )?;
-
        Ok(Self(s.to_owned()))
-
    }
-
}
-

-
impl TryFrom<&[u8]> for RefLike {
-
    type Error = Error;
-

-
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-
        str::from_utf8(bytes)
-
            .or(Err(Error::Utf8))
-
            .and_then(Self::try_from)
-
    }
-
}
-

-
impl FromStr for RefLike {
-
    type Err = Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl TryFrom<String> for RefLike {
-
    type Error = Error;
-

-
    fn try_from(s: String) -> Result<Self, Self::Error> {
-
        Self::try_from(s.as_str())
-
    }
-
}
-

-
impl TryFrom<&Path> for RefLike {
-
    type Error = Error;
-

-
    #[cfg(target_family = "windows")]
-
    fn try_from(p: &Path) -> Result<Self, Self::Error> {
-
        use std::{convert::TryInto as _, path::Component::Normal};
-

-
        p.components()
-
            .filter_map(|comp| match comp {
-
                Normal(s) => Some(s),
-
                _ => None,
-
            })
-
            .map(|os| os.to_str().ok_or(Error::Utf8))
-
            .collect::<Result<Vec<_>, Self::Error>>()?
-
            .join("/")
-
            .try_into()
-
    }
-

-
    #[cfg(target_family = "unix")]
-
    fn try_from(p: &Path) -> Result<Self, Self::Error> {
-
        Self::try_from(p.to_str().ok_or(Error::Utf8)?)
-
    }
-
}
-

-
impl From<&RefLike> for RefLike {
-
    fn from(me: &RefLike) -> Self {
-
        me.clone()
-
    }
-
}
-

-
impl From<git_ref_format::RefString> for RefLike {
-
    #[inline]
-
    fn from(r: git_ref_format::RefString) -> Self {
-
        Self(r.into())
-
    }
-
}
-

-
impl From<&git_ref_format::RefString> for RefLike {
-
    #[inline]
-
    fn from(r: &git_ref_format::RefString) -> Self {
-
        Self::from(r.as_refstr())
-
    }
-
}
-

-
impl From<&git_ref_format::RefStr> for RefLike {
-
    #[inline]
-
    fn from(r: &git_ref_format::RefStr) -> Self {
-
        Self(r.to_owned().into())
-
    }
-
}
-

-
impl From<RefLike> for String {
-
    fn from(RefLike(path): RefLike) -> Self {
-
        path
-
    }
-
}
-

-
impl FromIterator<Self> for RefLike {
-
    fn from_iter<T>(iter: T) -> Self
-
    where
-
        T: IntoIterator<Item = Self>,
-
    {
-
        Self(iter.into_iter().map(|x| x.0).collect::<Vec<_>>().join("/"))
-
    }
-
}
-

-
impl Display for RefLike {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(self.as_str())
-
    }
-
}
-

-
/// A [`RefLike`] without a "refs/" prefix.
-
///
-
/// Conversion functions strip the first **two** path components iff the path
-
/// starts with `refs/`.
-
///
-
/// Note that the [`serde::Deserialize`] impl thusly implies that input in
-
/// [`Qualified`] form is accepted, and silently converted.
-
///
-
/// # Examples
-
///
-
/// ```rust
-
/// use std::convert::TryFrom;
-
/// use radicle_git_ext::reference::name::*;
-
///
-
/// assert_eq!(
-
///     &*OneLevel::from(RefLike::try_from("refs/heads/next").unwrap()),
-
///     "next"
-
/// );
-
///
-
/// assert_eq!(
-
///     &*OneLevel::from(RefLike::try_from("refs/remotes/origin/it").unwrap()),
-
///     "origin/it"
-
/// );
-
///
-
/// assert_eq!(
-
///     &*OneLevel::from(RefLike::try_from("mistress").unwrap()),
-
///     "mistress"
-
/// );
-
///
-
/// assert_eq!(
-
///     OneLevel::from_qualified(Qualified::from(RefLike::try_from("refs/tags/grace").unwrap())),
-
///     (
-
///         OneLevel::from(RefLike::try_from("grace").unwrap()),
-
///         Some(RefLike::try_from("tags").unwrap())
-
///     ),
-
/// );
-
///
-
/// assert_eq!(
-
///     OneLevel::from_qualified(Qualified::from(RefLike::try_from("refs/remotes/origin/hopper").unwrap())),
-
///     (
-
///         OneLevel::from(RefLike::try_from("origin/hopper").unwrap()),
-
///         Some(RefLike::try_from("remotes").unwrap())
-
///     ),
-
/// );
-
///
-
/// assert_eq!(
-
///     OneLevel::from_qualified(Qualified::from(RefLike::try_from("refs/HEAD").unwrap())),
-
///     (OneLevel::from(RefLike::try_from("HEAD").unwrap()), None)
-
/// );
-
///
-
/// assert_eq!(
-
///     &*OneLevel::from(RefLike::try_from("origin/hopper").unwrap()).into_qualified(
-
///         RefLike::try_from("remotes").unwrap()
-
///     ),
-
///     "refs/remotes/origin/hopper",
-
/// );
-
/// ```
-
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
-
#[cfg_attr(
-
    feature = "serde",
-
    derive(serde::Serialize, serde::Deserialize),
-
    serde(into = "String", try_from = "RefLike")
-
)]
-
pub struct OneLevel(String);
-

-
impl OneLevel {
-
    pub fn as_str(&self) -> &str {
-
        self.as_ref()
-
    }
-

-
    pub fn from_qualified(Qualified(path): Qualified) -> (Self, Option<RefLike>) {
-
        let mut path = path.strip_prefix("refs/").unwrap_or(&path).split('/');
-
        match path.next() {
-
            Some(category) => {
-
                let category = RefLike(category.into());
-
                // check that the "category" is not the only component of the path
-
                match path.next() {
-
                    Some(head) => (
-
                        Self(
-
                            std::iter::once(head)
-
                                .chain(path)
-
                                .collect::<Vec<_>>()
-
                                .join("/"),
-
                        ),
-
                        Some(category),
-
                    ),
-
                    None => (Self::from(category), None),
-
                }
-
            },
-
            None => unreachable!(),
-
        }
-
    }
-

-
    pub fn into_qualified(self, category: RefLike) -> Qualified {
-
        Qualified(format!("refs/{category}/{self}"))
-
    }
-
}
-

-
impl Deref for OneLevel {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl AsRef<str> for OneLevel {
-
    fn as_ref(&self) -> &str {
-
        self
-
    }
-
}
-

-
impl From<RefLike> for OneLevel {
-
    fn from(RefLike(path): RefLike) -> Self {
-
        if path.starts_with("refs/") {
-
            Self(path.split('/').skip(2).collect::<Vec<_>>().join("/"))
-
        } else {
-
            Self(path)
-
        }
-
    }
-
}
-

-
impl From<Qualified> for OneLevel {
-
    fn from(Qualified(path): Qualified) -> Self {
-
        Self::from(RefLike(path))
-
    }
-
}
-

-
impl From<OneLevel> for RefLike {
-
    fn from(OneLevel(path): OneLevel) -> Self {
-
        Self(path)
-
    }
-
}
-

-
impl From<OneLevel> for String {
-
    fn from(OneLevel(path): OneLevel) -> Self {
-
        path
-
    }
-
}
-

-
impl Display for OneLevel {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(self.as_str())
-
    }
-
}
-

-
/// A [`RefLike`] **with** a "refs/" prefix.
-
///
-
/// Conversion functions will assume `refs/heads/` if the input was not
-
/// qualified.
-
///
-
/// Note that the [`serde::Deserialize`] impl thusly implies that input in
-
/// [`OneLevel`] form is accepted, and silently converted.
-
///
-
/// # Examples
-
///
-
/// ```rust
-
/// use std::convert::TryFrom;
-
/// use radicle_git_ext::reference::name::*;
-
///
-
/// assert_eq!(
-
///     &*Qualified::from(RefLike::try_from("laplace").unwrap()),
-
///     "refs/heads/laplace"
-
/// );
-
///
-
/// assert_eq!(
-
///     &*Qualified::from(RefLike::try_from("refs/heads/pu").unwrap()),
-
///     "refs/heads/pu"
-
/// );
-
///
-
/// assert_eq!(
-
///     &*Qualified::from(RefLike::try_from("refs/tags/v6.6.6").unwrap()),
-
///     "refs/tags/v6.6.6"
-
/// );
-
/// ```
-
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
-
#[cfg_attr(
-
    feature = "serde",
-
    derive(serde::Serialize, serde::Deserialize),
-
    serde(into = "String", try_from = "RefLike")
-
)]
-
pub struct Qualified(String);
-

-
impl Qualified {
-
    pub fn as_str(&self) -> &str {
-
        &self.0
-
    }
-
}
-

-
impl Deref for Qualified {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl AsRef<str> for Qualified {
-
    fn as_ref(&self) -> &str {
-
        &self.0
-
    }
-
}
-

-
impl From<RefLike> for Qualified {
-
    fn from(RefLike(path): RefLike) -> Self {
-
        if path.starts_with("refs/") {
-
            Self(path)
-
        } else {
-
            Self(format!("refs/heads/{path}"))
-
        }
-
    }
-
}
-

-
impl From<OneLevel> for Qualified {
-
    fn from(OneLevel(path): OneLevel) -> Self {
-
        Self::from(RefLike(path))
-
    }
-
}
-

-
impl From<Qualified> for RefLike {
-
    fn from(Qualified(path): Qualified) -> Self {
-
        Self(path)
-
    }
-
}
-

-
impl From<Qualified> for String {
-
    fn from(Qualified(path): Qualified) -> Self {
-
        path
-
    }
-
}
-

-
impl Display for Qualified {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(self)
-
    }
-
}
-

-
/// An owned, path-like value which is a valid refspec pattern.
-
///
-
/// Conversion functions behave as if `--allow-onelevel --refspec-pattern` where
-
/// given to [`git-check-ref-format`]. That is, most of the rules of [`RefLike`]
-
/// apply, but the path _may_ contain exactly one `*` character.
-
///
-
/// [`git-check-ref-format`]: https://git-scm.com/docs/git-check-ref-format
-
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
-
#[cfg_attr(
-
    feature = "serde",
-
    derive(serde::Serialize, serde::Deserialize),
-
    serde(into = "String", try_from = "String")
-
)]
-
pub struct RefspecPattern(String);
-

-
impl RefspecPattern {
-
    /// Append the `RefLike` to the `RefspecPattern`. This allows the creation
-
    /// of patterns where the `*` appears in the middle of the path, e.g.
-
    /// `refs/remotes/*/mfdoom`
-
    pub fn append(&self, refl: impl Into<RefLike>) -> Self {
-
        RefspecPattern(format!("{}/{}", self.0, refl.into()))
-
    }
-

-
    pub fn as_str(&self) -> &str {
-
        self.as_ref()
-
    }
-
}
-

-
impl From<&RefspecPattern> for RefspecPattern {
-
    fn from(pat: &RefspecPattern) -> Self {
-
        pat.clone()
-
    }
-
}
-

-
impl Deref for RefspecPattern {
-
    type Target = str;
-

-
    fn deref(&self) -> &Self::Target {
-
        &self.0
-
    }
-
}
-

-
impl AsRef<str> for RefspecPattern {
-
    fn as_ref(&self) -> &str {
-
        self
-
    }
-
}
-

-
impl TryFrom<&str> for RefspecPattern {
-
    type Error = Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        check::ref_format(
-
            check::Options {
-
                allow_onelevel: true,
-
                allow_pattern: true,
-
            },
-
            s,
-
        )?;
-
        Ok(Self(s.to_owned()))
-
    }
-
}
-

-
impl TryFrom<&[u8]> for RefspecPattern {
-
    type Error = Error;
-

-
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-
        str::from_utf8(bytes)
-
            .or(Err(Error::Utf8))
-
            .and_then(Self::try_from)
-
    }
-
}
-

-
impl FromStr for RefspecPattern {
-
    type Err = Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl TryFrom<String> for RefspecPattern {
-
    type Error = Error;
-

-
    fn try_from(s: String) -> Result<Self, Self::Error> {
-
        Self::try_from(s.as_str())
-
    }
-
}
-

-
impl From<RefspecPattern> for String {
-
    fn from(RefspecPattern(path): RefspecPattern) -> Self {
-
        path
-
    }
-
}
-

-
impl Display for RefspecPattern {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(self.as_str())
-
    }
-
}
-

-
// `RefLike`-likes can be coerced into `RefspecPattern`s
-

-
impl From<RefLike> for RefspecPattern {
-
    fn from(RefLike(path): RefLike) -> Self {
-
        Self(path)
-
    }
-
}
-

-
impl From<&RefLike> for RefspecPattern {
-
    fn from(RefLike(path): &RefLike) -> Self {
-
        Self(path.to_owned())
-
    }
-
}
-

-
impl From<OneLevel> for RefspecPattern {
-
    fn from(OneLevel(path): OneLevel) -> Self {
-
        Self(path)
-
    }
-
}
-

-
impl From<&OneLevel> for RefspecPattern {
-
    fn from(OneLevel(path): &OneLevel) -> Self {
-
        Self(path.to_owned())
-
    }
-
}
-

-
impl From<Qualified> for RefspecPattern {
-
    fn from(Qualified(path): Qualified) -> Self {
-
        Self(path)
-
    }
-
}
-

-
impl From<&Qualified> for RefspecPattern {
-
    fn from(Qualified(path): &Qualified) -> Self {
-
        Self(path.to_owned())
-
    }
-
}
-

-
impl From<git_ref_format::refspec::PatternString> for RefspecPattern {
-
    #[inline]
-
    fn from(r: git_ref_format::refspec::PatternString) -> Self {
-
        Self(r.into())
-
    }
-
}
-

-
impl From<&git_ref_format::refspec::PatternStr> for RefspecPattern {
-
    #[inline]
-
    fn from(r: &git_ref_format::refspec::PatternStr) -> Self {
-
        Self(r.to_owned().into())
-
    }
-
}
modified radicle-git-ext/t/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-
name = "git-ext-test"
+
name = "radicle-git-ext-test"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
added radicle-git-ext/t/src/commit.rs
@@ -0,0 +1,176 @@
+
use std::str::FromStr as _;
+

+
use radicle_git_ext::commit::Commit;
+

+
const NO_TRAILER: &str = "\
+
tree 50d6ef440728217febf9e35716d8b0296608d7f8
+
parent 0ad95dbdfe9fdf81938ca419cf740469173e2022
+
parent a4ec9e07e1b2e6f37f7119651ae3bb63b79988b6
+
author Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
+
committer Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
+

+
Merge remote-tracking branch 'origin/surf/organise-tests'
+

+
* origin/surf/organise-tests:
+
  radicle-surf: organise tests
+
";
+

+
const SINGLE_TRAILER: &str = "\
+
tree 50d6ef440728217febf9e35716d8b0296608d7f8
+
parent 0ad95dbdfe9fdf81938ca419cf740469173e2022
+
parent a4ec9e07e1b2e6f37f7119651ae3bb63b79988b6
+
author Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
+
committer Fintan Halpenny <fintan.halpenny@gmail.com> 1669292989 +0000
+

+
Merge remote-tracking branch 'origin/surf/organise-tests'
+

+
* origin/surf/organise-tests:
+
  radicle-surf: organise tests
+

+
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
+
";
+

+
const UNSIGNED: &str = "\
+
tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b
+
parent af06ad645133f580a87895353508053c5de60716
+
author Alexis Sellier <alexis@radicle.xyz> 1664467633 +0200
+
committer Alexis Sellier <alexis@radicle.xyz> 1664786099 -0200
+

+
Add SSH functionality with new `radicle-ssh`
+

+
We borrow code from `thrussh`, refactored to be runtime-less.
+

+
X-Signed-Off-By: Alex Sellier
+
X-Co-Authored-By: Fintan Halpenny
+
";
+

+
const SSH_SIGNATURE: &str = "\
+
-----BEGIN SSH SIGNATURE-----
+
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
+
4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+
AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+
+
GPp0HqxaB911OnSAr6bwU=
+
-----END SSH SIGNATURE-----";
+

+
const PGP_SIGNATURE: &str = "\
+
-----BEGIN PGP SIGNATURE-----
+
iQIzBAABCAAdFiEEHe7BWIo9taTY6TIiJVL7b2QGbLcFAmNcDhsACgkQJVL7b2QG
+
bLcc9Q//RgKf5N4enta9AuszGJZvdFhMPfIDUdw+WAZA6Z8zDPb/aAXZrPP/KIOM
+
zmX08FTqjP9B9YeWrEcFuAtxsRNqbDKrfpko9Y6bTsdrAJg3WIypBb9F8YDKJ6BO
+
CORJJqWOsLW129jW+mJDhcE0YTvPlcMiMI2qjVXKhU6Ag11W8IRZyTb9tvEaDjBR
+
YUnkPvgubv61K9BeUKexE2MakPBldaQtl0MF1Dk7/zo5btLd+KP0SOUKEhuMEu5b
+
LATHHdiYjt/2Xz7q8EcrFxXUaipxZe89dfTdi2ooJQw3ZDqjDHsGTHpDeBuzuSaJ
+
9fKVRwFz/78onfHPhmU4wfUhh+Fcl90p5/T+4dt2K6cr+7rq078e+aJYxkX2d0MG
+
PG0xGP0RN4g+X92K1kGuzoe4870xAnRTNh5nUB+X9snO8tVqQZTb0M2yI+sTsKrv
+
w/f+uiqL6e9DgIxlO5dgiNHCVoCs1QJ900jUGisrlzS4+n6GzMsG6s3c01X4yY9G
+
Ou/kGkMsn7tqejqC9RufygcchCFZqYwaHQwPkiYhfYGMarMpoCFvll0h8tSparpS
+
nnpAQXVdu8m3v1YdPUuTg5ksxSOe9HCIlVXGFhxy3iqCVRn+51FRnUI63rMTOm9/
+
LBqzvji02lDUPGqPgXfcCS0ty8FM2flBIXnwb8TDzCaPYhf53+U=
+
=6dw2
+
-----END PGP SIGNATURE-----";
+

+
const SIGNED: &str = "\
+
tree c66cc435f83ed0fba90ed4500e9b4b96e9bd001b
+
parent af06ad645133f580a87895353508053c5de60716
+
author Alexis Sellier <alexis@radicle.xyz> 1664467633 +0200
+
committer Alexis Sellier <alexis@radicle.xyz> 1664786099 -0200
+
other e6fe3c97619deb8ab4198620f9a7eb79d98363dd
+
gpgsig -----BEGIN SSH SIGNATURE-----
+
 U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
+
 4fm2ALoN7pyvD2ttQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
+
 AAAAQIQvhIewOgGfnXLgR5Qe1ZEr2vjekYXTdOfNWICi6ZiosgfZnIqV0enCPC4arVqQg+
+
 GPp0HqxaB911OnSAr6bwU=
+
 -----END SSH SIGNATURE-----
+
gpgsig -----BEGIN PGP SIGNATURE-----
+
 iQIzBAABCAAdFiEEHe7BWIo9taTY6TIiJVL7b2QGbLcFAmNcDhsACgkQJVL7b2QG
+
 bLcc9Q//RgKf5N4enta9AuszGJZvdFhMPfIDUdw+WAZA6Z8zDPb/aAXZrPP/KIOM
+
 zmX08FTqjP9B9YeWrEcFuAtxsRNqbDKrfpko9Y6bTsdrAJg3WIypBb9F8YDKJ6BO
+
 CORJJqWOsLW129jW+mJDhcE0YTvPlcMiMI2qjVXKhU6Ag11W8IRZyTb9tvEaDjBR
+
 YUnkPvgubv61K9BeUKexE2MakPBldaQtl0MF1Dk7/zo5btLd+KP0SOUKEhuMEu5b
+
 LATHHdiYjt/2Xz7q8EcrFxXUaipxZe89dfTdi2ooJQw3ZDqjDHsGTHpDeBuzuSaJ
+
 9fKVRwFz/78onfHPhmU4wfUhh+Fcl90p5/T+4dt2K6cr+7rq078e+aJYxkX2d0MG
+
 PG0xGP0RN4g+X92K1kGuzoe4870xAnRTNh5nUB+X9snO8tVqQZTb0M2yI+sTsKrv
+
 w/f+uiqL6e9DgIxlO5dgiNHCVoCs1QJ900jUGisrlzS4+n6GzMsG6s3c01X4yY9G
+
 Ou/kGkMsn7tqejqC9RufygcchCFZqYwaHQwPkiYhfYGMarMpoCFvll0h8tSparpS
+
 nnpAQXVdu8m3v1YdPUuTg5ksxSOe9HCIlVXGFhxy3iqCVRn+51FRnUI63rMTOm9/
+
 LBqzvji02lDUPGqPgXfcCS0ty8FM2flBIXnwb8TDzCaPYhf53+U=
+
 =6dw2
+
 -----END PGP SIGNATURE-----
+

+
Add SSH functionality with new `radicle-ssh`
+

+
We borrow code from `thrussh`, refactored to be runtime-less.
+

+
X-Signed-Off-By: Alex Sellier
+
X-Co-Authored-By: Fintan Halpenny
+
";
+

+
#[test]
+
fn test_push_header() {
+
    let mut commit = Commit::from_str(UNSIGNED).unwrap();
+

+
    commit.push_header("other", "e6fe3c97619deb8ab4198620f9a7eb79d98363dd");
+
    commit.push_header("gpgsig", SSH_SIGNATURE);
+
    commit.push_header("gpgsig", PGP_SIGNATURE);
+

+
    assert_eq!(commit.to_string(), SIGNED);
+
}
+

+
#[test]
+
fn test_get_header() {
+
    let commit = Commit::from_str(SIGNED).unwrap();
+

+
    assert_eq!(
+
        commit
+
            .signatures()
+
            .map(|sig| sig.to_string())
+
            .collect::<Vec<_>>(),
+
        vec![SSH_SIGNATURE.to_owned(), PGP_SIGNATURE.to_owned()]
+
    );
+
    assert_eq!(
+
        commit.values("other").collect::<Vec<_>>(),
+
        vec![String::from("e6fe3c97619deb8ab4198620f9a7eb79d98363dd")],
+
    );
+
    assert!(commit.values("unknown").next().is_none());
+
}
+

+
#[test]
+
fn test_conversion() {
+
    assert_eq!(
+
        Commit::from_str(NO_TRAILER).unwrap().to_string(),
+
        NO_TRAILER
+
    );
+
    assert_eq!(
+
        Commit::from_str(SINGLE_TRAILER).unwrap().to_string(),
+
        SINGLE_TRAILER
+
    );
+
    assert_eq!(Commit::from_str(SIGNED).unwrap().to_string(), SIGNED);
+
    assert_eq!(Commit::from_str(UNSIGNED).unwrap().to_string(), UNSIGNED);
+
}
+

+
use std::io;
+
use test_helpers::tempdir::WithTmpDir;
+

+
#[test]
+
fn valid_commits() {
+
    let radicle_git = format!(
+
        "file://{}",
+
        git2::Repository::discover(".").unwrap().path().display()
+
    );
+
    let repo = WithTmpDir::new(|path| {
+
        let repo = git2::Repository::clone(&radicle_git, path)
+
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+
        Ok::<_, io::Error>(repo)
+
    })
+
    .unwrap();
+

+
    let mut walk = repo.revwalk().unwrap();
+
    walk.push_head().unwrap();
+

+
    // take the first 20 commits and make sure we can parse them
+
    for oid in walk.take(20) {
+
        let oid = oid.unwrap();
+
        let commit = Commit::read(&repo, oid);
+
        assert!(commit.is_ok(), "Oid: {oid}, Error: {commit:?}")
+
    }
+
}
modified radicle-git-ext/t/src/lib.rs
@@ -1,12 +1,8 @@
// Copyright © 2022 The Radicle Link Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

-
#[cfg(test)]
-
#[macro_use]
-
extern crate assert_matches;
-

#[cfg(any(test, feature = "test"))]
pub mod gen;

#[cfg(test)]
-
mod tests;
+
mod commit;
deleted radicle-git-ext/t/src/tests.rs
@@ -1,228 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
// SPDX-License-Identifier: GPL-3.0-or-later
-

-
use std::convert::TryFrom;
-

-
use radicle_git_ext::reference::{check, name::*};
-
use test_helpers::roundtrip;
-

-
mod common {
-
    use super::*;
-
    use std::fmt::Debug;
-

-
    pub fn invalid<T>()
-
    where
-
        T: TryFrom<&'static str, Error = Error> + Debug,
-
    {
-
        const INVALID: [&str; 16] = [
-
            ".hidden",
-
            "/etc/shadow",
-
            "@",
-
            "@{",
-
            "C:",
-
            "\\WORKGROUP",
-
            "foo.lock",
-
            "head^",
-
            "here/../../etc/shadow",
-
            "refs//heads/main",
-
            "refs/heads/",
-
            "shawn/ white",
-
            "the/dotted./quad",
-
            "wh?t",
-
            "x[a-z]",
-
            "~ommij",
-
        ];
-

-
        for v in INVALID {
-
            assert_matches!(T::try_from(v), Err(Error::RefFormat(_)), "input: {}", v)
-
        }
-
    }
-

-
    pub fn valid<T>()
-
    where
-
        T: TryFrom<&'static str, Error = Error> + AsRef<str> + Debug,
-
    {
-
        const VALID: [&str; 5] = [
-
            "\u{1F32F}",
-
            "cl@wn",
-
            "foo/bar",
-
            "master",
-
            "refs/heads/mistress",
-
        ];
-

-
        for v in VALID {
-
            assert_matches!(T::try_from(v), Ok(x) if x.as_ref() == v, "input: {}", v)
-
        }
-
    }
-

-
    pub fn empty<T>()
-
    where
-
        T: TryFrom<&'static str, Error = Error> + Debug,
-
    {
-
        assert_matches!(T::try_from(""), Err(Error::RefFormat(check::Error::Empty)))
-
    }
-

-
    pub fn nulsafe<T>()
-
    where
-
        T: TryFrom<&'static str, Error = Error> + Debug,
-
    {
-
        assert_matches!(
-
            T::try_from("jeff\0"),
-
            Err(Error::RefFormat(check::Error::InvalidChar('\0')))
-
        )
-
    }
-
}
-

-
mod reflike {
-
    use super::*;
-

-
    #[test]
-
    fn empty() {
-
        common::empty::<RefLike>()
-
    }
-

-
    #[test]
-
    fn valid() {
-
        common::valid::<RefLike>()
-
    }
-

-
    #[test]
-
    fn invalid() {
-
        common::invalid::<RefLike>()
-
    }
-

-
    #[test]
-
    fn nulsafe() {
-
        common::nulsafe::<RefLike>()
-
    }
-

-
    #[test]
-
    fn globstar_invalid() {
-
        assert_matches!(
-
            RefLike::try_from("refs/heads/*"),
-
            Err(Error::RefFormat(check::Error::InvalidChar('*')))
-
        )
-
    }
-

-
    #[test]
-
    fn into_onelevel() {
-
        assert_eq!(
-
            &*OneLevel::from(RefLike::try_from("refs/heads/next").unwrap()),
-
            "next"
-
        )
-
    }
-

-
    #[test]
-
    fn into_heads() {
-
        assert_eq!(
-
            &*Qualified::from(RefLike::try_from("pu").unwrap()),
-
            "refs/heads/pu"
-
        )
-
    }
-

-
    #[test]
-
    fn serde() {
-
        let refl = RefLike::try_from("pu").unwrap();
-
        roundtrip::json(refl.clone());
-
        roundtrip::json(OneLevel::from(refl.clone()));
-
        roundtrip::json(Qualified::from(refl))
-
    }
-

-
    #[test]
-
    fn serde_invalid() {
-
        let json = serde_json::to_string("HEAD^").unwrap();
-
        assert!(serde_json::from_str::<RefLike>(&json).is_err());
-
        assert!(serde_json::from_str::<OneLevel>(&json).is_err());
-
        assert!(serde_json::from_str::<Qualified>(&json).is_err())
-
    }
-
}
-

-
mod pattern {
-
    use super::*;
-

-
    #[test]
-
    fn empty() {
-
        common::empty::<RefspecPattern>()
-
    }
-

-
    #[test]
-
    fn valid() {
-
        common::valid::<RefspecPattern>()
-
    }
-

-
    #[test]
-
    fn invalid() {
-
        common::invalid::<RefspecPattern>()
-
    }
-

-
    #[test]
-
    fn nulsafe() {
-
        common::nulsafe::<RefspecPattern>()
-
    }
-

-
    #[test]
-
    fn globstar_ok() {
-
        const GLOBBED: [&str; 7] = [
-
            "*",
-
            "fo*",
-
            "fo*/bar",
-
            "foo/*/bar",
-
            "foo/ba*",
-
            "foo/bar/*",
-
            "foo/b*r",
-
        ];
-

-
        for v in GLOBBED {
-
            assert_matches!(
-
                RefspecPattern::try_from(v),
-
                Ok(ref x) if x.as_str() == v,
-
                "input: {}", v
-
            )
-
        }
-
    }
-

-
    #[test]
-
    fn globstar_invalid() {
-
        const GLOBBED: [&str; 12] = [
-
            "**",
-
            "***",
-
            "*/*",
-
            "*/L/*",
-
            "fo*/*/bar",
-
            "fo*/ba*",
-
            "fo*/ba*/baz",
-
            "fo*/ba*/ba*",
-
            "fo*/bar/*",
-
            "foo/*/bar/*",
-
            "foo/*/bar/*/baz*",
-
            "foo/*/bar/*/baz/*",
-
        ];
-

-
        for v in GLOBBED {
-
            assert_matches!(
-
                RefspecPattern::try_from(v),
-
                Err(Error::RefFormat(check::Error::Pattern))
-
            )
-
        }
-
    }
-

-
    #[test]
-
    fn serde() {
-
        roundtrip::json(RefspecPattern::try_from("refs/heads/*").unwrap())
-
    }
-

-
    #[test]
-
    fn serde_invalid() {
-
        let json = serde_json::to_string("HEAD^").unwrap();
-
        assert!(serde_json::from_str::<RefspecPattern>(&json).is_err())
-
    }
-

-
    #[test]
-
    fn strip_prefix_works_for_different_ends() {
-
        let refl = RefLike::try_from("refs/heads/next").unwrap();
-
        assert_eq!(
-
            refl.strip_prefix("refs/heads").unwrap(),
-
            refl.strip_prefix("refs/heads/").unwrap()
-
        );
-
    }
-
}
deleted radicle-git-types/Cargo.toml
@@ -1,39 +0,0 @@
-
[package]
-
name = "radicle-git-types"
-
version = "0.1.0"
-
authors = ["Kim Altintop <kim@eagain.st>", "Fintan Halpenny <fintan.halpenny@gmail.com>"]
-
edition = "2021"
-
license = "GPL-3.0-or-later"
-

-
[lib]
-
doctest = false
-
test = false
-

-
[dependencies]
-
lazy_static = "1.4"
-
multibase = "0.9"
-
percent-encoding = "2"
-
thiserror = "1.0.30"
-
tracing = "0.1"
-

-
[dependencies.git2]
-
version = "0.16.1"
-
default-features = false
-
features = ["vendored-libgit2"]
-

-
[dependencies.minicbor]
-
version = "0.13"
-
features = ["std", "derive"]
-

-
[dependencies.radicle-git-ext]
-
path = "../radicle-git-ext"
-

-
[dependencies.radicle-macros]
-
path = "../radicle-macros"
-

-
[dependencies.radicle-std-ext]
-
path = "../radicle-std-ext"
-

-
[dependencies.serde]
-
version = "1.0"
-
features = ["derive"]
deleted radicle-git-types/src/lib.rs
@@ -1,79 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
extern crate radicle_git_ext as git_ext;
-
extern crate radicle_std_ext as std_ext;
-

-
#[macro_use]
-
extern crate radicle_macros;
-

-
pub mod reference;
-
pub mod refspec;
-
pub mod remote;
-

-
mod sealed;
-

-
pub use reference::{
-
    AsRemote,
-
    Many,
-
    Multiple,
-
    One,
-
    Reference as GenericRef,
-
    RefsCategory,
-
    Single,
-
    SymbolicRef,
-
};
-
pub use refspec::{Fetchspec, Pushspec, Refspec};
-

-
pub trait AsNamespace: Into<git_ext::RefLike> {
-
    fn into_namespace(self) -> git_ext::RefLike {
-
        self.into()
-
    }
-
}
-

-
/// Helper to aid type inference constructing a [`Reference`] without a
-
/// namespace.
-
pub struct Flat;
-

-
impl<N> From<Flat> for Option<N>
-
where
-
    N: AsNamespace,
-
{
-
    fn from(_flat: Flat) -> Self {
-
        None
-
    }
-
}
-

-
/// Type specialised reference for the most common use within this crate.
-
pub type Reference<N, C, R> = GenericRef<N, R, C>;
-

-
/// Whether we should force the overwriting of a reference or not.
-
#[derive(Debug, Clone, Copy)]
-
pub enum Force {
-
    /// We should overwrite.
-
    True,
-
    /// We should not overwrite.
-
    False,
-
}
-

-
impl Force {
-
    /// Convert the Force to its `bool` equivalent.
-
    fn as_bool(&self) -> bool {
-
        match self {
-
            Force::True => true,
-
            Force::False => false,
-
        }
-
    }
-
}
-

-
impl From<bool> for Force {
-
    fn from(b: bool) -> Self {
-
        if b {
-
            Self::True
-
        } else {
-
            Self::False
-
        }
-
    }
-
}
deleted radicle-git-types/src/reference.rs
@@ -1,550 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    convert::TryFrom,
-
    fmt::{self, Display},
-
    str::FromStr,
-
};
-

-
use git_ext as ext;
-

-
use super::{AsNamespace, Force};
-

-
/// Type witness for a [`Reference`] that should point to a single reference.
-
pub type One = ext::RefLike;
-

-
/// Alias for [`One`].
-
pub type Single = One;
-

-
/// Type witness for a [`Reference`] that should point to multiple references.
-
pub type Many = ext::RefspecPattern;
-

-
/// Alias for [`Many`].
-
pub type Multiple = Many;
-

-
#[derive(Clone, Debug, PartialEq, Eq)]
-
pub enum RefsCategory {
-
    Heads,
-
    Rad,
-
    Tags,
-
    Notes,
-
    /// Collaborative objects
-
    Cobs,
-
    Unknown(ext::RefLike),
-
}
-

-
impl RefsCategory {
-
    /// The categories that are present in a default git repository
-
    pub const fn default_categories() -> [RefsCategory; 3] {
-
        [Self::Heads, Self::Tags, Self::Notes]
-
    }
-
}
-

-
impl FromStr for RefsCategory {
-
    type Err = ext::reference::name::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Ok(match s {
-
            "heads" => Self::Heads,
-
            "rad" => Self::Rad,
-
            "tags" => Self::Tags,
-
            "notes" => Self::Notes,
-
            "cobs" => Self::Cobs,
-
            other => {
-
                let reflike = ext::RefLike::try_from(other)?;
-
                Self::Unknown(reflike)
-
            },
-
        })
-
    }
-
}
-

-
impl Display for RefsCategory {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        match self {
-
            Self::Heads => f.write_str("heads"),
-
            Self::Rad => f.write_str("rad"),
-
            Self::Tags => f.write_str("tags"),
-
            Self::Notes => f.write_str("notes"),
-
            Self::Cobs => f.write_str("cobs"),
-
            Self::Unknown(cat) => f.write_str(cat),
-
        }
-
    }
-
}
-

-
impl From<RefsCategory> for ext::RefLike {
-
    fn from(cat: RefsCategory) -> Self {
-
        ext::RefLike::try_from(cat.to_string()).unwrap()
-
    }
-
}
-

-
impl From<&RefsCategory> for ext::RefLike {
-
    fn from(cat: &RefsCategory) -> Self {
-
        ext::RefLike::try_from(cat.to_string()).unwrap()
-
    }
-
}
-

-
impl From<ext::RefLike> for RefsCategory {
-
    fn from(r: ext::RefLike) -> Self {
-
        (&r).into()
-
    }
-
}
-

-
impl From<&ext::RefLike> for RefsCategory {
-
    fn from(r: &ext::RefLike) -> Self {
-
        match r.as_str() {
-
            "heads" => Self::Heads,
-
            "rad" => Self::Rad,
-
            "tags" => Self::Tags,
-
            "notes" => Self::Notes,
-
            "cobs" => Self::Cobs,
-
            _ => Self::Unknown(r.clone()),
-
        }
-
    }
-
}
-

-
/// Ad-hoc trait to prevent the typechecker from recursing.
-
///
-
/// Morally, we can convert `Reference<N, R, C>` into `ext::RefLike` for any `R:
-
/// Into<ext::RefLike>`. However, the typechecker may then attempt to unify `R`
-
/// with `Reference<_, Reference<_, ...` recursively, leading to
-
/// non-termination. Hence, we restrict the types which can be used as
-
/// `Reference::remote` artificially.
-
pub trait AsRemote: Into<ext::RefLike> {}
-

-
impl AsRemote for ext::RefLike {}
-
impl AsRemote for &ext::RefLike {}
-

-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct Reference<Namespace, Remote, Cardinality> {
-
    /// The remote portion of this reference.
-
    pub remote: Option<Remote>,
-
    /// Where this reference falls under, i.e. `heads`, `tags`, `cob`, or`rad`.
-
    pub category: RefsCategory,
-
    /// The path of the reference, e.g. `feature/123`, `dev`, `heads/*`.
-
    pub name: Cardinality,
-
    /// The namespace of this reference.
-
    pub namespace: Option<Namespace>,
-
}
-

-
// Polymorphic definitions
-
impl<N, R, C> Reference<N, R, C>
-
where
-
    N: Clone,
-
    R: Clone,
-
    C: Clone,
-
{
-
    pub fn with_remote(self, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            ..self
-
        }
-
    }
-

-
    pub fn set_remote(&mut self, remote: impl Into<Option<R>>) {
-
        self.remote = remote.into();
-
    }
-

-
    pub fn remote(&mut self, remote: impl Into<Option<R>>) -> &mut Self {
-
        self.set_remote(remote);
-
        self
-
    }
-

-
    /// Set the namespace of this reference to another one. Note that the
-
    /// namespace does not have to be of the original namespace's type.
-
    pub fn with_namespace<NN, Other>(self, namespace: NN) -> Reference<Other, R, C>
-
    where
-
        NN: Into<Option<Other>>,
-
        Other: AsNamespace,
-
    {
-
        Reference {
-
            name: self.name,
-
            remote: self.remote,
-
            category: self.category,
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Set the named portion of this path.
-
    pub fn with_name<S: Into<C>>(self, name: S) -> Self {
-
        Self {
-
            name: name.into(),
-
            ..self
-
        }
-
    }
-

-
    /// Set the named portion of this path.
-
    pub fn set_name<S: Into<C>>(&mut self, name: S) {
-
        self.name = name.into();
-
    }
-

-
    pub fn name<S: Into<C>>(&mut self, name: S) -> &mut Self {
-
        self.set_name(name);
-
        self
-
    }
-
}
-

-
// References with a `One` cardinality
-
impl<N, R> Reference<N, R, One> {
-
    /// Find this particular reference.
-
    pub fn find<'a>(&self, repo: &'a git2::Repository) -> Result<git2::Reference<'a>, git2::Error>
-
    where
-
        Self: ToString,
-
    {
-
        repo.find_reference(&self.to_string())
-
    }
-

-
    /// Resolve the [`git2::Oid`] the reference points to (if it exists).
-
    ///
-
    /// Avoids allocating a [`git2::Reference`].
-
    pub fn oid(&self, repo: &git2::Repository) -> Result<git2::Oid, git2::Error>
-
    where
-
        Self: ToString,
-
    {
-
        repo.refname_to_id(&self.to_string())
-
    }
-

-
    pub fn create<'a>(
-
        &self,
-
        repo: &'a git2::Repository,
-
        target: git2::Oid,
-
        force: super::Force,
-
        log_message: &str,
-
    ) -> Result<git2::Reference<'a>, git2::Error>
-
    where
-
        Self: ToString,
-
    {
-
        tracing::debug!(
-
            "creating direct reference {} -> {} (force: {}, reflog: '{}')",
-
            self.to_string(),
-
            target,
-
            force.as_bool(),
-
            log_message
-
        );
-
        let name = self.to_string();
-
        repo.reference_ensure_log(&name)?;
-
        repo.reference(&name, target, force.as_bool(), log_message)
-
    }
-

-
    /// Create a [`SymbolicRef`] from `source` to `self` as the `target`.
-
    pub fn symbolic_ref<SN, SR>(
-
        self,
-
        source: Reference<SN, SR, Single>,
-
        force: Force,
-
    ) -> SymbolicRef<Reference<SN, SR, Single>, Self>
-
    where
-
        R: Clone,
-
        N: Clone,
-
    {
-
        SymbolicRef {
-
            source,
-
            target: self,
-
            force,
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs/namespaces/<namespace>/refs/rad/id`
-
    pub fn rad_id(namespace: impl Into<Option<N>>) -> Self {
-
        Self {
-
            remote: None,
-
            category: RefsCategory::Rad,
-
            name: reflike!("id"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs/namespaces/<namespace>/refs/rad/signed_refs`
-
    ///     * `refs/namespaces/<namespace>/refs/remote/<peer_id>/rad/
-
    ///       signed_refs`
-
    pub fn rad_signed_refs(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Rad,
-
            name: reflike!("signed_refs"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs/namespaces/<namespace>/refs/rad/self`
-
    ///     * `refs/namespaces/<namespace>/refs/remote/<peer_id>/rad/self`
-
    pub fn rad_self(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Rad,
-
            name: reflike!("self"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs/namespaces/<namespace>/refs/heads/<name>`
-
    ///     * `refs/namespaces/<namespace>/refs/remote/<peer_id>/heads/<name>`
-
    pub fn head(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>, name: One) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Heads,
-
            name,
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    /// * `refs/namespaces/<namespace>/refs/tags/<name>`
-
    /// * `refs/namespaces/<namespace>/refs/remote/<peer_id>/tags/<name>`
-
    pub fn tag(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>, name: One) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Tags,
-
            name,
-
            namespace: namespace.into(),
-
        }
-
    }
-
}
-

-
impl<N, R> Display for Reference<N, R, One>
-
where
-
    for<'a> &'a N: AsNamespace,
-
    for<'a> &'a R: AsRemote,
-
{
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(Into::<ext::RefLike>::into(self).as_str())
-
    }
-
}
-

-
impl<N, R> From<Reference<N, R, One>> for ext::RefLike
-
where
-
    for<'a> &'a N: AsNamespace,
-
    for<'a> &'a R: AsRemote,
-
{
-
    fn from(r: Reference<N, R, One>) -> Self {
-
        Self::from(&r)
-
    }
-
}
-

-
impl<'a, N, R> From<&'a Reference<N, R, One>> for ext::RefLike
-
where
-
    &'a N: AsNamespace,
-
    &'a R: AsRemote,
-
{
-
    fn from(r: &'a Reference<N, R, One>) -> Self {
-
        let mut refl = reflike!("refs");
-

-
        if let Some(ref namespace) = r.namespace {
-
            refl = refl
-
                .join(reflike!("namespaces"))
-
                .join(namespace)
-
                .join(reflike!("refs"));
-
        }
-
        if let Some(ref remote) = r.remote {
-
            refl = refl.join(reflike!("remotes")).join(remote);
-
        }
-

-
        refl.join(&r.category)
-
            .join(ext::OneLevel::from(r.name.to_owned()))
-
    }
-
}
-

-
impl<N, R> From<Reference<N, R, One>> for ext::RefspecPattern
-
where
-
    for<'a> &'a N: AsNamespace,
-
    for<'a> &'a R: AsRemote,
-
{
-
    fn from(r: Reference<N, R, One>) -> Self {
-
        Self::from(&r)
-
    }
-
}
-

-
impl<'a, N, R> From<&'a Reference<N, R, One>> for ext::RefspecPattern
-
where
-
    &'a N: AsNamespace,
-
    &'a R: AsRemote,
-
{
-
    fn from(r: &'a Reference<N, R, One>) -> Self {
-
        Into::<ext::RefLike>::into(r).into()
-
    }
-
}
-

-
// TODO(kim): what is this for?
-
#[allow(clippy::from_over_into)]
-
impl<'a, N, R> Into<ext::blob::Branch<'a>> for &'a Reference<N, R, Single>
-
where
-
    Self: ToString,
-
{
-
    fn into(self) -> ext::blob::Branch<'a> {
-
        ext::blob::Branch::from(self.to_string())
-
    }
-
}
-

-
// References with a `Many` cardinality
-
impl<N, R> Reference<N, R, Many> {
-
    /// Get the iterator for these references.
-
    pub fn references<'a>(
-
        &self,
-
        repo: &'a git2::Repository,
-
    ) -> Result<ext::References<'a>, git2::Error>
-
    where
-
        Self: ToString,
-
    {
-
        ext::References::from_globs(repo, [self.to_string()])
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs[/namespaces/<namespace>/refs]/rad/ids/*`
-
    pub fn rad_ids_glob(namespace: impl Into<Option<N>>) -> Self {
-
        Self {
-
            remote: None,
-
            category: RefsCategory::Rad,
-
            name: refspec_pattern!("ids/*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs[/namespaces/<namespace>/refs][/remotes/<remote>]/heads/*`
-
    pub fn heads(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Heads,
-
            name: refspec_pattern!("*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/rad/*`
-
    pub fn rads(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Rad,
-
            name: refspec_pattern!("*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/tags/*`
-
    pub fn tags(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Tags,
-
            name: refspec_pattern!("*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to:
-
    ///     * `refs[/namespaces/<namespace>]/refs[/remotes/<remote>]/notes/*`
-
    pub fn notes(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Notes,
-
            name: refspec_pattern!("*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-

-
    /// Build a reference that points to
-
    ///     * `refs[/namespaces/namespace]/refs[/remotes/<remote>]/cobs/*`
-
    pub fn cob(namespace: impl Into<Option<N>>, remote: impl Into<Option<R>>) -> Self {
-
        Self {
-
            remote: remote.into(),
-
            category: RefsCategory::Cobs,
-
            name: refspec_pattern!("*"),
-
            namespace: namespace.into(),
-
        }
-
    }
-
}
-

-
impl<N, R> Display for Reference<N, R, Many>
-
where
-
    for<'a> &'a N: AsNamespace,
-
    for<'a> &'a R: AsRemote,
-
{
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        f.write_str(Into::<ext::RefspecPattern>::into(self).as_str())
-
    }
-
}
-

-
impl<N, R> From<Reference<N, R, Many>> for ext::RefspecPattern
-
where
-
    for<'a> &'a N: AsNamespace,
-
    for<'a> &'a R: AsRemote,
-
{
-
    fn from(r: Reference<N, R, Many>) -> Self {
-
        Self::from(&r)
-
    }
-
}
-

-
impl<'a, N, R> From<&'a Reference<N, R, Many>> for ext::RefspecPattern
-
where
-
    &'a N: AsNamespace,
-
    &'a R: AsRemote,
-
{
-
    fn from(r: &'a Reference<N, R, Many>) -> Self {
-
        let mut refl = reflike!("refs");
-

-
        if let Some(ref namespace) = r.namespace {
-
            refl = refl
-
                .join(reflike!("namespaces"))
-
                .join(namespace)
-
                .join(reflike!("refs"));
-
        }
-
        if let Some(ref remote) = r.remote {
-
            refl = refl.join(reflike!("remotes")).join(remote);
-
        }
-

-
        refl.join(&r.category)
-
            .with_pattern_suffix(r.name.to_owned())
-
    }
-
}
-

-
////////////////////////////////////////////////////////////////////////////////
-

-
/// The data for creating a symbolic reference in a git repository.
-
pub struct SymbolicRef<S, T> {
-
    /// The new symbolic reference.
-
    pub source: S,
-
    /// The reference that already exists and we want to create symbolic
-
    /// reference of.
-
    pub target: T,
-
    /// Whether we should overwrite any pre-existing `source`.
-
    pub force: Force,
-
}
-

-
impl<S, T> SymbolicRef<S, T> {
-
    /// Create a symbolic reference of `target`, where the `source` is the newly
-
    /// created reference.
-
    ///
-
    /// # Errors
-
    ///
-
    ///   * If the `target` does not exist we won't create the symbolic
-
    ///     reference and we error early.
-
    ///   * If we could not create the new symbolic reference since the name
-
    ///     already exists. Note that this will not be the case if `Force::True`
-
    ///     is passed.
-
    pub fn create<'a>(&self, repo: &'a git2::Repository) -> Result<git2::Reference<'a>, git2::Error>
-
    where
-
        for<'b> &'b S: Into<ext::RefLike>,
-
        for<'b> &'b T: Into<ext::RefLike>,
-
    {
-
        let source = Into::<ext::RefLike>::into(&self.source);
-
        let target = Into::<ext::RefLike>::into(&self.target);
-

-
        let reflog_msg = &format!("creating symbolic ref {source} -> {target}");
-
        tracing::debug!("{}", reflog_msg);
-

-
        let _ = repo.refname_to_id(target.as_str())?;
-
        repo.reference_ensure_log(source.as_str())?;
-
        repo.reference_symbolic(
-
            source.as_str(),
-
            target.as_str(),
-
            self.force.as_bool(),
-
            reflog_msg,
-
        )
-
    }
-
}
deleted radicle-git-types/src/refspec.rs
@@ -1,201 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{
-
    convert::TryFrom,
-
    fmt::{self, Display},
-
    str::FromStr,
-
};
-

-
use git_ext as ext;
-

-
use super::Force;
-

-
#[derive(Debug)]
-
pub struct Refspec<S, D> {
-
    /// The source spec (LHS of the `:`).
-
    ///
-
    /// When used as a fetch spec, it refers to the remote side, while as a push
-
    /// spec it refers to the local side.
-
    pub src: S,
-

-
    /// The destination spec (RHS of the `:`).
-
    ///
-
    /// When used as a fetch spec, it refers to the local side, while as a push
-
    /// spec it refers to the remote side.
-
    pub dst: D,
-

-
    /// Whether to allow history rewrites.
-
    pub force: Force,
-
}
-

-
impl<S, D> Refspec<S, D> {
-
    pub fn into_fetchspec(self) -> Fetchspec
-
    where
-
        S: Into<ext::RefspecPattern>,
-
        D: Into<ext::RefspecPattern>,
-
    {
-
        self.into()
-
    }
-

-
    pub fn into_pushspec(self) -> Pushspec
-
    where
-
        S: Into<ext::RefLike>,
-
        D: Into<ext::RefLike>,
-
    {
-
        self.into()
-
    }
-
}
-

-
impl<S, D> Display for Refspec<S, D>
-
where
-
    for<'a> &'a S: Into<ext::RefspecPattern>,
-
    for<'a> &'a D: Into<ext::RefspecPattern>,
-
{
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        if self.force.as_bool() {
-
            f.write_str("+")?;
-
        }
-

-
        let src = Into::<ext::RefspecPattern>::into(&self.src);
-
        let dst = Into::<ext::RefspecPattern>::into(&self.dst);
-

-
        write!(f, "{src}:{dst}")
-
    }
-
}
-

-
impl TryFrom<&str> for Refspec<ext::RefspecPattern, ext::RefspecPattern> {
-
    type Error = ext::reference::name::Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        let force = s.starts_with('+').into();
-
        let specs = s.trim_start_matches('+');
-
        let mut iter = specs.split(':');
-
        let src = iter
-
            .next()
-
            .ok_or_else(ext::reference::name::Error::empty)
-
            .and_then(ext::RefspecPattern::try_from)?;
-
        let dst = iter
-
            .next()
-
            .ok_or_else(ext::reference::name::Error::empty)
-
            .and_then(ext::RefspecPattern::try_from)?;
-

-
        Ok(Self { src, dst, force })
-
    }
-
}
-

-
impl FromStr for Refspec<ext::RefspecPattern, ext::RefspecPattern> {
-
    type Err = ext::reference::name::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl TryFrom<&str> for Refspec<ext::RefLike, ext::RefLike> {
-
    type Error = ext::reference::name::Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        let force = s.starts_with('+').into();
-
        let specs = s.trim_start_matches('+');
-
        let mut iter = specs.split(':');
-
        let src = iter
-
            .next()
-
            .ok_or_else(ext::reference::name::Error::empty)
-
            .and_then(ext::RefLike::try_from)?;
-
        let dst = iter
-
            .next()
-
            .ok_or_else(ext::reference::name::Error::empty)
-
            .and_then(ext::RefLike::try_from)?;
-

-
        Ok(Self { src, dst, force })
-
    }
-
}
-

-
impl FromStr for Refspec<ext::RefLike, ext::RefLike> {
-
    type Err = ext::reference::name::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
#[derive(Debug)]
-
pub struct Fetchspec(Refspec<ext::RefspecPattern, ext::RefspecPattern>);
-

-
impl<S, D> From<Refspec<S, D>> for Fetchspec
-
where
-
    S: Into<ext::RefspecPattern>,
-
    D: Into<ext::RefspecPattern>,
-
{
-
    fn from(spec: Refspec<S, D>) -> Self {
-
        Self(Refspec {
-
            src: spec.src.into(),
-
            dst: spec.dst.into(),
-
            force: spec.force,
-
        })
-
    }
-
}
-

-
impl TryFrom<&str> for Fetchspec {
-
    type Error = ext::reference::name::Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        Refspec::try_from(s).map(Self)
-
    }
-
}
-

-
impl FromStr for Fetchspec {
-
    type Err = ext::reference::name::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl Display for Fetchspec {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        self.0.fmt(f)
-
    }
-
}
-

-
#[derive(Debug)]
-
pub struct Pushspec(Refspec<ext::RefLike, ext::RefLike>);
-

-
impl<S, D> From<Refspec<S, D>> for Pushspec
-
where
-
    S: Into<ext::RefLike>,
-
    D: Into<ext::RefLike>,
-
{
-
    fn from(spec: Refspec<S, D>) -> Self {
-
        Self(Refspec {
-
            src: spec.src.into(),
-
            dst: spec.dst.into(),
-
            force: spec.force,
-
        })
-
    }
-
}
-

-
impl TryFrom<&str> for Pushspec {
-
    type Error = ext::reference::name::Error;
-

-
    fn try_from(s: &str) -> Result<Self, Self::Error> {
-
        Refspec::try_from(s).map(Self)
-
    }
-
}
-

-
impl FromStr for Pushspec {
-
    type Err = ext::reference::name::Error;
-

-
    fn from_str(s: &str) -> Result<Self, Self::Err> {
-
        Self::try_from(s)
-
    }
-
}
-

-
impl Display for Pushspec {
-
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-
        self.0.fmt(f)
-
    }
-
}
deleted radicle-git-types/src/remote.rs
@@ -1,203 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
use std::{convert::TryFrom, str::FromStr};
-

-
use git_ext::{
-
    error::{is_exists_err, is_not_found_err},
-
    reference::{self, RefLike},
-
};
-
use std_ext::result::ResultExt as _;
-
use thiserror::Error;
-

-
use super::{Fetchspec, Pushspec};
-

-
#[derive(Debug, Error)]
-
pub enum FindError {
-
    #[error("missing {0}")]
-
    Missing(&'static str),
-

-
    #[error("failed to parse URL")]
-
    ParseUrl(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
-

-
    #[error("failed to parse refspec")]
-
    Refspec(#[from] reference::name::Error),
-

-
    #[error(transparent)]
-
    Git(#[from] git2::Error),
-
}
-

-
#[derive(Debug)]
-
pub struct Remote<Url> {
-
    /// The file path to the git monorepo.
-
    pub url: Url,
-
    /// Name of the remote, e.g. `"rad"`, `"origin"`.
-
    pub name: RefLike,
-
    /// The set of fetch specs to add upon creation.
-
    ///
-
    /// **Note**: empty fetch specs do not denote the default fetch spec
-
    /// (`refs/heads/*:refs/remote/<name>/*`), but ... empty fetch specs.
-
    pub fetchspecs: Vec<Fetchspec>,
-
    /// The set of push specs to add upon creation.
-
    pub pushspecs: Vec<Pushspec>,
-
}
-

-
impl<Url> Remote<Url> {
-
    /// Create a `"rad"` remote with a single fetch spec.
-
    pub fn rad_remote<Ref, Spec>(url: Url, fetch_spec: Ref) -> Self
-
    where
-
        Ref: Into<Option<Spec>>,
-
        Spec: Into<Fetchspec>,
-
    {
-
        Self {
-
            url,
-
            name: reflike!("rad"),
-
            fetchspecs: fetch_spec.into().into_iter().map(Into::into).collect(),
-
            pushspecs: vec![],
-
        }
-
    }
-

-
    /// Create a new `Remote` with the given `url` and `name`, while making the
-
    /// `fetch_spec` and `pushspecs` empty.
-
    pub fn new<R>(url: Url, name: R) -> Self
-
    where
-
        R: Into<RefLike>,
-
    {
-
        Self {
-
            url,
-
            name: name.into(),
-
            fetchspecs: vec![],
-
            pushspecs: vec![],
-
        }
-
    }
-

-
    /// Override the fetch specs.
-
    pub fn with_fetchspecs<I>(self, specs: I) -> Self
-
    where
-
        I: IntoIterator,
-
        <I as IntoIterator>::Item: Into<Fetchspec>,
-
    {
-
        Self {
-
            fetchspecs: specs.into_iter().map(Into::into).collect(),
-
            ..self
-
        }
-
    }
-

-
    /// Add a fetch spec.
-
    pub fn add_fetchspec(&mut self, spec: impl Into<Fetchspec>) {
-
        self.fetchspecs.push(spec.into())
-
    }
-

-
    /// Override the push specs.
-
    pub fn with_pushspecs<I>(self, specs: I) -> Self
-
    where
-
        I: IntoIterator,
-
        <I as IntoIterator>::Item: Into<Pushspec>,
-
    {
-
        Self {
-
            pushspecs: specs.into_iter().map(Into::into).collect(),
-
            ..self
-
        }
-
    }
-

-
    /// Add a push spec.
-
    pub fn add_pushspec(&mut self, spec: impl Into<Pushspec>) {
-
        self.pushspecs.push(spec.into())
-
    }
-

-
    /// Persist the remote in the `repo`'s config.
-
    ///
-
    /// If a remote with the same name already exists, previous values of the
-
    /// configuration keys `url`, `fetch`, and `push` will be overwritten.
-
    /// Note that this means that _other_ configuration keys are left
-
    /// untouched, if present.
-
    #[allow(clippy::unit_arg)]
-
    #[tracing::instrument(skip(self, repo), fields(name = self.name.as_str()))]
-
    pub fn save(&mut self, repo: &git2::Repository) -> Result<(), git2::Error>
-
    where
-
        Url: ToString,
-
    {
-
        let url = self.url.to_string();
-
        repo.remote(self.name.as_str(), &url)
-
            .and(Ok(()))
-
            .or_matches::<git2::Error, _, _>(is_exists_err, || Ok(()))?;
-

-
        {
-
            let mut config = repo.config()?;
-
            config
-
                .remove_multivar(&format!("remote.{}.url", self.name), ".*")
-
                .or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(()))?;
-
            config
-
                .remove_multivar(&format!("remote.{}.fetch", self.name), ".*")
-
                .or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(()))?;
-
            config
-
                .remove_multivar(&format!("remote.{}.push", self.name), ".*")
-
                .or_matches::<git2::Error, _, _>(is_not_found_err, || Ok(()))?;
-
        }
-

-
        repo.remote_set_url(self.name.as_str(), &url)?;
-

-
        for spec in self.fetchspecs.iter() {
-
            repo.remote_add_fetch(self.name.as_str(), &spec.to_string())?;
-
        }
-
        for spec in self.pushspecs.iter() {
-
            repo.remote_add_push(self.name.as_str(), &spec.to_string())?;
-
        }
-

-
        debug_assert!(repo.find_remote(self.name.as_str()).is_ok());
-

-
        Ok(())
-
    }
-

-
    /// Find a persisted remote by name.
-
    #[allow(clippy::unit_arg)]
-
    #[tracing::instrument(skip(repo))]
-
    pub fn find(repo: &git2::Repository, name: RefLike) -> Result<Option<Self>, FindError>
-
    where
-
        Url: FromStr,
-
        <Url as FromStr>::Err: std::error::Error + Send + Sync + 'static,
-
    {
-
        let git_remote = repo
-
            .find_remote(name.as_str())
-
            .map(Some)
-
            .or_matches::<FindError, _, _>(is_not_found_err, || Ok(None))?;
-

-
        match git_remote {
-
            None => Ok(None),
-
            Some(remote) => {
-
                let url = remote
-
                    .url()
-
                    .ok_or(FindError::Missing("url"))?
-
                    .parse()
-
                    .map_err(|e| FindError::ParseUrl(Box::new(e)))?;
-
                let fetchspecs = remote
-
                    .fetch_refspecs()?
-
                    .into_iter()
-
                    .flatten()
-
                    .map(Fetchspec::try_from)
-
                    .collect::<Result<_, _>>()?;
-
                let pushspecs = remote
-
                    .push_refspecs()?
-
                    .into_iter()
-
                    .flatten()
-
                    .map(Pushspec::try_from)
-
                    .collect::<Result<_, _>>()?;
-

-
                Ok(Some(Self {
-
                    url,
-
                    name,
-
                    fetchspecs,
-
                    pushspecs,
-
                }))
-
            },
-
        }
-
    }
-
}
-

-
impl<Url> AsRef<Url> for Remote<Url> {
-
    fn as_ref(&self) -> &Url {
-
        &self.url
-
    }
-
}
deleted radicle-git-types/src/sealed.rs
@@ -1,8 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
pub trait Sealed {}
-

-
impl Sealed for git_ext::Oid {}
deleted radicle-macros/Cargo.toml
@@ -1,20 +0,0 @@
-
[package]
-
name = "radicle-macros"
-
version = "0.1.0"
-
authors = ["The Radicle Team <dev@radicle.xyz>"]
-
edition = "2018"
-
license = "GPL-3.0-or-later"
-
description = "Radicle procedural macros"
-

-
[lib]
-
doctest = false
-
proc-macro = true
-
test = false
-

-
[dependencies]
-
proc-macro-error = "1.0.4"
-
quote = "1"
-
syn = "1"
-

-
[dependencies.radicle-git-ext]
-
path = "../radicle-git-ext"
deleted radicle-macros/src/lib.rs
@@ -1,72 +0,0 @@
-
// Copyright © 2019-2020 The Radicle Foundation <hello@radicle.foundation>
-
//
-
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
-
// Linking Exception. For full terms see the included LICENSE file.
-

-
#[macro_use]
-
extern crate proc_macro_error;
-

-
use std::convert::TryFrom;
-

-
use proc_macro::TokenStream;
-
use proc_macro_error::abort;
-
use quote::quote;
-
use syn::{parse_macro_input, LitStr};
-

-
use radicle_git_ext::reference::name::{RefLike, RefspecPattern};
-

-
/// Create `RefLike` from a string literal.
-
///
-
/// The string is validated at compile time, and an unsafe conversion is
-
/// emitted.
-
///
-
/// ```rust
-
/// use radicle_macros::reflike;
-
///
-
/// assert_eq!("lolek/bolek", reflike!("lolek/bolek").as_str())
-
/// ```
-
#[proc_macro_error]
-
#[proc_macro]
-
pub fn reflike(input: TokenStream) -> TokenStream {
-
    let lit = parse_macro_input!(input as LitStr);
-

-
    match RefLike::try_from(lit.value()) {
-
        Ok(safe) => {
-
            let safe: &str = &safe;
-
            let expand = quote! { unsafe { ::std::mem::transmute::<_, ::radicle_git_ext::RefLike>(#safe.to_owned()) }};
-
            TokenStream::from(expand)
-
        },
-

-
        Err(e) => {
-
            abort!(lit.span(), "invalid RefLike literal: {}", e);
-
        },
-
    }
-
}
-

-
/// Create a `RefspecPattern` from a string literal.
-
///
-
/// The string is validated at compile time, and an unsafe conversion is
-
/// emitted.
-
///
-
/// ```rust
-
/// use radicle_macros::refspec_pattern;
-
///
-
/// assert_eq!("refs/heads/*", refspec_pattern!("refs/heads/*").as_str())
-
/// ```
-
#[proc_macro_error]
-
#[proc_macro]
-
pub fn refspec_pattern(input: TokenStream) -> TokenStream {
-
    let lit = parse_macro_input!(input as LitStr);
-

-
    match RefspecPattern::try_from(lit.value()) {
-
        Ok(safe) => {
-
            let safe: &str = &safe;
-
            let expand = quote! { unsafe { ::std::mem::transmute::<_, ::radicle_git_ext::RefspecPattern>(#safe.to_owned()) }};
-
            TokenStream::from(expand)
-
        },
-

-
        Err(e) => {
-
            abort!(lit.span(), "invalid RefspecPattern literal: {}", e);
-
        },
-
    }
-
}
modified test/Cargo.toml
@@ -15,7 +15,7 @@ doc = false
path = "../git-commit/t"
features = ["test"]

-
[dev-dependencies.git-ext-test]
+
[dev-dependencies.radicle-git-ext-test]
path = "../radicle-git-ext/t"
features = ["test"]