Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Implement `rad-checkout` command
Alexis Sellier committed 3 years ago
commit 8fe949b2a030b3e57018ccb243c775a69540d691
parent 32cd94dd23ebe05288a151852d4e0e0cd752bcb2
5 files changed +184 -0
modified radicle-cli/src/commands.rs
@@ -1 +1,2 @@
+
pub mod checkout;
pub mod init;
added radicle-cli/src/commands/checkout.rs
@@ -0,0 +1,136 @@
+
use std::ffi::OsString;
+
use std::path::PathBuf;
+

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

+
use radicle::prelude::*;
+
use radicle::storage::WriteStorage;
+

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

+
pub const HELP: Help = Help {
+
    name: "checkout",
+
    description: "Checkout a radicle project working copy",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

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

+
Options
+

+
    --no-confirm    Don't ask for confirmation during checkout
+
    --help          Print help
+
"#,
+
};
+

+
pub struct Options {
+
    pub id: Id,
+
}
+

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

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut id = None;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("no-confirm") => {
+
                    // Ignored for now.
+
                }
+
                Long("help") => return Err(Error::Help.into()),
+
                Value(val) if id.is_none() => {
+
                    let val = val.to_string_lossy();
+
                    let val = Id::from_str(&val).context(format!("invalid id '{}'", val))?;
+

+
                    id = Some(val);
+
                }
+
                _ => return Err(anyhow::anyhow!(arg.unexpected())),
+
            }
+
        }
+

+
        Ok((
+
            Options {
+
                id: id.ok_or_else(|| anyhow!("a project id to checkout must be provided"))?,
+
            },
+
            vec![],
+
        ))
+
    }
+
}
+

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

+
    term::headline(&format!(
+
        "🌱 Project checkout successful under ./{}",
+
        term::format::highlight(path.file_name().unwrap_or_default().to_string_lossy())
+
    ));
+

+
    Ok(())
+
}
+

+
pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
+
    let storage = &profile.storage;
+
    let repo = storage.repository(options.id)?;
+
    let project = repo
+
        .project_of(profile.id())
+
        .context("project could not be found in local storage")?;
+
    let path = PathBuf::from(project.name.clone());
+

+
    if path.exists() {
+
        anyhow::bail!("the local path {:?} already exists", path.as_path());
+
    }
+

+
    term::headline(&format!(
+
        "Initializing local checkout for 🌱 {} ({})",
+
        term::format::highlight(options.id),
+
        project.name,
+
    ));
+

+
    let spinner = term::spinner("Performing checkout...");
+
    let working = match radicle::rad::checkout(options.id, profile.id(), path.clone(), &storage) {
+
        Ok(working) => working,
+
        Err(err) => {
+
            spinner.failed();
+
            term::blank();
+

+
            return Err(err.into());
+
        }
+
    };
+
    spinner.finish();
+

+
    // Setup a remote and tracking branch for all project delegates except yourself.
+
    let setup = project::SetupRemote {
+
        project: options.id,
+
        default_branch: project.default_branch.clone(),
+
        repo: &working,
+
        fetch: true,
+
        tracking: true,
+
    };
+
    for remote_id in repo.remote_ids()? {
+
        let remote_id = remote_id?;
+
        if &remote_id == profile.id() {
+
            continue;
+
        }
+

+
        if let Some((remote, branch)) = setup.run(remote_id)? {
+
            let remote = remote.name().unwrap(); // Only valid UTF-8 is used.
+
                                                 //
+
            term::success!("Remote {} set", term::format::highlight(remote));
+
            term::success!(
+
                "Remote-tracking branch {} created for {}",
+
                term::format::highlight(&branch),
+
                term::format::tertiary(term::format::node(&remote_id))
+
            );
+
        }
+
    }
+

+
    Ok(path)
+
}
modified radicle-cli/src/lib.rs
@@ -1,4 +1,5 @@
#![allow(clippy::collapsible_if)]
pub mod commands;
pub mod git;
+
pub mod project;
pub mod terminal;
added radicle-cli/src/project.rs
@@ -0,0 +1,40 @@
+
use radicle::git::raw::Remote;
+
use radicle::prelude::*;
+
use radicle::rad;
+

+
use crate::git;
+

+
/// Setup a project remote and tracking branch.
+
pub struct SetupRemote<'a> {
+
    /// The project id.
+
    pub project: Id,
+
    /// The project default branch.
+
    pub default_branch: BranchName,
+
    /// The repository in which to setup the remote.
+
    pub repo: &'a git::Repository,
+
    /// Whether or not to fetch the remote immediately.
+
    pub fetch: bool,
+
    /// Whether or not to setup a remote tracking branch.
+
    pub tracking: bool,
+
}
+

+
impl<'a> SetupRemote<'a> {
+
    /// Run the setup for the given peer.
+
    pub fn run(&self, node: NodeId) -> anyhow::Result<Option<(Remote, String)>> {
+
        let url = radicle::git::Url::from(self.project).with_namespace(node);
+
        let mut remote =
+
            radicle::git::configure_remote(self.repo, rad::peer_remote(&node).as_str(), &url)?;
+

+
        // Fetch the refs into the working copy.
+
        if self.fetch {
+
            remote.fetch::<&str>(&[], None, None)?;
+
        }
+
        // Setup remote-tracking branch.
+
        if self.tracking {
+
            let branch = git::set_tracking(self.repo, &node, &self.default_branch)?;
+

+
            return Ok(Some((remote, branch)));
+
        }
+
        Ok(None)
+
    }
+
}
modified radicle/src/rad.rs
@@ -11,6 +11,7 @@ use crate::git;
use crate::identity::project::DocError;
use crate::identity::Id;
use crate::node;
+
use crate::node::NodeId;
use crate::storage::git::transport::{self, remote};
use crate::storage::git::{ProjectError, Storage};
use crate::storage::refs::SignedRefs;
@@ -19,6 +20,11 @@ use crate::{identity, storage};

pub static REMOTE_NAME: Lazy<git::RefString> = Lazy::new(|| git::refname!("rad"));

+
/// Radicle remote name for peer, eg. `rad/<node-id>`
+
pub fn peer_remote(peer: &NodeId) -> String {
+
    format!("rad/{peer}")
+
}
+

#[derive(Error, Debug)]
pub enum InitError {
    #[error("doc: {0}")]