Skip to main content

radicle_cli/commands/
checkout.rs

1mod args;
2
3use std::path::PathBuf;
4
5use anyhow::Context as _;
6use anyhow::anyhow;
7
8use radicle::git;
9use radicle::node::AliasStore;
10use radicle::prelude::*;
11use radicle::storage::git::transport;
12
13use crate::project;
14use crate::terminal as term;
15
16pub use args::Args;
17
18pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
19    let profile = ctx.profile()?;
20    execute(args, &profile)?;
21
22    Ok(())
23}
24
25fn execute(args: Args, profile: &Profile) -> anyhow::Result<PathBuf> {
26    let storage = &profile.storage;
27    let remote = args.remote.unwrap_or(profile.did());
28    let doc = storage
29        .repository(args.repo)?
30        .identity_doc()
31        .context("repository could not be found in local storage")?;
32    let payload = doc.project()?;
33    let path = PathBuf::from(payload.name());
34
35    transport::local::register(storage.clone());
36
37    if path.exists() {
38        anyhow::bail!("the local path {:?} already exists", path.as_path());
39    }
40
41    let mut spinner = term::spinner("Performing checkout…");
42    let repo = match radicle::rad::checkout(args.repo, &remote, path.clone(), &storage, false) {
43        Ok(repo) => repo,
44        Err(err) => {
45            spinner.failed();
46            term::blank();
47
48            return Err(err.into());
49        }
50    };
51    spinner.message(format!(
52        "Repository checkout successful under ./{}",
53        term::format::highlight(path.file_name().unwrap_or_default().to_string_lossy())
54    ));
55    spinner.finish();
56
57    let remotes = doc
58        .delegates()
59        .clone()
60        .into_iter()
61        .map(|did| *did)
62        .filter(|id| id != profile.id())
63        .collect::<Vec<_>>();
64
65    // Set up remote tracking branches for project delegates.
66    setup_remotes(
67        project::SetupRemote {
68            rid: args.repo,
69            tracking: Some(payload.default_branch().clone()),
70            repo: &repo,
71            fetch: true,
72        },
73        &remotes,
74        profile,
75    )?;
76
77    Ok(path)
78}
79
80/// Set up a remote and tracking branch for each given remote.
81pub fn setup_remotes(
82    setup: project::SetupRemote,
83    remotes: &[NodeId],
84    profile: &Profile,
85) -> anyhow::Result<()> {
86    let aliases = profile.aliases();
87
88    for remote_id in remotes {
89        if let Err(e) = setup_remote(&setup, remote_id, None, &aliases) {
90            term::warning(format!("Failed to setup remote for {remote_id}: {e}").as_str());
91        }
92    }
93    Ok(())
94}
95
96/// Set up a remote and tracking branch for the given remote.
97pub fn setup_remote(
98    setup: &project::SetupRemote,
99    remote_id: &NodeId,
100    remote_name: Option<git::fmt::RefString>,
101    aliases: &impl AliasStore,
102) -> anyhow::Result<git::fmt::RefString> {
103    let remote_name = if let Some(name) = remote_name {
104        name
105    } else {
106        let name = if let Some(alias) = aliases.alias(remote_id) {
107            format!("{alias}@{remote_id}")
108        } else {
109            remote_id.to_human()
110        };
111        git::fmt::RefString::try_from(name.as_str())
112            .map_err(|_| anyhow!("invalid remote name: '{name}'"))?
113    };
114    let (remote, branch) = setup.run(&remote_name, *remote_id)?;
115
116    term::success!("Remote {} added", term::format::tertiary(remote.name));
117
118    if let Some(branch) = branch {
119        term::success!(
120            "Remote-tracking branch {} created for {}",
121            term::format::tertiary(branch),
122            term::format::tertiary(term::format::node_id_human(remote_id))
123        );
124    }
125    Ok(remote_name)
126}