~radicle-link/dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
6 2

[PATCH v1 0/1] RadRemoteUrl

Details
Message ID
<20220623133531.951926-1-alex@memoryandthought.me>
DKIM signature
missing
Download raw message
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

[PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<20220623133531.951926-2-alex@memoryandthought.me>
In-Reply-To
<20220623133531.951926-1-alex@memoryandthought.me> (view parent)
DKIM signature
missing
Download raw message
Patch: +253 -131
`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

Re: [PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<CKY7EVYZ4XYJ.2RFIEWXDG6D96@haptop>
In-Reply-To
<20220623133531.951926-2-alex@memoryandthought.me> (view parent)
DKIM signature
pass
Download raw message
On Thu Jun 23, 2022 at 2:35 PM IST, Alex Good wrote:
> `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.

I *think* we can keep compatability here. If we kept `LocalUrl`
(removing the `active_index`), but introduced an `LocalTransportUrl`
that was used for the internal transport. They could live in the same
module, I think, and have documentation on why there are two different
ones and when to use them :)

I feel like we can also avoid the duplication and unify the
implementations with some `trait HasScheme { const SCHEME: &'static
str }` trickery. But it's also not the worst having it duplicated.

>
> 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

Re: [PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<CAH_DpYQtpLe1H2yobF9cG0Lu1beOBnSPp8o_7RENNDoFoa8WZw@mail.gmail.com>
In-Reply-To
<CKY7EVYZ4XYJ.2RFIEWXDG6D96@haptop> (view parent)
DKIM signature
missing
Download raw message
On 24/06/22 08:59am, Fintan Halpenny wrote:
> On Thu Jun 23, 2022 at 2:35 PM IST, Alex Good wrote:
> > `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.
>
> I *think* we can keep compatability here. If we kept `LocalUrl`
> (removing the `active_index`), but introduced an `LocalTransportUrl`
> that was used for the internal transport. They could live in the same
> module, I think, and have documentation on why there are two different
> ones and when to use them :)

I actually think `RadRemoteUrl` is a better name than `LocalUrl` for
this usage. It communicates that it's really just the URL for a
particular kind of remote in the repository. Likewise
`LocalTransportUrl` is clearer that the whole active inde thing is a
kind of hack.

>
> I feel like we can also avoid the duplication and unify the
> implementations with some `trait HasScheme { const SCHEME: &'static
> str }` trickery. But it's also not the worst having it duplicated.
>

I thought about this but wasn't sure it was worth the work. Most of the
duplciation is actually the parse error names. I felt like extracting
that out to some common code would actually make it harder to read this
stuff.


> >
> > 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
>

Re: [PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<CKYBVX5HW4XP.2WHUNN4MMUJ05@haptop>
In-Reply-To
<CAH_DpYQtpLe1H2yobF9cG0Lu1beOBnSPp8o_7RENNDoFoa8WZw@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
On Fri Jun 24, 2022 at 12:13 PM IST, Alex Good wrote:
> On 24/06/22 08:59am, Fintan Halpenny wrote:
> > On Thu Jun 23, 2022 at 2:35 PM IST, Alex Good wrote:
> > > `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.
> >
> > I *think* we can keep compatability here. If we kept `LocalUrl`
> > (removing the `active_index`), but introduced an `LocalTransportUrl`
> > that was used for the internal transport. They could live in the same
> > module, I think, and have documentation on why there are two different
> > ones and when to use them :)
>
> I actually think `RadRemoteUrl` is a better name than `LocalUrl` for
> this usage. It communicates that it's really just the URL for a
> particular kind of remote in the repository. Likewise
> `LocalTransportUrl` is clearer that the whole active inde thing is a
> kind of hack.

It's not the naming I'm questioning here, it's the compatability :)
Renaming these forces renames for us *and* any dependents that use
`LocalUrl` (but maybe there is none?)

>
> >
> > I feel like we can also avoid the duplication and unify the
> > implementations with some `trait HasScheme { const SCHEME: &'static
> > str }` trickery. But it's also not the worst having it duplicated.
> >
>
> I thought about this but wasn't sure it was worth the work. Most of the
> duplciation is actually the parse error names. I felt like extracting
> that out to some common code would actually make it harder to read this
> stuff.

Well that and the parsing itself. The differences being the scheme and
the index. LocalTransportUrl is essentially a RadRemoteUrl with an
index and a different scheme.

Just spitballing, but can we have const generics like:

---
pub struct Url<const SCHEME: &'static str, R> {
  urn: Urn<R>
}

pub struct RadRemoteUrl<R> {
    url: Url<"rad", R>,
}

pub struct LocalTransportUrl<R> {
    url: Url<"rad-internal", R>,
    index: Arc<AtomicUsize>,
}
---

Re: [PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<CAH_DpYR5Uk_Q1E+17Aqd+9ZuWvLdAzAM0sAhCuxOasgZfBp5XA@mail.gmail.com>
In-Reply-To
<CKYBVX5HW4XP.2WHUNN4MMUJ05@haptop> (view parent)
DKIM signature
missing
Download raw message
On 24/06/22 12:29pm, Fintan Halpenny wrote:
> It's not the naming I'm questioning here, it's the compatability :)
> Renaming these forces renames for us *and* any dependents that use
> `LocalUrl` (but maybe there is none?)

Ah I see. Yeah I'll survey the known dependents and see if anyone will
be adversely affected. Part of the problem of exporting every name in
our codebase is we can't really rename things without a theoretical
compatibility break.

>
> >
> > >
> > > I feel like we can also avoid the duplication and unify the
> > > implementations with some `trait HasScheme { const SCHEME: &'static
> > > str }` trickery. But it's also not the worst having it duplicated.
> > >
> >
> > I thought about this but wasn't sure it was worth the work. Most of the
> > duplciation is actually the parse error names. I felt like extracting
> > that out to some common code would actually make it harder to read this
> > stuff.
>
> Well that and the parsing itself. The differences being the scheme and
> the index. LocalTransportUrl is essentially a RadRemoteUrl with an
> index and a different scheme.
>
> Just spitballing, but can we have const generics like:
>
> ---
> pub struct Url<const SCHEME: &'static str, R> {
>   urn: Urn<R>
> }
>
> pub struct RadRemoteUrl<R> {
>     url: Url<"rad", R>,
> }
>
> pub struct LocalTransportUrl<R> {
>     url: Url<"rad-internal", R>,
>     index: Arc<AtomicUsize>,
> }
> ---

Interesting, I'll have a play.

Re: [PATCH v1 1/1] Introduce RadRemoteUrl

Details
Message ID
<CKYH4KL27TUF.3E82HPV4GMVH@haptop>
In-Reply-To
<CAH_DpYR5Uk_Q1E+17Aqd+9ZuWvLdAzAM0sAhCuxOasgZfBp5XA@mail.gmail.com> (view parent)
DKIM signature
pass
Download raw message
On Fri Jun 24, 2022 at 4:33 PM IST, Alex Good wrote:
> On 24/06/22 12:29pm, Fintan Halpenny wrote:
> > It's not the naming I'm questioning here, it's the compatability :)
> > Renaming these forces renames for us *and* any dependents that use
> > `LocalUrl` (but maybe there is none?)
>
> Ah I see. Yeah I'll survey the known dependents and see if anyone will
> be adversely affected. Part of the problem of exporting every name in
> our codebase is we can't really rename things without a theoretical
> compatibility break.

Sweet, appreciate it!

And ya, that's fair. I just wanted to point out the potential of not
needing to break naming compatability.

I also think it would be fine to have the two types live in the same
module, possibly have inline `mod internal` and `mod transport` with
`pub use` re-exports.

> > > > I feel like we can also avoid the duplication and unify the
> > > > implementations with some `trait HasScheme { const SCHEME: &'static
> > > > str }` trickery. But it's also not the worst having it duplicated.
> > > >
> > >
> > > I thought about this but wasn't sure it was worth the work. Most of the
> > > duplciation is actually the parse error names. I felt like extracting
> > > that out to some common code would actually make it harder to read this
> > > stuff.
> >
> > Well that and the parsing itself. The differences being the scheme and
> > the index. LocalTransportUrl is essentially a RadRemoteUrl with an
> > index and a different scheme.
> >
> > Just spitballing, but can we have const generics like:
> >
> > ---
> > pub struct Url<const SCHEME: &'static str, R> {
> >   urn: Urn<R>
> > }
> >
> > pub struct RadRemoteUrl<R> {
> >     url: Url<"rad", R>,
> > }
> >
> > pub struct LocalTransportUrl<R> {
> >     url: Url<"rad-internal", R>,
> >     index: Arc<AtomicUsize>,
> > }
> > ---
>
> Interesting, I'll have a play.

+1
Reply to thread Export thread (mbox)