codeberg_cli/actions/repo/
fork.rs

1use crate::actions::GeneralArgs;
2use crate::render::spinner::{spin_and_try_every_second_for, spin_until_ready};
3use crate::render::ui::confirm_with_prompt;
4use crate::types::context::BergContext;
5use anyhow::Context;
6use forgejo_api::structs::CreateForkOption;
7use url::Url;
8
9use super::parse_owner_and_repo;
10
11use clap::Parser;
12
13/// Fork a repository
14#[derive(Parser, Debug)]
15pub struct RepoForkArgs {
16    /// Repository to be forked
17    #[arg(value_name = "OWNER/REPO")]
18    pub owner_and_repo: String,
19}
20
21impl RepoForkArgs {
22    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
23        let _ = general_args;
24        let ctx = BergContext::new(self, general_args).await?;
25
26        let (owner, repo) = parse_owner_and_repo(ctx.args.owner_and_repo.as_str())?;
27        let ssh_url =
28            spin_until_ready(start_fork_repo(&ctx, owner.as_str(), repo.as_str())).await?;
29        ask_confirm_clone(repo.as_str())?;
30        start_clone_repo(ssh_url)?;
31        Ok(())
32    }
33}
34
35async fn start_fork_repo(
36    ctx: &BergContext<RepoForkArgs>,
37    owner: &str,
38    repo: &str,
39) -> anyhow::Result<Url> {
40    // just to check if the repo exists
41    let _ssh_url_original = get_ssh_url(ctx, owner, repo).await?;
42
43    ctx.client
44        .create_fork(
45            owner,
46            repo,
47            CreateForkOption {
48                name: None,
49                organization: None,
50            },
51        )
52        .await?;
53    let user = ctx.client.user_get_current().await?;
54    let username = user
55        .login
56        .as_ref()
57        .cloned()
58        .context("Current user has no username")?;
59    let new_url = spin_and_try_every_second_for(|| get_ssh_url(ctx, &username, repo), 10).await?;
60    tracing::debug!("Forked Repo SSH URL: {new_url:?}");
61    Ok(new_url)
62}
63
64async fn get_ssh_url(
65    ctx: &BergContext<RepoForkArgs>,
66    owner: &str,
67    repo: &str,
68) -> anyhow::Result<Url> {
69    ctx.client
70        .repo_get(owner, repo)
71        .await
72        .map_err(anyhow::Error::from)
73        .and_then(|repo| repo.ssh_url.context("No SSH url on repo"))
74        .context("User doesn't own the repo that was specified.")
75}
76
77fn ask_confirm_clone(repo: &str) -> anyhow::Result<()> {
78    let current_path = std::env::current_dir()?;
79    if !confirm_with_prompt(
80        format!("Do you really to fork {repo} into the directory {current_path:?}").as_str(),
81    )? {
82        anyhow::bail!("Abort cloning the repository.")
83    }
84    Ok(())
85}
86
87fn start_clone_repo(ssh_url: Url) -> anyhow::Result<()> {
88    let mut cmd = std::process::Command::new("git");
89    cmd.arg("clone").arg(ssh_url.to_string());
90    tracing::debug!("cmd: {cmd:?}");
91    let mut child = cmd.stdout(std::process::Stdio::inherit()).spawn()?;
92    child.wait()?;
93    Ok(())
94}