Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
cli: Add way to init from existing repositories
Merged did:key:z6MksFqX...wzpT opened 1 year ago

This adds an option to rad init to initialize a working copy with an existing radicle repository in storage:

rad init --existing <rid>

This sets up the remote(s) and configures the repo for you.

4 files changed +144 -12 74336f96 ee1c8678
added radicle-cli/examples/rad-init-existing.md
@@ -0,0 +1,40 @@
+
Let's clone a regular repository via plain Git:
+
```
+
$ git clone $URL heartwood
+
$ cd heartwood
+
$ git rev-parse HEAD
+
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
+
```
+

+
We can see it's not a Radicle working copy:
+
``` (fail)
+
$ rad .
+
✗ Error: Current directory is not a Radicle repository
+
```
+

+
Let's pick an existing repository:
+
```
+
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
```
+

+
And initialize this working copy as that existing repository:
+
```
+
$ rad init --existing rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
✓ Initialized existing repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji in [..]/heartwood/..
+
```
+

+
We can confirm that the working copy is initialized:
+
```
+
$ rad .
+
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
$ git remote show rad
+
* remote rad
+
  Fetch URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
  Push  URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
  HEAD branch: (unknown)
+
  Remote branch:
+
    master new (next fetch will store in remotes/rad)
+
  Local ref configured for 'git push':
+
    master pushes to master (up to date)
+
```
modified radicle-cli/src/commands/init.rs
@@ -18,6 +18,7 @@ use radicle::node::events::UploadPack;
use radicle::node::policy::Scope;
use radicle::node::{Event, Handle, NodeId, DEFAULT_SUBSCRIBE_TIMEOUT};
use radicle::prelude::Doc;
+
use radicle::storage::ReadStorage as _;
use radicle::{profile, Node};

use crate::commands;
@@ -43,6 +44,7 @@ Options
        --scope <scope>            Repository follow scope: `followed` or `all` (default: all)
        --private                  Set repository visibility to *private*
        --public                   Set repository visibility to *public*
+
        --existing <rid>           Setup repository as an existing Radicle repository
    -u, --set-upstream             Setup the upstream of the default branch
        --setup-signing            Setup the radicle key as a signing key for this repository
        --no-confirm               Don't ask for confirmation during setup
@@ -60,6 +62,7 @@ pub struct Options {
    pub branch: Option<String>,
    pub interactive: Interactive,
    pub visibility: Option<Visibility>,
+
    pub existing: Option<RepoId>,
    pub setup_signing: bool,
    pub scope: Scope,
    pub set_upstream: bool,
@@ -81,6 +84,7 @@ impl Args for Options {
        let mut set_upstream = false;
        let mut setup_signing = false;
        let mut scope = Scope::All;
+
        let mut existing = None;
        let mut seed = true;
        let mut verbose = false;
        let mut visibility = None;
@@ -150,6 +154,12 @@ impl Args for Options {
                Long("public") => {
                    visibility = Some(Visibility::Public);
                }
+
                Long("existing") if existing.is_none() => {
+
                    let val = parser.value()?;
+
                    let rid = term::args::rid(&val)?;
+

+
                    existing = Some(rid);
+
                }
                Long("verbose") | Short('v') => {
                    verbose = true;
                }
@@ -170,6 +180,7 @@ impl Args for Options {
                description,
                branch,
                scope,
+
                existing,
                interactive,
                set_upstream,
                setup_signing,
@@ -184,29 +195,38 @@ impl Args for Options {

pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let profile = ctx.profile()?;
-

-
    init(options, &profile)
-
}
-

-
pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()> {
-
    let cwd = std::env::current_dir()?;
-
    let path = options.path.unwrap_or_else(|| cwd.clone());
-
    let path = path.as_path().canonicalize()?;
-
    let interactive = options.interactive;
-
    let repo = match git::Repository::open(&path) {
+
    let cwd = env::current_dir()?;
+
    let path = options.path.as_deref().unwrap_or(cwd.as_path());
+
    let repo = match git::Repository::open(path) {
        Ok(r) => r,
        Err(e) if radicle::git::ext::is_not_found_err(&e) => {
            anyhow::bail!("a Git repository was not found at the given path")
        }
        Err(e) => return Err(e.into()),
    };
-

    if let Ok((remote, _)) = git::rad_remote(&repo) {
        if let Some(remote) = remote.url() {
            bail!("repository is already initialized with remote {remote}");
        }
    }

+
    if let Some(rid) = options.existing {
+
        init_existing(repo, rid, options, &profile)
+
    } else {
+
        init(repo, options, &profile)
+
    }
+
}
+

+
pub fn init(
+
    repo: git::Repository,
+
    options: Options,
+
    profile: &profile::Profile,
+
) -> anyhow::Result<()> {
+
    let path = repo
+
        .workdir()
+
        .unwrap_or_else(|| repo.path())
+
        .canonicalize()?;
+
    let interactive = options.interactive;
    let head: String = repo
        .head()
        .ok()
@@ -319,7 +339,7 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
                term::format::dim("(RID)"),
                term::format::highlight(rid.urn())
            );
-
            let directory = if path == cwd {
+
            let directory = if path == env::current_dir()? {
                "this directory".to_owned()
            } else {
                term::format::tertiary(path.display()).to_string()
@@ -350,6 +370,48 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
    Ok(())
}

+
pub fn init_existing(
+
    working: git::Repository,
+
    rid: RepoId,
+
    options: Options,
+
    profile: &profile::Profile,
+
) -> anyhow::Result<()> {
+
    let stored = profile.storage.repository(rid)?;
+
    let project = stored.project()?;
+
    let url = radicle::git::Url::from(rid);
+

+
    radicle::git::configure_repository(&working)?;
+
    radicle::git::configure_remote(
+
        &working,
+
        &radicle::rad::REMOTE_NAME,
+
        &url,
+
        &url.clone().with_namespace(profile.public_key),
+
    )?;
+

+
    if options.set_upstream {
+
        // Setup eg. `master` -> `rad/master`
+
        radicle::git::set_upstream(
+
            &working,
+
            &*radicle::rad::REMOTE_NAME,
+
            project.default_branch(),
+
            radicle::git::refs::workdir::branch(project.default_branch()),
+
        )?;
+
    }
+

+
    term::success!(
+
        "Initialized existing repository {} in {}..",
+
        term::format::tertiary(rid),
+
        term::format::dim(
+
            working
+
                .workdir()
+
                .unwrap_or_else(|| working.path())
+
                .display()
+
        ),
+
    );
+

+
    Ok(())
+
}
+

#[derive(Debug)]
enum SyncResult<T> {
    NodeStopped,
modified radicle-cli/tests/commands.rs
@@ -205,6 +205,28 @@ fn rad_init() {
}

#[test]
+
fn rad_init_existing() {
+
    let mut environment = Environment::new();
+
    let mut profile = environment.node(config::node("alice"));
+
    let working = tempfile::tempdir().unwrap();
+
    let rid = profile.project("heartwood", "Radicle Heartwood Protocol & Stack");
+

+
    test(
+
        "examples/rad-init-existing.md",
+
        working.path(),
+
        Some(&profile.home),
+
        [(
+
            "URL",
+
            git::url::File::new(profile.storage.path())
+
                .rid(rid)
+
                .to_string()
+
                .as_str(),
+
        )],
+
    )
+
    .unwrap();
+
}
+

+
#[test]
fn rad_init_no_seed() {
    let mut environment = Environment::new();
    let alice = environment.node(Config::test(Alias::new("alice")));
modified radicle/src/git.rs
@@ -697,6 +697,8 @@ pub mod process {
pub mod url {
    use std::path::PathBuf;

+
    use crate::prelude::RepoId;
+

    /// A Git URL using the `file://` scheme.
    pub struct File {
        pub path: PathBuf,
@@ -707,6 +709,12 @@ pub mod url {
        pub fn new(path: impl Into<PathBuf>) -> Self {
            Self { path: path.into() }
        }
+

+
        /// Return a URL with the given RID set.
+
        pub fn rid(mut self, rid: RepoId) -> Self {
+
            self.path.push(rid.canonical());
+
            self
+
        }
    }

    impl ToString for File {