Skip to main content

codeberg_cli/actions/repo/
clone.rs

1use miette::{Context, IntoDiagnostic};
2use url::Url;
3
4use crate::actions::GlobalArgs;
5use crate::render::spinner::spin_until_ready;
6use crate::render::ui::confirm_with_prompt;
7use crate::types::context::BergContext;
8use crate::types::git::{Git, MaybeOwnerRepo, OwnerRepo};
9
10use clap::Parser;
11
12/// Clone a repository
13#[derive(Parser, Debug)]
14pub struct RepoCloneArgs {
15    /// Repository to be cloned
16    #[arg(value_name = "REPO or OWNER/REPO")]
17    pub owner_and_repo: MaybeOwnerRepo,
18    /// Whether or not to clone via ssh
19    #[arg(long, default_value_t = false)]
20    pub use_ssh: bool,
21    /// Where to clone into
22    #[arg(value_name = "DESTINATION")]
23    pub destination: Option<String>,
24}
25
26impl RepoCloneArgs {
27    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
28        let _ = global_args;
29        let ctx = BergContext::new(self, global_args.clone()).await?;
30
31        let owner_repo = match ctx.args.owner_and_repo {
32            MaybeOwnerRepo::ImplicitOwner(ref repo) => {
33                let owner = ctx
34                    .client
35                    .user_get_current()
36                    .await
37                    .into_diagnostic()?
38                    .login
39                    .context("Current user has no username on targeted forgejo instance")?;
40                OwnerRepo {
41                    owner,
42                    repo: repo.clone(),
43                }
44            }
45            MaybeOwnerRepo::ExplicitOwner(ref owner_repo) => owner_repo.clone(),
46        };
47
48        let url = if ctx.args.use_ssh {
49            spin_until_ready(get_ssh_url(
50                &ctx,
51                owner_repo.owner.as_str(),
52                owner_repo.repo.as_str(),
53            ))
54            .await?
55        } else {
56            ctx.config
57                .url()?
58                .join(format!("/{owner_repo}.git").as_str())
59                .into_diagnostic()?
60        };
61
62        println!("Cloning {url} ...");
63
64        if !global_args.non_interactive
65            && !confirm_with_prompt(format!(
66                "Do you want to clone the repository '{owner_repo}' into {pwd}/{repo}",
67                pwd = std::env::current_dir().into_diagnostic()?.display(),
68                repo = owner_repo.repo
69            ))?
70        {
71            println!("Canceled clone!");
72            std::process::exit(0);
73        }
74
75        Git::clone(
76            url,
77            ctx.args.destination.unwrap_or(owner_repo.repo).as_str(),
78        )?;
79
80        Ok(())
81    }
82}
83
84async fn get_ssh_url(
85    ctx: &BergContext<RepoCloneArgs>,
86    ownername: &str,
87    reponame: &str,
88) -> miette::Result<Url> {
89    ctx.client
90        .repo_get(ownername, reponame)
91        .await
92        .into_diagnostic()
93        .inspect(|x| tracing::debug!("{x:#?}"))
94        .and_then(|repo| repo.ssh_url.context("No SSH url on repo"))
95        .and_then(|url| {
96            Url::parse(url.as_str())
97                .into_diagnostic()
98                .with_context(|| format!("Url wasn't a valid SSH Url: {url}", url = url.as_str()))
99        })
100        .context("User doesn't own the repo that was specified or at least doesn't have access'.")
101}