~radicle-link/dev

RadRemoteUrl v1 PROPOSED

This solves the issue with a global `insteadOf` causing problems with
the internal transport. The solution is to use a `rad-internal://` scheme
for the internal transport to avoid conflicts with the persisted
`rad://` remotes.

Published-At:
    urn: rad:git:hnrkemwx6gd9r9uw1gqgc9x4urewtnamkwy5o
    seed: hydtac74mgo8xeh34cy7tmzzfejcybmxgfyawhnb4zj8wxxo4qckgh@seed.lnk.network:8799
    remote: hydjhd8q9nkoxzkpddhcuue9xzpfr4bn6d44fo1f4q1japwm4brhh6
    tag: patches/rad-remote-url/v1
Published-At: https://github.com/alexjg/radicle-link/tree/patches/rad-remote-url/v1

Alex Good (1):
  Introduce RadRemoteUrl

 cli/gitd-lib/src/args.rs                      |  5 +-
 cli/lnk-identities/src/git.rs                 | 27 +++---
 cli/lnk-identities/src/git/checkout.rs        | 15 ++--
 cli/lnk-identities/src/git/existing.rs        |  4 +-
 cli/lnk-identities/src/git/include.rs         |  4 +-
 cli/lnk-identities/src/git/new.rs             |  8 +-
 cli/lnk-identities/src/person.rs              |  5 +-
 cli/lnk-identities/src/project.rs             |  5 +-
 .../t/src/tests/git/checkout.rs               | 25 +++---
 .../t/src/tests/git/existing.rs               | 19 ++--
 cli/lnk-identities/t/src/tests/git/new.rs     | 13 ++-
 git-helpers/src/credential.rs                 | 14 +--
 git-helpers/src/remote_helper.rs              | 12 +--
 git-helpers/t/src/integration/remote.rs       |  6 +-
 librad/src/git.rs                             |  1 +
 librad/src/git/include.rs                     | 27 +++---
 librad/src/git/local.rs                       |  2 +-
 librad/src/git/local/transport.rs             |  8 +-
 librad/src/git/local/transport/internal.rs    | 14 +--
 librad/src/git/local/url.rs                   | 19 ++--
 librad/src/git/rad_url.rs                     | 89 +++++++++++++++++++
 librad/src/git/types/remote.rs                | 21 +++--
 .../src/integration/scenario/working_copy.rs  |  6 +-
 librad/t/src/integration/smoke/gossip.rs      |  4 +-
 librad/t/src/tests/git/include.rs             | 13 +--
 librad/t/src/tests/git/local/url.rs           |  4 +-
 librad/t/src/tests/git/types/remote.rs        |  8 +-
 test/it-helpers/src/working_copy.rs           |  6 +-
 28 files changed, 253 insertions(+), 131 deletions(-)
 create mode 100644 librad/src/git/rad_url.rs

-- 
2.36.1
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~radicle-link/dev/patches/33213/mbox | git am -3
Learn more about email & git

[PATCH v1 1/1] Introduce RadRemoteUrl Export this patch

`LocalUrl` was being used throughout the codebase to represent a git
remote with a url fo the form `rad://<urn>.git`. This is slightly
confusing because `LocalUrl` had an `active_index` field which is
intended purely for use with the local transport internals. Furthermore,
the use of the `rad` url scheme causes conflicts with our intended
git configuration using a global `insteadOf` stanza to point `rad://`
URLs at the `lnk-gitd`.

Introduce `RadRemoteUrl` to represent remotes which are intended to be
saved to git config in a repository with the `rad://` URL scheme. Rename
`LocalUrl` to `LocalTransportUrl` and modify
it to use URLs of the form `rad-internal://` then only use
`LocalTransportUrl` in the internal transport implementation.
Signed-off-by: Alex Good <alex@memoryandthought.me>
---
 cli/gitd-lib/src/args.rs                      |  5 +-
 cli/lnk-identities/src/git.rs                 | 27 +++---
 cli/lnk-identities/src/git/checkout.rs        | 15 ++--
 cli/lnk-identities/src/git/existing.rs        |  4 +-
 cli/lnk-identities/src/git/include.rs         |  4 +-
 cli/lnk-identities/src/git/new.rs             |  8 +-
 cli/lnk-identities/src/person.rs              |  5 +-
 cli/lnk-identities/src/project.rs             |  5 +-
 .../t/src/tests/git/checkout.rs               | 25 +++---
 .../t/src/tests/git/existing.rs               | 19 ++--
 cli/lnk-identities/t/src/tests/git/new.rs     | 13 ++-
 git-helpers/src/credential.rs                 | 14 +--
 git-helpers/src/remote_helper.rs              | 12 +--
 git-helpers/t/src/integration/remote.rs       |  6 +-
 librad/src/git.rs                             |  1 +
 librad/src/git/include.rs                     | 27 +++---
 librad/src/git/local.rs                       |  2 +-
 librad/src/git/local/transport.rs             |  8 +-
 librad/src/git/local/transport/internal.rs    | 14 +--
 librad/src/git/local/url.rs                   | 19 ++--
 librad/src/git/rad_url.rs                     | 89 +++++++++++++++++++
 librad/src/git/types/remote.rs                | 21 +++--
 .../src/integration/scenario/working_copy.rs  |  6 +-
 librad/t/src/integration/smoke/gossip.rs      |  4 +-
 librad/t/src/tests/git/include.rs             | 13 +--
 librad/t/src/tests/git/local/url.rs           |  4 +-
 librad/t/src/tests/git/types/remote.rs        |  8 +-
 test/it-helpers/src/working_copy.rs           |  6 +-
 28 files changed, 253 insertions(+), 131 deletions(-)
 create mode 100644 librad/src/git/rad_url.rs

diff --git a/cli/gitd-lib/src/args.rs b/cli/gitd-lib/src/args.rs
index 2ec4d966..1404f73d 100644
--- a/cli/gitd-lib/src/args.rs
+++ b/cli/gitd-lib/src/args.rs
@@ -61,7 +61,10 @@ impl Args {
        self,
        spawner: Arc<link_async::Spawner>,
    ) -> Result<Config<BoxedSigner>, Error> {
        let home = self.lnk_home.map(LnkHome::Root).unwrap_or(LnkHome::ProjectDirs);
        let home = self
            .lnk_home
            .map(LnkHome::Root)
            .unwrap_or(LnkHome::ProjectDirs);
        let profile = Profile::from_home(&home, None)?;
        let signer = spawner
            .blocking({
diff --git a/cli/lnk-identities/src/git.rs b/cli/lnk-identities/src/git.rs
index c9ed5e90..f54428bf 100644
--- a/cli/lnk-identities/src/git.rs
+++ b/cli/lnk-identities/src/git.rs
@@ -10,10 +10,8 @@ use nonempty::NonEmpty;
use librad::{
    canonical::Cstring,
    git::{
        local::{
            transport::{self, CanOpenStorage},
            url::LocalUrl,
        },
        local::transport::{self, CanOpenStorage},
        rad_url::RadRemoteUrl,
        types::{
            remote::{LocalFetchspec, LocalPushspec, Remote},
            Fetchspec,
@@ -56,9 +54,9 @@ pub enum Error {
pub fn setup_remote<F>(
    repo: &git2::Repository,
    open_storage: F,
    url: LocalUrl,
    url: RadRemoteUrl,
    default_branch: &OneLevel,
) -> Result<Remote<LocalUrl>, Error>
) -> Result<Remote<RadRemoteUrl>, Error>
where
    F: CanOpenStorage + Clone + 'static,
{
@@ -187,8 +185,8 @@ pub fn set_upstream<Url>(
pub fn clone<F>(
    path: &Path,
    storage: F,
    mut remote: Remote<LocalUrl>,
) -> Result<(git2::Repository, Remote<LocalUrl>), Error>
    mut remote: Remote<RadRemoteUrl>,
) -> Result<(git2::Repository, Remote<RadRemoteUrl>), Error>
where
    F: CanOpenStorage + 'static,
{
@@ -221,7 +219,7 @@ pub mod validation {

    use librad::{
        git::{
            local::url::LocalUrl,
            rad_url::RadRemoteUrl,
            types::remote::{self, Remote},
        },
        git_ext::{self, OneLevel},
@@ -238,7 +236,10 @@ pub mod validation {
        },

        #[error("a `rad` remote exists with the URL `{found}`, the expected URL for this project is `{expected}`. If you want to continue with creating this project you will need to remove the existing `rad` remote entry.")]
        UrlMismatch { expected: LocalUrl, found: LocalUrl },
        UrlMismatch {
            expected: RadRemoteUrl,
            found: RadRemoteUrl,
        },

        #[error(transparent)]
        Remote(#[from] remote::FindError),
@@ -262,9 +263,9 @@ pub mod validation {

    pub fn remote(
        repo: &git2::Repository,
        url: &LocalUrl,
    ) -> Result<Option<Remote<LocalUrl>>, Error> {
        match Remote::<LocalUrl>::find(repo, reflike!("rad")) {
        url: &RadRemoteUrl,
    ) -> Result<Option<Remote<RadRemoteUrl>>, Error> {
        match Remote::<RadRemoteUrl>::find(repo, reflike!("rad")) {
            Err(err) => Err(Error::Remote(err)),
            Ok(Some(remote)) if remote.url != *url => Err(Error::UrlMismatch {
                expected: url.clone(),
diff --git a/cli/lnk-identities/src/git/checkout.rs b/cli/lnk-identities/src/git/checkout.rs
index b85163dd..c675a4e7 100644
--- a/cli/lnk-identities/src/git/checkout.rs
+++ b/cli/lnk-identities/src/git/checkout.rs
@@ -10,7 +10,8 @@ use either::Either;
use librad::{
    git::{
        identities::{self, Person},
        local::{transport::CanOpenStorage, url::LocalUrl},
        local::transport::CanOpenStorage,
        rad_url::RadRemoteUrl,
        storage::ReadOnly,
        types::{
            remote::{LocalFetchspec, LocalPushspec, Remote},
@@ -137,7 +138,7 @@ where
}

pub struct Local {
    url: LocalUrl,
    url: RadRemoteUrl,
    path: PathBuf,
}

@@ -147,12 +148,12 @@ impl Local {
        I: HasName + HasUrn,
    {
        Self {
            url: LocalUrl::from(identity.urn()),
            url: RadRemoteUrl::from(identity.urn()),
            path,
        }
    }

    fn checkout<F>(self, open_storage: F) -> Result<(git2::Repository, Remote<LocalUrl>), Error>
    fn checkout<F>(self, open_storage: F) -> Result<(git2::Repository, Remote<RadRemoteUrl>), Error>
    where
        F: CanOpenStorage + 'static,
    {
@@ -169,7 +170,7 @@ impl Local {
}

pub struct Peer {
    url: LocalUrl,
    url: RadRemoteUrl,
    remote: (Person, PeerId),
    default_branch: OneLevel,
    path: PathBuf,
@@ -183,14 +184,14 @@ impl Peer {
        let urn = identity.urn();
        let default_branch = identity.branch_or_die(urn.clone())?;
        Ok(Self {
            url: LocalUrl::from(urn),
            url: RadRemoteUrl::from(urn),
            remote,
            default_branch,
            path,
        })
    }

    fn checkout<F>(self, open_storage: F) -> Result<(git2::Repository, Remote<LocalUrl>), Error>
    fn checkout<F>(self, open_storage: F) -> Result<(git2::Repository, Remote<RadRemoteUrl>), Error>
    where
        F: CanOpenStorage + Clone + 'static,
    {
diff --git a/cli/lnk-identities/src/git/existing.rs b/cli/lnk-identities/src/git/existing.rs
index bc542969..cf001a06 100644
--- a/cli/lnk-identities/src/git/existing.rs
+++ b/cli/lnk-identities/src/git/existing.rs
@@ -8,7 +8,7 @@ use std::{fmt, marker::PhantomData, path::PathBuf};
use serde::{Deserialize, Serialize};

use librad::{
    git::local::{transport::CanOpenStorage, url::LocalUrl},
    git::{local::transport::CanOpenStorage, rad_url::RadRemoteUrl},
    git_ext,
    std_ext::result::ResultExt as _,
};
@@ -91,7 +91,7 @@ impl fmt::Debug for Valid {
}

impl<P: HasBranch> Existing<Valid, P> {
    pub fn init<F>(self, url: LocalUrl, open_storage: F) -> Result<git2::Repository, Error>
    pub fn init<F>(self, url: RadRemoteUrl, open_storage: F) -> Result<git2::Repository, Error>
    where
        F: CanOpenStorage + Clone + 'static,
    {
diff --git a/cli/lnk-identities/src/git/include.rs b/cli/lnk-identities/src/git/include.rs
index a4e64cc9..cb796784 100644
--- a/cli/lnk-identities/src/git/include.rs
+++ b/cli/lnk-identities/src/git/include.rs
@@ -9,7 +9,6 @@ use librad::{
    git::{
        identities,
        include::{self, Include},
        local::url::LocalUrl,
        storage::ReadOnly,
    },
    git_ext,
@@ -46,11 +45,10 @@ where
    I: HasUrn,
{
    let urn = identity.urn();
    let url = LocalUrl::from(urn.clone());
    let tracked = identities::relations::tracked(storage, &urn)?;
    let include = Include::from_tracked_persons(
        paths.git_includes_dir().to_path_buf(),
        url,
        urn,
        tracked
            .into_iter()
            .filter_map(|peer| {
diff --git a/cli/lnk-identities/src/git/new.rs b/cli/lnk-identities/src/git/new.rs
index c8c620f6..cdafd428 100644
--- a/cli/lnk-identities/src/git/new.rs
+++ b/cli/lnk-identities/src/git/new.rs
@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};

use librad::{
    canonical::Cstring,
    git::local::{transport::CanOpenStorage, url::LocalUrl},
    git::{local::transport::CanOpenStorage, rad_url::RadRemoteUrl},
    git_ext::OneLevel,
    identities::payload,
};
@@ -75,7 +75,7 @@ impl<P> New<Invalid, P> {
}

impl New<Valid, payload::ProjectPayload> {
    pub fn init<F>(self, url: LocalUrl, open_storage: F) -> Result<git2::Repository, Error>
    pub fn init<F>(self, url: RadRemoteUrl, open_storage: F) -> Result<git2::Repository, Error>
    where
        F: CanOpenStorage + Clone + 'static,
    {
@@ -92,7 +92,7 @@ impl New<Valid, payload::ProjectPayload> {
}

impl New<Valid, payload::PersonPayload> {
    pub fn init<F>(self, url: LocalUrl, open_storage: F) -> Result<git2::Repository, Error>
    pub fn init<F>(self, url: RadRemoteUrl, open_storage: F) -> Result<git2::Repository, Error>
    where
        F: CanOpenStorage + Clone + 'static,
    {
@@ -106,7 +106,7 @@ fn init<F>(
    path: PathBuf,
    default: OneLevel,
    description: &Option<Cstring>,
    url: LocalUrl,
    url: RadRemoteUrl,
    open_storage: F,
) -> Result<git2::Repository, Error>
where
diff --git a/cli/lnk-identities/src/person.rs b/cli/lnk-identities/src/person.rs
index 7e409018..9213cff2 100644
--- a/cli/lnk-identities/src/person.rs
+++ b/cli/lnk-identities/src/person.rs
@@ -11,7 +11,8 @@ use librad::{
    crypto::{BoxedSigner, PublicKey},
    git::{
        identities::{self, person, relations, Person},
        local::{transport, url::LocalUrl},
        local::transport,
        rad_url::RadRemoteUrl,
        storage::{ReadOnly, Storage},
        types::{Namespace, Reference},
        Urn,
@@ -83,7 +84,7 @@ where
    direct.extend(delegations.into_iter());

    let urn = person::urn(storage, payload.clone(), direct.clone())?;
    let url = LocalUrl::from(urn);
    let url = RadRemoteUrl::from(urn);
    let settings = transport::Settings {
        paths: paths.clone(),
        signer,
diff --git a/cli/lnk-identities/src/project.rs b/cli/lnk-identities/src/project.rs
index 2469d65a..14856cb2 100644
--- a/cli/lnk-identities/src/project.rs
+++ b/cli/lnk-identities/src/project.rs
@@ -12,7 +12,8 @@ use librad::{
    crypto::BoxedSigner,
    git::{
        identities::{self, local::LocalIdentity, project, relations, Project},
        local::{transport, url::LocalUrl},
        local::transport,
        rad_url::RadRemoteUrl,
        storage::{ReadOnly, Storage},
        types::{Namespace, Reference},
        Urn,
@@ -125,7 +126,7 @@ where
    let delegations = resolve_indirect(storage, delegations)?;

    let urn = project::urn(storage, payload.clone(), delegations.clone())?;
    let url = LocalUrl::from(urn);
    let url = RadRemoteUrl::from(urn);
    let settings = transport::Settings {
        paths: paths.clone(),
        signer,
diff --git a/cli/lnk-identities/t/src/tests/git/checkout.rs b/cli/lnk-identities/t/src/tests/git/checkout.rs
index 5177eb38..724b364f 100644
--- a/cli/lnk-identities/t/src/tests/git/checkout.rs
+++ b/cli/lnk-identities/t/src/tests/git/checkout.rs
@@ -16,12 +16,7 @@ use it_helpers::{
use librad::{
    canonical::Cstring,
    crypto::SecretKey,
    git::{
        identities::local,
        local::{transport, url::LocalUrl},
        util,
        Storage,
    },
    git::{identities::local, local::transport, rad_url::RadRemoteUrl, util, Storage},
    git_ext::tree,
    reflike,
    PeerId,
@@ -57,7 +52,7 @@ fn local_checkout() -> anyhow::Result<()> {
    )?;
    let branch = proj.project.subject().default_branch.as_ref().unwrap();
    assert_head(&repo, branch)?;
    assert_remote(&repo, branch, &LocalUrl::from(proj.project.urn()))?;
    assert_remote(&repo, branch, &RadRemoteUrl::from(proj.project.urn()))?;
    Ok(())
}

@@ -121,13 +116,13 @@ fn remote_checkout() {
            .unwrap();
        let branch = proj.project.subject().default_branch.as_ref().unwrap();
        assert_head(&repo, branch).unwrap();
        assert_remote(&repo, branch, &LocalUrl::from(proj.project.urn())).unwrap();
        assert_remote(&repo, branch, &RadRemoteUrl::from(proj.project.urn())).unwrap();
        assert_peer_remote(
            &repo,
            branch,
            &proj.owner.subject().name,
            &peer1.peer_id(),
            &LocalUrl::from(proj.project.urn()),
            &RadRemoteUrl::from(proj.project.urn()),
        )
        .unwrap();
    })
@@ -159,9 +154,13 @@ fn assert_head(repo: &git2::Repository, branch: &Cstring) -> anyhow::Result<()>

/// Assert that:
///   * the `rad` remote exists
///   * its URL matches the `LocalUrl`
///   * its URL matches the `RadRemoteUrl`
///   * its upstream branch is the default branch
fn assert_remote(repo: &git2::Repository, branch: &Cstring, url: &LocalUrl) -> anyhow::Result<()> {
fn assert_remote(
    repo: &git2::Repository,
    branch: &Cstring,
    url: &RadRemoteUrl,
) -> anyhow::Result<()> {
    let rad = repo.find_remote("rad")?;
    assert_eq!(rad.url().unwrap(), &url.to_string());

@@ -176,14 +175,14 @@ fn assert_remote(repo: &git2::Repository, branch: &Cstring, url: &LocalUrl) -> a

/// Assert that:
///   * the peer remote exists
///   * its URL matches the `LocalUrl`
///   * its URL matches the `RadRemoteUrl`
///   * the refs/remotes/<handle>@<peer>/<branch> exists
fn assert_peer_remote(
    repo: &git2::Repository,
    branch: &Cstring,
    handle: &Cstring,
    peer: &PeerId,
    url: &LocalUrl,
    url: &RadRemoteUrl,
) -> anyhow::Result<()> {
    let remote_name = format!("{}@{}", handle, peer);
    let remote = repo.find_remote(&remote_name)?;
diff --git a/cli/lnk-identities/t/src/tests/git/existing.rs b/cli/lnk-identities/t/src/tests/git/existing.rs
index f3fbc2e8..fbd7c0af 100644
--- a/cli/lnk-identities/t/src/tests/git/existing.rs
+++ b/cli/lnk-identities/t/src/tests/git/existing.rs
@@ -14,7 +14,8 @@ use librad::{
    crypto::SecretKey,
    git::{
        identities::project::ProjectPayload,
        local::{transport, url::LocalUrl},
        local::transport,
        rad_url::RadRemoteUrl,
        storage::Storage,
        types::remote::Remote,
        Urn,
@@ -81,7 +82,7 @@ fn validation_different_remote_exists() -> anyhow::Result<()> {
        )?;

        let urn = Urn::new(git2::Oid::zero().into());
        let url = LocalUrl::from(urn);
        let url = RadRemoteUrl::from(urn);
        let mut remote = Remote::new(url, reflike!("rad"));
        remote.save(&repo)?;

@@ -95,7 +96,7 @@ fn validation_different_remote_exists() -> anyhow::Result<()> {
        let storage = Storage::open(&*paths, signer.clone())?;
        let proj = TestProject::create(&storage)?;
        let urn = proj.project.urn();
        let url = LocalUrl::from(urn);
        let url = RadRemoteUrl::from(urn);
        let settings = transport::Settings {
            paths: paths.clone(),
            signer: signer.into(),
@@ -128,7 +129,7 @@ fn validation_remote_exists() -> anyhow::Result<()> {
        let validated = new.validate()?;
        let proj = TestProject::create(&storage)?;
        let urn = proj.project.urn();
        let url = LocalUrl::from(urn);
        let url = RadRemoteUrl::from(urn);
        let settings = transport::Settings {
            paths: paths.clone(),
            signer: signer.into(),
@@ -173,7 +174,7 @@ fn creation() -> anyhow::Result<()> {
        let storage = Storage::open(&*paths, signer.clone())?;
        let proj = TestProject::create(&storage)?;
        let urn = proj.project.urn();
        let url = LocalUrl::from(urn);
        let url = RadRemoteUrl::from(urn);
        let settings = transport::Settings {
            paths: paths.clone(),
            signer: signer.into(),
@@ -188,9 +189,13 @@ fn creation() -> anyhow::Result<()> {

/// Assert that:
///   * the `rad` remote exists
///   * its URL matches the `LocalUrl`
///   * its URL matches the `RadRemoteUrl`
///   * the default branch exists under the remote
fn assert_remote(repo: &git2::Repository, branch: &Cstring, url: &LocalUrl) -> anyhow::Result<()> {
fn assert_remote(
    repo: &git2::Repository,
    branch: &Cstring,
    url: &RadRemoteUrl,
) -> anyhow::Result<()> {
    let rad = repo.find_remote("rad")?;
    assert_eq!(rad.url().unwrap(), &url.to_string());

diff --git a/cli/lnk-identities/t/src/tests/git/new.rs b/cli/lnk-identities/t/src/tests/git/new.rs
index edb85d8b..8c5f0dce 100644
--- a/cli/lnk-identities/t/src/tests/git/new.rs
+++ b/cli/lnk-identities/t/src/tests/git/new.rs
@@ -13,7 +13,8 @@ use librad::{
    crypto::SecretKey,
    git::{
        identities::project::ProjectPayload,
        local::{transport, url::LocalUrl},
        local::transport,
        rad_url::RadRemoteUrl,
        storage::Storage,
    },
};
@@ -60,7 +61,7 @@ fn creation() -> anyhow::Result<()> {
        let storage = Storage::open(&*paths, signer.clone())?;
        let proj = TestProject::create(&storage)?;
        let urn = proj.project.urn();
        let url = LocalUrl::from(urn);
        let url = RadRemoteUrl::from(urn);
        let settings = transport::Settings {
            paths: paths.clone(),
            signer: signer.into(),
@@ -98,9 +99,13 @@ fn assert_head(repo: &git2::Repository, branch: &Cstring) -> anyhow::Result<()>

/// Assert that:
///   * the `rad` remote exists
///   * its URL matches the `LocalUrl`
///   * its URL matches the `RadRemoteUrl`
///   * its upstream branch is the default branch
fn assert_remote(repo: &git2::Repository, branch: &Cstring, url: &LocalUrl) -> anyhow::Result<()> {
fn assert_remote(
    repo: &git2::Repository,
    branch: &Cstring,
    url: &RadRemoteUrl,
) -> anyhow::Result<()> {
    let rad = repo.find_remote("rad")?;
    assert_eq!(rad.url().unwrap(), &url.to_string());

diff --git a/git-helpers/src/credential.rs b/git-helpers/src/credential.rs
index 4c3f03bc..58f79ae6 100644
--- a/git-helpers/src/credential.rs
+++ b/git-helpers/src/credential.rs
@@ -10,13 +10,13 @@ use std::{
    process::{Command, Stdio},
};

use librad::{crypto::keystore::pinentry::SecUtf8, git::local::url::LocalUrl};
use librad::{crypto::keystore::pinentry::SecUtf8, git::rad_url::RadRemoteUrl};

pub type Passphrase = SecUtf8;

pub trait Credential {
    fn get(&self, url: &LocalUrl) -> io::Result<Passphrase>;
    fn put(&mut self, url: &LocalUrl, passphrase: Passphrase) -> io::Result<()>;
    fn get(&self, url: &RadRemoteUrl) -> io::Result<Passphrase>;
    fn put(&mut self, url: &RadRemoteUrl, passphrase: Passphrase) -> io::Result<()>;
}

pub struct Git {
@@ -30,7 +30,7 @@ impl Git {
        }
    }

    pub fn get(&self, url: &LocalUrl) -> io::Result<Passphrase> {
    pub fn get(&self, url: &RadRemoteUrl) -> io::Result<Passphrase> {
        let mut child = Command::new("git")
            .env("GIT_DIR", &self.git_dir)
            .envs(env::vars().filter(|(key, _)| key.starts_with("GIT_TRACE")))
@@ -56,7 +56,7 @@ impl Git {
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "couldn't obtain passphrase"))
    }

    pub fn put(&mut self, url: &LocalUrl, passphrase: Passphrase) -> io::Result<()> {
    pub fn put(&mut self, url: &RadRemoteUrl, passphrase: Passphrase) -> io::Result<()> {
        let mut child = Command::new("git")
            .env("GIT_DIR", &self.git_dir)
            .envs(env::vars().filter(|(key, _)| key.starts_with("GIT_TRACE")))
@@ -82,11 +82,11 @@ impl Git {
}

impl Credential for Git {
    fn get(&self, url: &LocalUrl) -> io::Result<Passphrase> {
    fn get(&self, url: &RadRemoteUrl) -> io::Result<Passphrase> {
        self.get(url)
    }

    fn put(&mut self, url: &LocalUrl, passphrase: Passphrase) -> io::Result<()> {
    fn put(&mut self, url: &RadRemoteUrl, passphrase: Passphrase) -> io::Result<()> {
        self.put(url, passphrase)
    }
}
diff --git a/git-helpers/src/remote_helper.rs b/git-helpers/src/remote_helper.rs
index 067ee597..49be21ec 100644
--- a/git-helpers/src/remote_helper.rs
+++ b/git-helpers/src/remote_helper.rs
@@ -19,9 +19,9 @@ use librad::{
        BoxedSigner,
        SomeSigner,
    },
    git::local::{
        transport::{CanOpenStorage, LocalTransport, Localio, Mode::Stateful, Settings},
        url::LocalUrl,
    git::{
        local::transport::{CanOpenStorage, LocalTransport, Localio, Mode::Stateful, Settings},
        rad_url::RadRemoteUrl,
    },
    profile::Profile,
    PublicKey,
@@ -53,7 +53,7 @@ See https://git-scm.com/docs/git-remote-ext for more detail."#
        args[0]
            .parse()
            .or_else(|_| args[1].parse())
            .map_err(|_| anyhow::anyhow!("invalid args: {:?}", args))
            .map_err(|e| anyhow::anyhow!("invalid args: {:?}: {}", args, e))
    }?;

    let git_dir = env::var("GIT_DIR").map(PathBuf::from)?;
@@ -89,7 +89,7 @@ See https://git-scm.com/docs/git-remote-ext for more detail."#
            println!();

            transport
                .connect(url, service, Stateful, Localio::inherit())?
                .connect(url.into(), service, Stateful, Localio::inherit())?
                .wait()?;

            break;
@@ -101,7 +101,7 @@ See https://git-scm.com/docs/git-remote-ext for more detail."#
    Ok(())
}

fn get_signer(git_dir: &Path, keys_dir: &Path, url: &LocalUrl) -> anyhow::Result<BoxedSigner> {
fn get_signer(git_dir: &Path, keys_dir: &Path, url: &RadRemoteUrl) -> anyhow::Result<BoxedSigner> {
    let mut cred = credential::Git::new(git_dir);
    let pass = cred.get(url)?;
    let file = keys_dir.join(SECRET_KEY_FILE);
diff --git a/git-helpers/t/src/integration/remote.rs b/git-helpers/t/src/integration/remote.rs
index 1f704d60..62ae55e9 100644
--- a/git-helpers/t/src/integration/remote.rs
+++ b/git-helpers/t/src/integration/remote.rs
@@ -12,7 +12,7 @@ use std::{
use it_helpers::fixed::TestProject;
use librad::{
    crypto::keystore::{self, crypto, pinentry::SecUtf8, Keystore},
    git::{local::url::LocalUrl, storage::Storage, Urn},
    git::{rad_url::RadRemoteUrl, storage::Storage, Urn},
    paths::Paths,
    profile::Profile,
    PublicKey,
@@ -62,7 +62,7 @@ fn smoke() {
            .arg("-c")
            .arg(format!("credential.helper={}", credential_helper()))
            .arg("clone")
            .arg(LocalUrl::from(urn).to_string())
            .arg(RadRemoteUrl::from(urn).to_string())
            .arg(repo_dir.path())
            .env("PATH", &path)
            .env("LNK_HOME", lnk_dir.path())
@@ -113,7 +113,7 @@ fn setup_repo(path: &Path, origin: &Urn) -> anyhow::Result<()> {
    )?;

    repo.set_head("refs/heads/master")?;
    repo.remote("origin", &LocalUrl::from(origin.clone()).to_string())?;
    repo.remote("origin", &RadRemoteUrl::from(origin.clone()).to_string())?;

    let mut config = repo.config()?;
    config
diff --git a/librad/src/git.rs b/librad/src/git.rs
index 1aa7bf7c..df24e6cb 100644
--- a/librad/src/git.rs
+++ b/librad/src/git.rs
@@ -8,6 +8,7 @@ pub mod identities;
pub mod include;
pub mod local;
pub mod p2p;
pub mod rad_url;
pub mod refs;

pub mod storage;
diff --git a/librad/src/git/include.rs b/librad/src/git/include.rs
index 8384ce6e..b488ec76 100644
--- a/librad/src/git/include.rs
+++ b/librad/src/git/include.rs
@@ -14,8 +14,9 @@ use git_ext as ext;
use tempfile::NamedTempFile;

use super::{
    local::url::LocalUrl,
    rad_url::RadRemoteUrl,
    types::{Flat, Force, GenericRef, Reference, Refspec, Remote},
    Urn,
};
use crate::PeerId;

@@ -59,7 +60,7 @@ pub enum Error {
/// ```
pub struct Include<Path> {
    /// The list of remotes that will be generated for this include file.
    remotes: Vec<Remote<LocalUrl>>,
    remotes: Vec<Remote<RadRemoteUrl>>,
    /// The directory path where the include file will be stored.
    pub path: Path,
    /// The namespace and `PeerId` this include file is interested in. In other
@@ -68,20 +69,20 @@ pub struct Include<Path> {
    ///
    /// Note that the final file name will be named after
    /// the namespace.
    pub local_url: LocalUrl,
    pub namespace: Urn,
}

impl<Path> Include<Path> {
    /// Create a new `Include` with an empty set of remotes.
    pub fn new(path: Path, local_url: LocalUrl) -> Self {
    pub fn new(path: Path, namespace: Urn) -> Self {
        Include {
            remotes: vec![],
            path,
            local_url,
            namespace,
        }
    }

    pub fn add_remote(&mut self, url: LocalUrl, peer: PeerId, handle: impl Into<ext::RefLike>) {
    pub fn add_remote(&mut self, url: RadRemoteUrl, peer: PeerId, handle: impl Into<ext::RefLike>) {
        let remote = Self::build_remote(url, peer, handle);
        self.remotes.push(remote);
    }
@@ -135,7 +136,7 @@ impl<Path> Include<Path> {
        self.path
            .as_ref()
            .to_path_buf()
            .join(self.local_url.urn.encode_id())
            .join(self.namespace.encode_id())
            .with_extension("inc")
    }

@@ -145,7 +146,7 @@ impl<Path> Include<Path> {
    /// The tracked personal identities are expected to be retrieved by talking
    /// to the [`crate::git::storage::Storage`].
    #[tracing::instrument(level = "debug", skip(tracked))]
    pub fn from_tracked_persons<R, I>(path: Path, local_url: LocalUrl, tracked: I) -> Self
    pub fn from_tracked_persons<R, I>(path: Path, urn: Urn, tracked: I) -> Self
    where
        Path: Debug,
        R: Into<ext::RefLike>,
@@ -153,22 +154,24 @@ impl<Path> Include<Path> {
    {
        let remotes = tracked
            .into_iter()
            .map(|(handle, peer)| Self::build_remote(local_url.clone(), peer, handle.into()))
            .map(|(handle, peer)| {
                Self::build_remote(RadRemoteUrl::from(urn.clone()), peer, handle.into())
            })
            .collect();
        tracing::trace!("computed remotes: {:?}", remotes);

        Self {
            remotes,
            path,
            local_url,
            namespace: urn,
        }
    }

    fn build_remote(
        url: LocalUrl,
        url: RadRemoteUrl,
        peer: PeerId,
        handle: impl Into<ext::RefLike>,
    ) -> Remote<LocalUrl> {
    ) -> Remote<RadRemoteUrl> {
        let handle = handle.into();
        let name = ext::RefLike::try_from(format!("{}@{}", handle, peer))
            .expect("handle and peer are reflike");
diff --git a/librad/src/git/local.rs b/librad/src/git/local.rs
index bb850642..a9bc4780 100644
--- a/librad/src/git/local.rs
+++ b/librad/src/git/local.rs
@@ -6,6 +6,6 @@
pub mod transport;
pub mod url;

pub const URL_SCHEME: &str = "rad";
pub const URL_SCHEME: &str = "rad-internal";

use super::Urn;
diff --git a/librad/src/git/local/transport.rs b/librad/src/git/local/transport.rs
index 559958e2..9bffda58 100644
--- a/librad/src/git/local/transport.rs
+++ b/librad/src/git/local/transport.rs
@@ -24,7 +24,7 @@ use super::{
        types::Namespace,
        Urn,
    },
    url::LocalUrl,
    url::LocalTransportUrl,
};
use crate::paths::Paths;

@@ -72,12 +72,12 @@ pub trait CanOpenStorage: Send + Sync {

pub(crate) fn with_local_transport<F, G, A>(
    open_storage: F,
    url: LocalUrl,
    url: LocalTransportUrl,
    g: G,
) -> Result<A, Error>
where
    F: CanOpenStorage + 'static,
    G: FnOnce(LocalUrl) -> A,
    G: FnOnce(LocalTransportUrl) -> A,
{
    internal::with(open_storage, url, g)
}
@@ -178,7 +178,7 @@ impl LocalTransport {
    #[tracing::instrument(level = "debug", skip(self, service, stdio))]
    pub fn connect(
        &mut self,
        url: LocalUrl,
        url: LocalTransportUrl,
        service: Service,
        mode: Mode,
        stdio: Localio,
diff --git a/librad/src/git/local/transport/internal.rs b/librad/src/git/local/transport/internal.rs
index c17b8e59..e33a5cd4 100644
--- a/librad/src/git/local/transport/internal.rs
+++ b/librad/src/git/local/transport/internal.rs
@@ -21,12 +21,16 @@ use git_ext::{into_git_err, RECEIVE_PACK_HEADER, UPLOAD_PACK_HEADER};
use rustc_hash::FxHashMap;
use thiserror::Error;

use super::{super::url::LocalUrl, CanOpenStorage};
use super::{super::url::LocalTransportUrl, CanOpenStorage};

pub(super) fn with<F, G, A>(open_storage: F, url: LocalUrl, g: G) -> Result<A, super::Error>
pub(super) fn with<F, G, A>(
    open_storage: F,
    url: LocalTransportUrl,
    g: G,
) -> Result<A, super::Error>
where
    F: CanOpenStorage + 'static,
    G: FnOnce(LocalUrl) -> A,
    G: FnOnce(LocalTransportUrl) -> A,
{
    let (tx, rx) = mpsc::channel();
    let act = Active {
@@ -35,7 +39,7 @@ where
    };
    let fct = Factory::new();
    let idx = fct.add(act);
    let url = LocalUrl {
    let url = LocalTransportUrl {
        active_index: Some(idx),
        ..url
    };
@@ -148,7 +152,7 @@ impl git2::transport::SmartSubtransport for Factory {
        url: &str,
        service: git2::transport::Service,
    ) -> Result<Box<dyn git2::transport::SmartSubtransportStream>, git2::Error> {
        let url = url.parse::<LocalUrl>().map_err(|e| {
        let url = url.parse::<LocalTransportUrl>().map_err(|e| {
            git2::Error::new(
                git2::ErrorCode::Invalid,
                git2::ErrorClass::Invalid,
diff --git a/librad/src/git/local/url.rs b/librad/src/git/local/url.rs
index f92180b7..075ee1fd 100644
--- a/librad/src/git/local/url.rs
+++ b/librad/src/git/local/url.rs
@@ -16,12 +16,12 @@ use thiserror::Error;
use super::Urn;

#[derive(Clone, Debug, PartialEq)]
pub struct LocalUrl {
pub struct LocalTransportUrl {
    pub urn: Urn,
    pub(super) active_index: Option<usize>,
}

impl From<Urn> for LocalUrl {
impl From<Urn> for LocalTransportUrl {
    fn from(urn: Urn) -> Self {
        Self {
            urn,
@@ -30,7 +30,7 @@ impl From<Urn> for LocalUrl {
    }
}

impl Display for LocalUrl {
impl Display for LocalTransportUrl {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}://{}.git", super::URL_SCHEME, self.urn.encode_id(),)?;

@@ -70,7 +70,7 @@ pub enum ParseError {
    Peer(#[from] crypto::peer::conversion::Error),
}

impl FromStr for LocalUrl {
impl FromStr for LocalTransportUrl {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -97,8 +97,15 @@ impl FromStr for LocalUrl {
    }
}

impl From<LocalUrl> for Urn {
    fn from(url: LocalUrl) -> Self {
impl From<LocalTransportUrl> for Urn {
    fn from(url: LocalTransportUrl) -> Self {
        url.urn
    }
}

impl From<super::super::rad_url::RadRemoteUrl> for LocalTransportUrl {
    fn from(r: super::super::rad_url::RadRemoteUrl) -> Self {
        let urn: Urn = r.into();
        Self::from(urn)
    }
}
diff --git a/librad/src/git/rad_url.rs b/librad/src/git/rad_url.rs
new file mode 100644
index 00000000..c0af2c13
--- /dev/null
+++ b/librad/src/git/rad_url.rs
@@ -0,0 +1,89 @@
// 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 multihash::Multihash;
use thiserror::Error;

use super::Urn;

pub const URL_SCHEME: &str = "rad";

#[derive(Clone, Debug, PartialEq)]
pub struct RadRemoteUrl(Urn);

impl From<Urn> for RadRemoteUrl {
    fn from(urn: Urn) -> Self {
        Self(urn)
    }
}

impl Display for RadRemoteUrl {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}://{}.git", URL_SCHEME, self.0.encode_id(),)
    }
}

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ParseError {
    #[error("invalid scheme: {0}")]
    InvalidScheme(String),

    #[error("cannot-be-a-base URL")]
    CannotBeABase,

    #[error("malformed URL")]
    Url(#[from] url::ParseError),

    #[error(transparent)]
    Oid(#[from] ext::oid::FromMultihashError),

    #[error(transparent)]
    Multibase(#[from] multibase::Error),

    #[error(transparent)]
    Multihash(#[from] multihash::DecodeOwnedError),

    #[error(transparent)]
    Peer(#[from] crypto::peer::conversion::Error),
}

impl FromStr for RadRemoteUrl {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let url = url::Url::parse(s)?;
        if url.scheme() != URL_SCHEME {
            return Err(Self::Err::InvalidScheme(url.scheme().to_owned()));
        }
        if url.cannot_be_a_base() {
            return Err(Self::Err::CannotBeABase);
        }

        let host = url
            .host_str()
            .expect("we checked for cannot-be-a-base. qed")
            .trim_end_matches(".git");
        let bytes = multibase::decode(host).map(|(_base, bytes)| bytes)?;
        let mhash = Multihash::from_bytes(bytes)?;
        let oid = ext::Oid::try_from(mhash)?;
        let urn = Urn::new(oid);

        Ok(Self(urn))
    }
}

impl From<RadRemoteUrl> for Urn {
    fn from(url: RadRemoteUrl) -> Self {
        url.0
    }
}
diff --git a/librad/src/git/types/remote.rs b/librad/src/git/types/remote.rs
index 970853d5..67013e0a 100644
--- a/librad/src/git/types/remote.rs
+++ b/librad/src/git/types/remote.rs
@@ -14,7 +14,10 @@ use std_ext::result::ResultExt as _;
use thiserror::Error;

use super::{
    super::local::{self, transport::with_local_transport, url::LocalUrl},
    super::{
        local::{self, transport::with_local_transport, url::LocalTransportUrl},
        rad_url::RadRemoteUrl,
    },
    Fetchspec,
    Force,
    Pushspec,
@@ -203,7 +206,7 @@ impl<Url> Remote<Url> {
    }
}

/// What to push when calling `Remote::<LocalUrl>::push`.
/// What to push when calling `Remote::<RadRemoteUrl>::push`.
#[derive(Debug)]
pub enum LocalPushspec {
    /// Read the matching refs from the repo at runtime.
@@ -220,7 +223,7 @@ pub enum LocalPushspec {
    Configured,
}

/// What to fetch when calling `Remote::<LocalUrl>::fetch`.
/// What to fetch when calling `Remote::<RadRemoteUrl>::fetch`.
#[derive(Debug)]
pub enum LocalFetchspec {
    /// Use the provided [`Fetchspec`]s.
@@ -232,7 +235,7 @@ pub enum LocalFetchspec {
    Configured,
}

impl Remote<LocalUrl> {
impl Remote<RadRemoteUrl> {
    /// Get the remote repository's reference advertisement list.
    #[tracing::instrument(skip(self, repo, open_storage))]
    pub fn remote_heads<F>(
@@ -244,7 +247,7 @@ impl Remote<LocalUrl> {
        F: local::transport::CanOpenStorage + 'static,
    {
        let heads: Result<Vec<(RefLike, git2::Oid)>, local::transport::Error> =
            with_local_transport(open_storage, self.url.clone(), |url| {
            with_local_transport(open_storage, self.url.clone().into(), |url| {
                let mut git_remote = repo.remote_anonymous(&url.to_string())?;
                git_remote.connect(git2::Direction::Fetch)?;
                let heads = git_remote
@@ -333,9 +336,9 @@ impl Remote<LocalUrl> {
    where
        S: AsRef<str> + git2::IntoCString + Clone + std::fmt::Debug,
        F: local::transport::CanOpenStorage + 'static,
        G: FnOnce(LocalUrl) -> Result<git2::Remote<'a>, git2::Error>,
        G: FnOnce(LocalTransportUrl) -> Result<git2::Remote<'a>, git2::Error>,
    {
        with_local_transport(open_storage, self.url.clone(), |url| {
        with_local_transport(open_storage, self.url.clone().into(), |url| {
            let mut git_remote = open_remote(url)?;
            let mut updated_refs = Vec::new();
            let mut callbacks = git2::RemoteCallbacks::new();
@@ -410,9 +413,9 @@ impl Remote<LocalUrl> {
    where
        S: AsRef<str> + git2::IntoCString + Clone,
        F: local::transport::CanOpenStorage + 'static,
        G: FnOnce(LocalUrl) -> Result<git2::Remote<'a>, git2::Error>,
        G: FnOnce(LocalTransportUrl) -> Result<git2::Remote<'a>, git2::Error>,
    {
        with_local_transport(open_storage, self.url.clone(), |url| {
        with_local_transport(open_storage, self.url.clone().into(), |url| {
            let mut git_remote = open_remote(url)?;
            let mut updated_refs = Vec::new();
            let mut callbacks = git2::RemoteCallbacks::new();
diff --git a/librad/t/src/integration/scenario/working_copy.rs b/librad/t/src/integration/scenario/working_copy.rs
index 54bce982..8b62985f 100644
--- a/librad/t/src/integration/scenario/working_copy.rs
+++ b/librad/t/src/integration/scenario/working_copy.rs
@@ -14,7 +14,7 @@ use librad::{
    git::{
        identities::{self, Person, Project},
        include,
        local::url::LocalUrl,
        rad_url::RadRemoteUrl,
        tracking,
        types::{
            remote::{LocalFetchspec, LocalPushspec},
@@ -145,7 +145,7 @@ where
    G: RequestPullGuard,
{
    let repo = git2::Repository::init(repo_path)?;
    let url = LocalUrl::from(project.urn());
    let url = RadRemoteUrl::from(project.urn());

    let fetchspec = Refspec {
        src: Reference::heads(Namespace::from(project.urn()), peer.peer_id()),
@@ -203,7 +203,7 @@ where

    let inc = include::Include::from_tracked_persons(
        inc_path,
        LocalUrl::from(project.urn()),
        project.urn(),
        tracked_persons.into_iter().map(|(person, peer_id)| {
            (
                ext::RefLike::try_from(person.subject().name.as_str()).unwrap(),
diff --git a/librad/t/src/integration/smoke/gossip.rs b/librad/t/src/integration/smoke/gossip.rs
index e91c1cb9..1d03651c 100644
--- a/librad/t/src/integration/smoke/gossip.rs
+++ b/librad/t/src/integration/smoke/gossip.rs
@@ -14,7 +14,7 @@ use git_ref_format::{lit, name, Qualified};
use it_helpers::{fixed::TestProject, git::create_commit, testnet};
use librad::{
    git::{
        local::url::LocalUrl,
        rad_url::RadRemoteUrl,
        storage::ReadOnlyStorage as _,
        types::{remote, Fetchspec, Force, Reference, Remote},
        Urn,
@@ -77,7 +77,7 @@ fn fetches_on_gossip_notify() {
            let peer1 = (*peer1).clone();
            move || {
                let repo = git2::Repository::init(&project_repo_path).unwrap();
                let url = LocalUrl::from(project_urn);
                let url = RadRemoteUrl::from(project_urn);

                let mut remote = Remote::rad_remote::<_, Fetchspec>(url, None);

diff --git a/librad/t/src/tests/git/include.rs b/librad/t/src/tests/git/include.rs
index 76687990..b6cf97e7 100644
--- a/librad/t/src/tests/git/include.rs
+++ b/librad/t/src/tests/git/include.rs
@@ -6,7 +6,7 @@
use librad::{
    git::{
        include::{Error, Include},
        local::url::LocalUrl,
        rad_url::RadRemoteUrl,
        Urn,
    },
    git_ext as ext,
@@ -45,12 +45,13 @@ lazy_static! {
#[test]
fn can_create_and_update() -> Result<(), Error> {
    let tmp_dir = tempfile::tempdir()?;
    let url = LocalUrl::from(Urn::new(git2::Oid::zero().into()));
    let urn = Urn::new(git2::Oid::zero().into());
    let url = RadRemoteUrl::from(urn.clone());

    // Start with an empty config to catch corner-cases where git2::Config does not
    // create a file yet.
    let config = {
        let include = Include::new(tmp_dir.path().to_path_buf(), url.clone());
        let include = Include::new(tmp_dir.path().to_path_buf(), urn.clone());
        let path = include.file_path();
        let config = git2::Config::open(&path)?;
        include.save()?;
@@ -60,7 +61,7 @@ fn can_create_and_update() -> Result<(), Error> {

    let remote_lyla = format!("{}@{}", *LYLA_HANDLE, *LYLA_PEER_ID);
    {
        let mut include = Include::new(tmp_dir.path().to_path_buf(), url.clone());
        let mut include = Include::new(tmp_dir.path().to_path_buf(), urn.clone());
        include.add_remote(url.clone(), *LYLA_PEER_ID, (*LYLA_HANDLE).clone());
        include.save()?;
    };
@@ -80,7 +81,7 @@ fn can_create_and_update() -> Result<(), Error> {

    let remote_rover = format!("{}@{}", *ROVER_HANDLE, *ROVER_PEER_ID);
    {
        let mut include = Include::new(tmp_dir.path().to_path_buf(), url.clone());
        let mut include = Include::new(tmp_dir.path().to_path_buf(), urn.clone());
        include.add_remote(url.clone(), *LYLA_PEER_ID, (*LYLA_HANDLE).clone());
        include.add_remote(url.clone(), *ROVER_PEER_ID, (*ROVER_HANDLE).clone());
        include.save()?;
@@ -116,7 +117,7 @@ fn can_create_and_update() -> Result<(), Error> {
    let remote_lingling = format!("{}@{}", *LINGLING_HANDLE, *LINGLING_PEER_ID);

    {
        let mut include = Include::new(tmp_dir.path().to_path_buf(), url.clone());
        let mut include = Include::new(tmp_dir.path().to_path_buf(), urn);
        include.add_remote(url, *LINGLING_PEER_ID, (*LINGLING_HANDLE).clone());
        include.save()?;
    };
diff --git a/librad/t/src/tests/git/local/url.rs b/librad/t/src/tests/git/local/url.rs
index e88ff97c..c1affbf1 100644
--- a/librad/t/src/tests/git/local/url.rs
+++ b/librad/t/src/tests/git/local/url.rs
@@ -3,11 +3,11 @@
// This file is part of radicle-link, distributed under the GPLv3 with Radicle
// Linking Exception. For full terms see the included LICENSE file.

use librad::git::{local::url::LocalUrl, Urn};
use librad::git::{local::url::LocalTransportUrl, Urn};
use test_helpers::roundtrip;

#[test]
fn trip() {
    let url = LocalUrl::from(Urn::new(git2::Oid::zero().into()));
    let url = LocalTransportUrl::from(Urn::new(git2::Oid::zero().into()));
    roundtrip::str(url)
}
diff --git a/librad/t/src/tests/git/types/remote.rs b/librad/t/src/tests/git/types/remote.rs
index 50bfeda3..214b6aa4 100644
--- a/librad/t/src/tests/git/types/remote.rs
+++ b/librad/t/src/tests/git/types/remote.rs
@@ -7,7 +7,7 @@ use std::{convert::TryFrom, io};

use librad::{
    git::{
        local::url::LocalUrl,
        rad_url::RadRemoteUrl,
        types::{remote::Remote, AsNamespace, Force, Namespace, Reference, Refspec},
        Urn,
    },
@@ -47,12 +47,12 @@ fn can_create_remote() {
        };

        {
            let url = LocalUrl::from(URN.clone());
            let url = RadRemoteUrl::from(URN.clone());
            let mut remote = Remote::rad_remote(url, fetch).with_pushspecs(Some(push));
            remote.save(&repo).expect("failed to persist the remote");
        }

        let remote = Remote::<LocalUrl>::find(&repo, reflike!("rad"))
        let remote = Remote::<RadRemoteUrl>::find(&repo, reflike!("rad"))
            .unwrap()
            .expect("should exist");

@@ -87,7 +87,7 @@ fn can_create_remote() {

#[test]
fn check_remote_fetch_spec() -> Result<(), git2::Error> {
    let url = LocalUrl::from(URN.clone());
    let url = RadRemoteUrl::from(URN.clone());
    let name = ext::RefLike::try_from(format!("lyla@{}", *PEER_ID)).unwrap();

    let heads = Reference::heads(None, *PEER_ID);
diff --git a/test/it-helpers/src/working_copy.rs b/test/it-helpers/src/working_copy.rs
index b27a149a..82fa1c09 100644
--- a/test/it-helpers/src/working_copy.rs
+++ b/test/it-helpers/src/working_copy.rs
@@ -2,7 +2,7 @@ use git_ref_format::{lit, name, refspec, Qualified, RefStr, RefString};

use librad::{
    git::{
        local::url::LocalUrl,
        rad_url::RadRemoteUrl,
        types::{
            remote::{LocalFetchspec, LocalPushspec},
            Fetchspec,
@@ -126,7 +126,7 @@ where
    /// remote called "rad".
    pub fn fetch(&mut self, from: WorkingRemote) -> Result<(), anyhow::Error> {
        let fetchspec = from.fetchspec();
        let url = LocalUrl::from(self.project.project.urn());
        let url = RadRemoteUrl::from(self.project.project.urn());
        let mut remote = Remote::rad_remote(url, fetchspec);
        let _ = remote.fetch(self.peer.clone(), &self.repo, LocalFetchspec::Configured)?;
        Ok(())
@@ -134,7 +134,7 @@ where

    /// Push changes from `refs/heads/*` to the local peer
    pub fn push(&mut self) -> Result<(), anyhow::Error> {
        let url = LocalUrl::from(self.project.project.urn());
        let url = RadRemoteUrl::from(self.project.project.urn());
        let name = RefString::try_from("rad").unwrap();
        let fetchspec = Refspec {
            src: RefString::from_iter([name::REFS, name::HEADS]).with_pattern(refspec::STAR),
-- 
2.36.1