codeberg-cli 0.5.5

CLI Tool for codeberg similar to gh and glab
Documentation
use miette::{Context, IntoDiagnostic};
use url::Url;

use crate::actions::GlobalArgs;
use crate::render::spinner::spin_until_ready;
use crate::render::ui::confirm_with_prompt;
use crate::types::context::BergContext;
use crate::types::git::{Git, MaybeOwnerRepo, OwnerRepo};

use clap::Parser;

/// Clone a repository
#[derive(Parser, Debug)]
pub struct RepoCloneArgs {
    /// Repository to be cloned
    #[arg(value_name = "REPO or OWNER/REPO")]
    pub owner_and_repo: MaybeOwnerRepo,
    /// Whether or not to clone via ssh
    #[arg(long, default_value_t = false)]
    pub use_ssh: bool,
    /// Where to clone into
    #[arg(value_name = "DESTINATION")]
    pub destination: Option<String>,
}

impl RepoCloneArgs {
    pub async fn run(self, global_args: GlobalArgs) -> miette::Result<()> {
        let _ = global_args;
        let ctx = BergContext::new(self, global_args.clone()).await?;

        let owner_repo = match ctx.args.owner_and_repo {
            MaybeOwnerRepo::ImplicitOwner(ref repo) => {
                let owner = ctx
                    .client
                    .user_get_current()
                    .await
                    .into_diagnostic()?
                    .login
                    .context("Current user has no username on targeted forgejo instance")?;
                OwnerRepo {
                    owner,
                    repo: repo.clone(),
                }
            }
            MaybeOwnerRepo::ExplicitOwner(ref owner_repo) => owner_repo.clone(),
        };

        let url = if ctx.args.use_ssh {
            spin_until_ready(get_ssh_url(
                &ctx,
                owner_repo.owner.as_str(),
                owner_repo.repo.as_str(),
            ))
            .await?
        } else {
            ctx.config
                .url()?
                .join(format!("/{owner_repo}.git").as_str())
                .into_diagnostic()?
        };

        println!("Cloning {url} ...");

        if !global_args.non_interactive
            && !confirm_with_prompt(format!(
                "Do you want to clone the repository '{owner_repo}' into {pwd}/{repo}",
                pwd = std::env::current_dir().into_diagnostic()?.display(),
                repo = owner_repo.repo
            ))?
        {
            println!("Canceled clone!");
            std::process::exit(0);
        }

        Git::clone(
            url,
            ctx.args.destination.unwrap_or(owner_repo.repo).as_str(),
        )?;

        Ok(())
    }
}

async fn get_ssh_url(
    ctx: &BergContext<RepoCloneArgs>,
    ownername: &str,
    reponame: &str,
) -> miette::Result<Url> {
    ctx.client
        .repo_get(ownername, reponame)
        .await
        .into_diagnostic()
        .inspect(|x| tracing::debug!("{x:#?}"))
        .and_then(|repo| repo.ssh_url.context("No SSH url on repo"))
        .and_then(|url| {
            Url::parse(url.as_str())
                .into_diagnostic()
                .with_context(|| format!("Url wasn't a valid SSH Url: {url}", url = url.as_str()))
        })
        .context("User doesn't own the repo that was specified or at least doesn't have access'.")
}