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