codeberg_cli/actions/repo/
migrate.rs1use 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#[derive(Parser, Debug)]
15pub struct RepoMigrateArgs {
16 #[arg(short, long)]
18 pub clone_addr: Option<String>,
19 #[arg(long)]
21 pub description: Option<String>,
22 #[arg(short, long, value_enum, value_name = "VISIBILITY")]
24 pub private: Option<Privacy>,
25 #[arg(id = "repo-name", short, long)]
27 pub repo_name: Option<String>,
28 #[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}