Skip to main content

radicle_cli/commands/
checkout.rs

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