Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: allow directory in rad clone
Fintan Halpenny committed 2 years ago
commit 070e912b3ed9b5bef35ff6c9cdb723c1daa2fb04
parent c761d03a5aafebc15c0f821f353c9df06b509281
3 files changed +88 -5
added radicle-cli/examples/rad-clone-directory.md
@@ -0,0 +1,27 @@
+
We can specify where a repository gets cloned into on our filesystem
+
by specifying the directory in the `rad clone` command:
+

+
```
+
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
+
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
+
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
+
✓ Creating checkout in ./Developer/Radicle..
+
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
+
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
+
✓ Repository successfully cloned under [..]/Developer/Radicle/
+
╭────────────────────────────────────╮
+
│ heartwood                          │
+
│ Radicle Heartwood Protocol & Stack │
+
│ 0 issues · 0 patches               │
+
╰────────────────────────────────────╯
+
Run `cd [..]/Developer/Radicle` to go to the project directory.
+
```
+

+
Note that attempting to clone into a directory that already exists,
+
and is not empty, will fail:
+

+
``` (fail)
+
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
+
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
+
✗ Error: the directory path "Developer/Radicle" already exists
+
```
modified radicle-cli/src/commands/clone.rs
@@ -1,6 +1,6 @@
#![allow(clippy::or_fun_call)]
use std::ffi::OsString;
-
use std::path::Path;
+
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time;

@@ -34,7 +34,7 @@ pub const HELP: Help = Help {
    usage: r#"
Usage

-
    rad clone <rid> [--scope <scope>] [<option>...]
+
    rad clone <rid> [<directory>] [--scope <scope>] [<option>...]

Options

@@ -46,7 +46,11 @@ Options

#[derive(Debug)]
pub struct Options {
+
    /// The RID of the repository.
    id: Id,
+
    /// The target directory for the repository to be cloned into.
+
    directory: Option<PathBuf>,
+
    /// The seeding scope of the repository.
    scope: Scope,
}

@@ -57,6 +61,7 @@ impl Args for Options {
        let mut parser = lexopt::Parser::from_args(args);
        let mut id: Option<Id> = None;
        let mut scope = Scope::All;
+
        let mut directory = None;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -79,13 +84,24 @@ impl Args for Options {

                    id = Some(val);
                }
+
                // Parse <directory> once <rid> has been parsed
+
                Value(val) if id.is_some() && directory.is_none() => {
+
                    directory = Some(Path::new(&val).to_path_buf());
+
                }
                _ => return Err(anyhow!(arg.unexpected())),
            }
        }
        let id =
            id.ok_or_else(|| anyhow!("to clone, an RID must be provided; see `rad clone --help`"))?;

-
        Ok((Options { id, scope }, vec![]))
+
        Ok((
+
            Options {
+
                id,
+
                directory,
+
                scope,
+
            },
+
            vec![],
+
        ))
    }
}

@@ -102,6 +118,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {

    let (working, repo, doc, proj) = clone(
        options.id,
+
        options.directory.clone(),
        options.scope,
        &mut node,
        &signer,
@@ -150,9 +167,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    ])]);
    info.print();

+
    let location = options
+
        .directory
+
        .map_or(proj.name().to_string(), |loc| loc.display().to_string());
    term::info!(
        "Run {} to go to the project directory.",
-
        term::format::command(format!("cd ./{}", proj.name())),
+
        term::format::command(format!("cd ./{location}")),
    );

    Ok(())
@@ -160,6 +180,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {

#[derive(Error, Debug)]
pub enum CloneError {
+
    #[error("the directory path {path:?} already exists")]
+
    Exists { path: PathBuf },
    #[error("node: {0}")]
    Node(#[from] node::Error),
    #[error("storage: {0}")]
@@ -180,6 +202,7 @@ pub enum CloneError {

pub fn clone<G: Signer>(
    id: Id,
+
    directory: Option<PathBuf>,
    scope: Scope,
    node: &mut Node,
    signer: &G,
@@ -221,7 +244,14 @@ pub fn clone<G: Signer>(

    let doc = repository.identity_doc()?;
    let proj = doc.project()?;
-
    let path = Path::new(proj.name());
+
    let path = directory.unwrap_or(Path::new(proj.name()).to_path_buf());
+

+
    // N.b. fail if the path exists and is not empty
+
    if path.exists() {
+
        if path.read_dir().map_or(true, |mut dir| dir.next().is_some()) {
+
            return Err(CloneError::Exists { path });
+
        }
+
    }

    if results.success().next().is_none() {
        if results.failed().next().is_some() {
modified radicle-cli/tests/commands.rs
@@ -749,6 +749,32 @@ fn rad_clone() {
}

#[test]
+
fn rad_clone_directory() {
+
    let mut environment = Environment::new();
+
    let mut alice = environment.node(Config::test(Alias::new("alice")));
+
    let bob = environment.node(Config::test(Alias::new("bob")));
+
    let working = environment.tmp().join("working");
+

+
    // Setup a test project.
+
    let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
+

+
    let mut alice = alice.spawn();
+
    let mut bob = bob.spawn();
+
    // Prevent Alice from fetching Bob's fork, as we're not testing that and it may cause errors.
+
    alice.handle.seed(acme, Scope::Followed).unwrap();
+

+
    bob.connect(&alice).converge([&alice]);
+

+
    test(
+
        "examples/rad-clone-directory.md",
+
        working,
+
        Some(&bob.home),
+
        [],
+
    )
+
    .unwrap();
+
}
+

+
#[test]
fn rad_clone_all() {
    let mut environment = Environment::new();
    let mut alice = environment.node(Config::test(Alias::new("alice")));