codeberg_cli/actions/repo/
migrate.rs

1use crate::actions::GeneralArgs;
2use crate::actions::text_manipulation::{input_prompt_for, select_prompt_for};
3use crate::render::json::JsonToStdout;
4use crate::render::ui::{fuzzy_select_with_key, multi_fuzzy_select_with_key};
5use crate::types::api::privacy_type::Privacy;
6use crate::types::api::service_type::Service;
7use crate::types::context::BergContext;
8use clap::Parser;
9use forgejo_api::structs::{MigrateRepoOptions, MigrateRepoOptionsService};
10use strum::{Display, VariantArray};
11
12/// Create a new repository
13#[derive(Parser, Debug)]
14pub struct RepoMigrateArgs {
15    /// The repository that's going to be migrated to forgejo
16    #[arg(short, long)]
17    pub clone_addr: Option<String>,
18    /// The description of the repository *after* migration
19    #[arg(long)]
20    pub description: Option<String>,
21    /// The visibility of the repository *after* migration
22    #[arg(short, long, value_enum, value_name = "VISIBILITY")]
23    pub private: Option<Privacy>,
24    /// The repository name *after* the migration
25    #[arg(id = "repo-name", short, long)]
26    pub repo_name: Option<String>,
27    /// The kind of service, we're going to migrate *from* (source location of repository)
28    #[arg(short, long, value_enum, value_name = "SERVICE")]
29    pub service: Option<Service>,
30}
31
32#[derive(Display, PartialEq, Eq, VariantArray)]
33enum CreatableFields {
34    Service,
35    Description,
36    Private,
37}
38
39impl RepoMigrateArgs {
40    pub async fn run(self, general_args: GeneralArgs) -> anyhow::Result<()> {
41        let ctx = BergContext::new(self, general_args).await?;
42
43        let options = create_options(&ctx).await?;
44        let repo = ctx.client.repo_migrate(options).await?;
45        match general_args.output_mode {
46            crate::types::output::OutputMode::Pretty => {
47                tracing::debug!("{repo:?}");
48            }
49            crate::types::output::OutputMode::Json => {
50                repo.print_json()?;
51            }
52        }
53        Ok(())
54    }
55}
56
57async fn create_options(ctx: &BergContext<RepoMigrateArgs>) -> anyhow::Result<MigrateRepoOptions> {
58    let repo_name = match ctx.args.repo_name.as_ref() {
59        Some(name) => name.clone(),
60        None => {
61            if ctx.general_args.non_interactive {
62                anyhow::bail!(
63                    "You need to provide a target repository name address in non-interactive mode!"
64                );
65            }
66            inquire::Text::new(input_prompt_for("New Repository Name").as_str()).prompt()?
67        }
68    };
69
70    let clone_addr = match ctx.args.clone_addr.as_ref() {
71        Some(name) => name.clone(),
72        None => {
73            if ctx.general_args.non_interactive {
74                anyhow::bail!("You need to provide a clone address in non-interactive mode!");
75            }
76            inquire::Text::new(input_prompt_for("Repository clone address").as_str()).prompt()?
77        }
78    };
79
80    let mut options = MigrateRepoOptions {
81        auth_password: None,
82        auth_token: None,
83        auth_username: None,
84        clone_addr,
85        description: ctx.args.description.clone(),
86        issues: None,
87        labels: None,
88        lfs: None,
89        lfs_endpoint: None,
90        milestones: None,
91        mirror: None,
92        mirror_interval: None,
93        pull_requests: None,
94        releases: None,
95        repo_name,
96        repo_owner: None,
97        service: ctx.args.service.map(|service| service.into()),
98        uid: None,
99        wiki: None,
100        private: ctx.args.private.map(|privacy| match privacy {
101            Privacy::Private => true,
102            Privacy::Public => false,
103        }),
104    };
105
106    if !ctx.general_args.non_interactive {
107        let optional_data = {
108            use CreatableFields::*;
109            [
110                (Service, ctx.args.service.is_none()),
111                (Description, ctx.args.description.is_none()),
112                (Private, ctx.args.private.is_none()),
113            ]
114            .into_iter()
115            .filter_map(|(name, missing)| missing.then_some(name))
116            .collect::<Vec<_>>()
117        };
118
119        if !optional_data.is_empty() {
120            let chosen_optionals = multi_fuzzy_select_with_key(
121                &optional_data,
122                "Choose optional properties",
123                |_| false,
124                |o| o.to_string(),
125            )?;
126
127            {
128                use CreatableFields::*;
129                options.service =
130                    migrate_service(ctx, chosen_optionals.contains(&&Service)).await?;
131                options.private = repo_private(ctx, chosen_optionals.contains(&&Private)).await?;
132                options.description =
133                    repo_description(ctx, chosen_optionals.contains(&&Description)).await?;
134            }
135        }
136    }
137
138    Ok(options)
139}
140
141async fn migrate_service(
142    ctx: &BergContext<RepoMigrateArgs>,
143    interactive: bool,
144) -> anyhow::Result<Option<MigrateRepoOptionsService>> {
145    let service = match ctx.args.service {
146        Some(service) => service.to_string(),
147        None => {
148            use Service::*;
149            if !interactive {
150                return Ok(None);
151            }
152            fuzzy_select_with_key(
153                &[
154                    Git, Github, Gitea, Gitlab, Gogs, Onedev, Gitbucket, Codebase,
155                ],
156                select_prompt_for("service"),
157                |service| service.to_string(),
158            )
159            .copied()?
160            .to_string()
161        }
162    };
163    let migraterepooptionsservice = serde_json::from_str(&service).unwrap();
164    Ok(Some(migraterepooptionsservice))
165}
166
167async fn repo_private(
168    ctx: &BergContext<RepoMigrateArgs>,
169    interactive: bool,
170) -> anyhow::Result<Option<bool>> {
171    let privacy = match ctx.args.private {
172        Some(privacy) => match privacy {
173            Privacy::Private => true,
174            Privacy::Public => false,
175        },
176        None => {
177            if !interactive {
178                return Ok(None);
179            }
180            fuzzy_select_with_key(
181                &[true, false],
182                select_prompt_for("repo privacy"),
183                |private| {
184                    if *private {
185                        String::from("Private")
186                    } else {
187                        String::from("Public")
188                    }
189                },
190            )
191            .copied()?
192        }
193    };
194    Ok(Some(privacy))
195}
196
197async fn repo_description(
198    ctx: &BergContext<RepoMigrateArgs>,
199    interactive: bool,
200) -> anyhow::Result<Option<String>> {
201    let description = match ctx.args.description.as_ref() {
202        Some(desc) => desc.clone(),
203        None => {
204            if !interactive {
205                return Ok(None);
206            }
207            ctx.editor_for("a description", "Enter Repository description")?
208        }
209    };
210    Ok(Some(description))
211}