Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add `rad-clone` command
Alexis Sellier committed 3 years ago
commit b0400984478df19c282496df8d36bf4cbedc7abd
parent ca1b1f445354d93f81da9d89ce3f83db12855c24
3 files changed +132 -3
modified radicle-cli/src/commands.rs
@@ -2,6 +2,8 @@
pub mod rad_auth;
#[path = "commands/checkout.rs"]
pub mod rad_checkout;
+
#[path = "commands/clone.rs"]
+
pub mod rad_clone;
#[path = "commands/help.rs"]
pub mod rad_help;
#[path = "commands/init.rs"]
modified radicle-cli/src/commands/checkout.rs
@@ -115,8 +115,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
        .filter(|id| id != profile.id())
        .collect::<Vec<_>>();

-
    // Setup tracking for project delegates.
-
    setup_tracking(
+
    // Setup remote tracking branches for project delegates.
+
    setup_remotes(
        project::SetupRemote {
            project: id,
            default_branch: payload.default_branch,
@@ -131,7 +131,7 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
}

/// Setup a remote and tracking branch for each given remote.
-
pub fn setup_tracking(setup: project::SetupRemote, remotes: &[NodeId]) -> anyhow::Result<()> {
+
pub fn setup_remotes(setup: project::SetupRemote, remotes: &[NodeId]) -> anyhow::Result<()> {
    for remote_id in remotes {
        if let Some((remote, branch)) = setup.run(*remote_id)? {
            let remote = remote.name().unwrap(); // Only valid UTF-8 is used.
added radicle-cli/src/commands/clone.rs
@@ -0,0 +1,127 @@
+
#![allow(clippy::or_fun_call)]
+
use std::ffi::OsString;
+
use std::path::Path;
+
use std::str::FromStr;
+

+
use anyhow::anyhow;
+
use anyhow::Context as _;
+

+
use radicle::node::Handle;
+
use radicle::prelude::*;
+
use radicle::rad;
+
use radicle::storage::WriteStorage;
+

+
use crate::commands::rad_checkout::setup_remotes;
+
use crate::project;
+
use crate::terminal as term;
+
use crate::terminal::args::{Args, Error, Help};
+
use crate::terminal::Interactive;
+

+
pub const HELP: Help = Help {
+
    name: "clone",
+
    description: "Clone radicle projects",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad clone <id> [<option>...]
+

+
Options
+

+
    --no-confirm    Don't ask for confirmation during clone
+
    --help          Print help
+

+
"#,
+
};
+

+
#[derive(Debug)]
+
pub struct Options {
+
    id: Id,
+
    interactive: Interactive,
+
}
+

+
impl Args for Options {
+
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+
        use lexopt::prelude::*;
+

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut id: Option<Id> = None;
+
        let mut interactive = Interactive::Yes;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("no-confirm") => {
+
                    interactive = Interactive::No;
+
                }
+
                Long("help") => {
+
                    return Err(Error::Help.into());
+
                }
+
                Value(val) if id.is_none() => {
+
                    let val = val.to_string_lossy();
+
                    let val = val.strip_prefix("rad://").unwrap_or(&val);
+
                    let val = Id::from_str(val)?;
+

+
                    id = Some(val);
+
                }
+
                _ => return Err(anyhow!(arg.unexpected())),
+
            }
+
        }
+
        let id = id.ok_or_else(|| {
+
            anyhow!("to clone, a radicle id must be provided; see `rad clone --help`")
+
        })?;
+

+
        Ok((Options { id, interactive }, vec![]))
+
    }
+
}
+

+
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
    clone(options.id, options.interactive, ctx)
+
}
+

+
pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyhow::Result<()> {
+
    let profile = ctx.profile()?;
+
    let node = radicle::node::connect(profile.node())?;
+
    let signer = term::signer(&profile)?;
+

+
    // Track & fetch project.
+
    node.track(&id).context("track")?;
+
    node.fetch(&id).context("fetch")?;
+

+
    // Create a local fork of the project, under our own id.
+
    rad::fork(id, &signer, &profile.storage).context("fork")?;
+

+
    let doc = profile
+
        .storage
+
        .repository(id)?
+
        .project_of(profile.id())
+
        .map_err(|_e| anyhow!("couldn't load project {} from local state", id))?;
+

+
    let path = Path::new(&doc.name);
+
    let repo = rad::checkout(id, profile.id(), path, &profile.storage)?;
+
    let delegates = doc
+
        .delegates
+
        .iter()
+
        .map(|d| *d.id)
+
        .filter(|id| id != profile.id())
+
        .collect::<Vec<_>>();
+
    let default_branch = doc.payload.default_branch.clone();
+

+
    // Setup tracking for project delegates.
+
    setup_remotes(
+
        project::SetupRemote {
+
            project: id,
+
            default_branch,
+
            repo: &repo,
+
            fetch: true,
+
            tracking: true,
+
        },
+
        &delegates,
+
    )?;
+

+
    term::headline(&format!(
+
        "🌱 Project successfully cloned under {}",
+
        term::format::highlight(Path::new(".").join(path).display())
+
    ));
+

+
    Ok(())
+
}