diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/send.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/send.rs | 275 |
1 files changed, 244 insertions, 31 deletions
diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs index 609812b..835153e 100644 --- a/src/bin/ngit/sub_commands/send.rs +++ b/src/bin/ngit/sub_commands/send.rs | |||
| @@ -1,16 +1,27 @@ | |||
| 1 | use std::{path::Path, str::FromStr}; | 1 | use std::{path::Path, str::FromStr, thread, time::Duration}; |
| 2 | 2 | ||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use console::Style; | 4 | use console::Style; |
| 5 | use ngit::{ | 5 | use ngit::{ |
| 6 | cli_interactor::{PromptChoiceParms, multi_select_with_custom_value}, | ||
| 6 | client::{Params, send_events}, | 7 | client::{Params, send_events}, |
| 7 | git::nostr_url::CloneUrl, | 8 | git::nostr_url::CloneUrl, |
| 8 | git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, | 9 | git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, |
| 9 | push::push_refs_and_generate_pr_or_pr_update_event, | 10 | push::push_refs_and_generate_pr_or_pr_update_event, |
| 10 | repo_ref::is_grasp_server, | 11 | repo_ref::{ |
| 12 | format_grasp_server_url_as_clone_url, format_grasp_server_url_as_relay_url, | ||
| 13 | is_grasp_server, normalize_grasp_server_url, | ||
| 14 | }, | ||
| 11 | utils::proposal_tip_is_pr_or_pr_update, | 15 | utils::proposal_tip_is_pr_or_pr_update, |
| 12 | }; | 16 | }; |
| 13 | use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event}; | 17 | use nostr::{ |
| 18 | ToBech32, | ||
| 19 | event::Event, | ||
| 20 | nips::{ | ||
| 21 | nip01::Coordinate, | ||
| 22 | nip19::{Nip19Coordinate, Nip19Event}, | ||
| 23 | }, | ||
| 24 | }; | ||
| 14 | use nostr_sdk::hashes::sha1::Hash as Sha1Hash; | 25 | use nostr_sdk::hashes::sha1::Hash as Sha1Hash; |
| 15 | 26 | ||
| 16 | use crate::{ | 27 | use crate::{ |
| @@ -179,7 +190,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re | |||
| 179 | None | 190 | None |
| 180 | }; | 191 | }; |
| 181 | 192 | ||
| 182 | let (signer, user_ref, _) = login::login_or_signup( | 193 | let (signer, mut user_ref, _) = login::login_or_signup( |
| 183 | &Some(&git_repo), | 194 | &Some(&git_repo), |
| 184 | &extract_signer_cli_arguments(cli_args).unwrap_or(None), | 195 | &extract_signer_cli_arguments(cli_args).unwrap_or(None), |
| 185 | &cli_args.password, | 196 | &cli_args.password, |
| @@ -194,20 +205,55 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re | |||
| 194 | commits.reverse(); | 205 | commits.reverse(); |
| 195 | 206 | ||
| 196 | let events = if as_pr { | 207 | let events = if as_pr { |
| 208 | let mut to_try = vec![]; | ||
| 209 | let mut tried = vec![]; | ||
| 197 | let repo_grasps = repo_ref.grasp_servers(); | 210 | let repo_grasps = repo_ref.grasp_servers(); |
| 198 | let repo_grasp_clone_urls: Vec<String> = repo_ref | 211 | // if the user already has a fork, or is a maintainer, use those git servers |
| 199 | .git_server | 212 | let mut user_repo_ref = get_repo_ref_from_cache( |
| 200 | .iter() | 213 | Some(git_repo_path), |
| 201 | .filter(|s| is_grasp_server(s, &repo_grasps)) | 214 | &Nip19Coordinate { |
| 202 | .cloned() | 215 | coordinate: Coordinate { |
| 203 | .collect(); | 216 | kind: nostr::event::Kind::GitRepoAnnouncement, |
| 204 | if repo_grasp_clone_urls.is_empty() { | 217 | public_key: user_ref.public_key, |
| 218 | identifier: repo_ref.identifier.clone(), | ||
| 219 | }, | ||
| 220 | relays: vec![], | ||
| 221 | }, | ||
| 222 | ) | ||
| 223 | .await | ||
| 224 | .ok(); | ||
| 225 | if let Some(user_repo_ref) = &user_repo_ref { | ||
| 226 | for url in &user_repo_ref.git_server { | ||
| 227 | if CloneUrl::from_str(url).is_ok() { | ||
| 228 | to_try.push(url.clone()); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | } | ||
| 232 | if !to_try.is_empty() || !repo_grasps.is_empty() { | ||
| 233 | println!( | ||
| 234 | "pushing proposal refs to {}", | ||
| 235 | if repo_ref.maintainers.contains(&user_ref.public_key) { | ||
| 236 | "repository git servers" | ||
| 237 | } else if to_try.is_empty() { | ||
| 238 | "repository grasp servers" | ||
| 239 | } else if repo_grasps.is_empty() { | ||
| 240 | "the git servers listed in your fork" | ||
| 241 | } else { | ||
| 242 | "the git servers listed in your fork and repository grasp servers" | ||
| 243 | } | ||
| 244 | ); | ||
| 245 | } else { | ||
| 205 | println!( | 246 | println!( |
| 206 | "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." | 247 | "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." |
| 207 | ); | 248 | ); |
| 208 | } | 249 | } |
| 209 | let mut to_try = repo_grasp_clone_urls.clone(); | 250 | // also use repo grasp servers |
| 210 | let mut tried = vec![]; | 251 | for url in &repo_ref.git_server { |
| 252 | if is_grasp_server(url, &repo_grasps) && !to_try.contains(url) { | ||
| 253 | to_try.push(url.clone()); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 211 | let mut git_ref = None; | 257 | let mut git_ref = None; |
| 212 | loop { | 258 | loop { |
| 213 | let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event( | 259 | let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event( |
| @@ -217,7 +263,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re | |||
| 217 | &user_ref, | 263 | &user_ref, |
| 218 | root_proposal.as_ref(), | 264 | root_proposal.as_ref(), |
| 219 | &cover_letter_title_description, | 265 | &cover_letter_title_description, |
| 220 | &repo_grasp_clone_urls, | 266 | &to_try, |
| 221 | git_ref.clone(), | 267 | git_ref.clone(), |
| 222 | &signer, | 268 | &signer, |
| 223 | &console::Term::stdout(), | 269 | &console::Term::stdout(), |
| @@ -230,27 +276,194 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re | |||
| 230 | if let Some(events) = events { | 276 | if let Some(events) = events { |
| 231 | break events; | 277 | break events; |
| 232 | } | 278 | } |
| 233 | let clone_url = Interactor::default() | 279 | // fallback to creating user personal-fork on their grasp servers |
| 234 | .input( | 280 | let untried_user_grasp_servers: Vec<String> = user_ref |
| 235 | PromptInputParms::default().with_prompt("git repo url with write permission"), | 281 | .grasp_list |
| 236 | )? | 282 | .urls |
| 237 | .clone(); | 283 | .iter() |
| 238 | if CloneUrl::from_str(&clone_url).is_ok() { | 284 | .map(std::string::ToString::to_string) |
| 239 | to_try.push(clone_url); | 285 | .filter(|g| { |
| 240 | let mut git_ref_or_branch_name = Interactor::default() | 286 | // is a grasp server not in list of tried |
| 241 | .input( | 287 | !is_grasp_server(g, &tried) |
| 242 | PromptInputParms::default() | 288 | }) |
| 243 | .with_prompt("ref / branch name") | 289 | .collect(); |
| 244 | .with_default(git_ref.unwrap_or("refs/nostr/<event-id>".to_string())), | 290 | |
| 291 | if untried_user_grasp_servers.is_empty() | ||
| 292 | && Interactor::default().choice( | ||
| 293 | PromptChoiceParms::default() | ||
| 294 | .with_prompt("choose alternative git server") | ||
| 295 | .dont_report() | ||
| 296 | .with_choices(vec![ | ||
| 297 | "choose grasp server(s)".to_string(), | ||
| 298 | "enter a git repo url with write permission".to_string(), | ||
| 299 | ]) | ||
| 300 | .with_default(0), | ||
| 301 | )? == 1 | ||
| 302 | { | ||
| 303 | loop { | ||
| 304 | let clone_url = Interactor::default() | ||
| 305 | .input( | ||
| 306 | PromptInputParms::default() | ||
| 307 | .with_prompt("git repo url with write permission"), | ||
| 308 | )? | ||
| 309 | .clone(); | ||
| 310 | if CloneUrl::from_str(&clone_url).is_ok() { | ||
| 311 | to_try.push(clone_url); | ||
| 312 | let mut git_ref_or_branch_name = Interactor::default() | ||
| 313 | .input( | ||
| 314 | PromptInputParms::default() | ||
| 315 | .with_prompt("ref / branch name") | ||
| 316 | .with_default( | ||
| 317 | git_ref.unwrap_or("refs/nostr/<event-id>".to_string()), | ||
| 318 | ), | ||
| 319 | )? | ||
| 320 | .clone(); | ||
| 321 | if !git_ref_or_branch_name.starts_with("refs/") { | ||
| 322 | git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}"); | ||
| 323 | } | ||
| 324 | git_ref = Some(git_ref_or_branch_name); | ||
| 325 | break; | ||
| 326 | } | ||
| 327 | println!("invalid clone url"); | ||
| 328 | } | ||
| 329 | continue; | ||
| 330 | } | ||
| 331 | |||
| 332 | let mut new_grasp_server_events: Vec<Event> = vec![]; | ||
| 333 | |||
| 334 | let grasp_servers = if untried_user_grasp_servers.is_empty() { | ||
| 335 | let default_choices: Vec<String> = client | ||
| 336 | .get_grasp_default_set() | ||
| 337 | .iter() | ||
| 338 | .filter(|g| !is_grasp_server(g, &tried)) | ||
| 339 | .cloned() | ||
| 340 | .collect(); | ||
| 341 | let selections = vec![true; default_choices.len()]; // all selected by default | ||
| 342 | let grasp_servers = multi_select_with_custom_value( | ||
| 343 | "grasp server(s)", | ||
| 344 | "grasp server", | ||
| 345 | default_choices, | ||
| 346 | selections, | ||
| 347 | normalize_grasp_server_url, | ||
| 348 | )?; | ||
| 349 | if grasp_servers.is_empty() { | ||
| 350 | // ask again | ||
| 351 | continue; | ||
| 352 | } | ||
| 353 | let normalised_grasp_servers: Vec<String> = grasp_servers | ||
| 354 | .iter() | ||
| 355 | .filter_map(|g| normalize_grasp_server_url(g).ok()) | ||
| 356 | .collect(); | ||
| 357 | // if any grasp servers not listed in user grasp list prompt to update | ||
| 358 | let grasp_servers_not_in_user_prefs: Vec<String> = normalised_grasp_servers | ||
| 359 | .iter() | ||
| 360 | .filter(|g| { | ||
| 361 | !user_ref.grasp_list.urls.contains( | ||
| 362 | // unwrap is safe as we constructed g | ||
| 363 | &nostr::Url::parse(&format_grasp_server_url_as_relay_url(g).unwrap()) | ||
| 364 | .unwrap(), | ||
| 365 | ) | ||
| 366 | }) | ||
| 367 | .cloned() | ||
| 368 | .collect(); | ||
| 369 | if !grasp_servers_not_in_user_prefs.is_empty() | ||
| 370 | && Interactor::default().confirm( | ||
| 371 | PromptConfirmParms::default() | ||
| 372 | .with_prompt( | ||
| 373 | "add these to your list of prefered grasp servers?".to_string(), | ||
| 374 | ) | ||
| 375 | .with_default(true), | ||
| 245 | )? | 376 | )? |
| 246 | .clone(); | 377 | { |
| 247 | if !git_ref_or_branch_name.starts_with("refs/") { | 378 | for g in &normalised_grasp_servers { |
| 248 | git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}"); | 379 | let as_url = nostr::Url::parse(&format_grasp_server_url_as_relay_url(g)?)?; |
| 380 | if !user_ref.grasp_list.urls.contains(&as_url) { | ||
| 381 | user_ref.grasp_list.urls.push(as_url); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | new_grasp_server_events.push(user_ref.grasp_list.to_event(&signer).await?); | ||
| 249 | } | 385 | } |
| 250 | git_ref = Some(git_ref_or_branch_name); | 386 | normalised_grasp_servers |
| 251 | } else { | 387 | } else { |
| 252 | println!("invalid clone url"); | 388 | println!( |
| 389 | "{} personal-fork so we can push commits to your prefered grasp servers", | ||
| 390 | if user_repo_ref.is_some() { | ||
| 391 | "Updating" | ||
| 392 | } else { | ||
| 393 | "Creating a" | ||
| 394 | }, | ||
| 395 | ); | ||
| 396 | untried_user_grasp_servers | ||
| 397 | }; | ||
| 398 | |||
| 399 | let grasp_servers_as_personal_clone_url: Vec<String> = grasp_servers | ||
| 400 | .iter() | ||
| 401 | .filter_map(|g| { | ||
| 402 | format_grasp_server_url_as_clone_url( | ||
| 403 | g, | ||
| 404 | &user_ref.public_key, | ||
| 405 | &repo_ref.identifier, | ||
| 406 | ) | ||
| 407 | .ok() | ||
| 408 | }) | ||
| 409 | .collect(); | ||
| 410 | |||
| 411 | // create personal-fork / update existing user repo and add these grasp servers | ||
| 412 | let updated_user_repo_ref = { | ||
| 413 | if let Some(mut user_repo_ref) = user_repo_ref { | ||
| 414 | for g in &grasp_servers_as_personal_clone_url { | ||
| 415 | let _ = user_repo_ref.add_grasp_server(g); | ||
| 416 | } | ||
| 417 | user_repo_ref | ||
| 418 | } else { | ||
| 419 | // clone repo_ref and reset as personal-fork | ||
| 420 | let mut user_repo_ref = repo_ref.clone(); | ||
| 421 | user_repo_ref.trusted_maintainer = user_ref.public_key; | ||
| 422 | user_repo_ref.maintainers = vec![user_ref.public_key]; | ||
| 423 | user_repo_ref.git_server = vec![]; | ||
| 424 | user_repo_ref.relays = vec![]; | ||
| 425 | if !user_repo_ref | ||
| 426 | .hashtags | ||
| 427 | .contains(&"personal-fork".to_string()) | ||
| 428 | { | ||
| 429 | user_repo_ref.hashtags.push("personal-fork".to_string()); | ||
| 430 | } | ||
| 431 | user_repo_ref | ||
| 432 | } | ||
| 433 | }; | ||
| 434 | // pubish event to my-relays and my-fork-relays | ||
| 435 | new_grasp_server_events.push(updated_user_repo_ref.to_event(&signer).await?); | ||
| 436 | send_events( | ||
| 437 | &client, | ||
| 438 | Some(git_repo_path), | ||
| 439 | new_grasp_server_events, | ||
| 440 | user_ref.relays.write(), | ||
| 441 | updated_user_repo_ref.relays.clone(), | ||
| 442 | !cli_args.disable_cli_spinners, | ||
| 443 | false, | ||
| 444 | ) | ||
| 445 | .await?; | ||
| 446 | user_repo_ref = Some(updated_user_repo_ref); | ||
| 447 | // wait a few seconds | ||
| 448 | let countdown_start = 5; | ||
| 449 | let term = console::Term::stdout(); | ||
| 450 | for i in (1..=countdown_start).rev() { | ||
| 451 | term.write_line( | ||
| 452 | format!( | ||
| 453 | "waiting {i}s grasp servers to create your repo before we push your data" | ||
| 454 | ) | ||
| 455 | .as_str(), | ||
| 456 | )?; | ||
| 457 | thread::sleep(Duration::new(1, 0)); // Sleep for 1 second | ||
| 458 | term.clear_last_lines(1)?; | ||
| 459 | } | ||
| 460 | term.flush().unwrap(); // Ensure the output is flushed to the terminal | ||
| 461 | |||
| 462 | // add grasp servers to to_try | ||
| 463 | for url in grasp_servers_as_personal_clone_url { | ||
| 464 | to_try.push(url); | ||
| 253 | } | 465 | } |
| 466 | // the loop with continue with the grasp servers | ||
| 254 | } | 467 | } |
| 255 | } else { | 468 | } else { |
| 256 | let events = generate_cover_letter_and_patch_events( | 469 | let events = generate_cover_letter_and_patch_events( |