codeberg_cli/actions/repo/
migrate.rs

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