From 8527646022abdb290222a45314d090eef0871cae Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 4 Sep 2025 12:09:06 +0100 Subject: feat(remote): use push PR non-interactive fallback move the PR push code in 'ngit send' into lib. reuse the non-interactive fallbacks in git-remote-nostr --- src/bin/git_remote_nostr/push.rs | 66 +++----- src/bin/ngit/sub_commands/send.rs | 309 ++---------------------------------- src/lib/push.rs | 319 +++++++++++++++++++++++++++++++++++++- 3 files changed, 351 insertions(+), 343 deletions(-) (limited to 'src') diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index df895b1..8c102ee 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs @@ -19,7 +19,7 @@ use ngit::{ git_events::{self, KIND_PULL_REQUEST, event_to_cover_letter, get_event_root}, list::list_from_remotes, login::{self, user::UserRef}, - push::{push_refs_and_generate_pr_or_pr_update_event, push_to_remote}, + push::{push_to_remote, select_servers_push_refs_and_generate_pr_or_pr_update_event}, repo_ref::{self, get_repo_config_from_yaml, is_grasp_server_in_list}, repo_state, utils::{ @@ -173,7 +173,7 @@ async fn create_and_publish_events_and_proposals( existing_state: HashMap, term: &Term, ) -> Result<(Vec, bool)> { - let (signer, user_ref, _) = + let (signer, mut user_ref, _) = login::login_or_signup(&Some(git_repo), &None, &None, Some(client), true).await?; if !repo_ref.maintainers.contains(&user_ref.public_key) { @@ -249,10 +249,11 @@ async fn create_and_publish_events_and_proposals( } let (proposal_events, rejected_proposal_refspecs) = process_proposal_refspecs( + client, git_repo, repo_ref, proposal_refspecs, - &user_ref, + &mut user_ref, &signer, term, ) @@ -281,10 +282,11 @@ async fn create_and_publish_events_and_proposals( #[allow(clippy::too_many_lines)] async fn process_proposal_refspecs( + client: &Client, git_repo: &Repo, repo_ref: &RepoRef, proposal_refspecs: &Vec, - user_ref: &UserRef, + user_ref: &mut UserRef, signer: &Arc, term: &Term, ) -> Result<(Vec, Vec)> { @@ -294,7 +296,7 @@ async fn process_proposal_refspecs( return Ok((events, rejected_proposal_refspecs)); } let all_proposals = get_all_proposals(git_repo, repo_ref).await?; - let current_user = &user_ref.public_key; + let current_user = user_ref.public_key; for refspec in proposal_refspecs { let (from, to) = refspec_to_from_to(refspec).unwrap(); @@ -302,7 +304,7 @@ async fn process_proposal_refspecs( // this failed to find existing PR from user if let Some((_, (proposal, patches))) = - find_proposal_and_patches_by_branch_name(to, &all_proposals, Some(current_user)) + find_proposal_and_patches_by_branch_name(to, &all_proposals, Some(¤t_user)) { if [repo_ref.maintainers.clone(), vec![proposal.pubkey]] .concat() @@ -320,6 +322,7 @@ async fn process_proposal_refspecs( ); } for patch in generate_patches_or_pr_event_or_pr_updates( + client, git_repo, repo_ref, &ahead, @@ -359,6 +362,7 @@ async fn process_proposal_refspecs( || git_repo.are_commits_too_big_for_patches(&ahead) { for event in generate_patches_or_pr_event_or_pr_updates( + client, git_repo, repo_ref, &ahead, @@ -428,7 +432,7 @@ async fn process_proposal_refspecs( ); } for event in generate_patches_or_pr_event_or_pr_updates( - git_repo, repo_ref, &ahead, user_ref, None, signer, term, + client, git_repo, repo_ref, &ahead, user_ref, None, signer, term, ) .await? { @@ -441,11 +445,13 @@ async fn process_proposal_refspecs( } #[allow(clippy::too_many_lines)] +#[allow(clippy::too_many_arguments)] async fn generate_patches_or_pr_event_or_pr_updates( + client: &Client, git_repo: &Repo, repo_ref: &RepoRef, ahead: &[Sha1Hash], - user_ref: &UserRef, + user_ref: &mut UserRef, root_proposal: Option<&Event>, signer: &Arc, term: &Term, @@ -454,53 +460,27 @@ async fn generate_patches_or_pr_event_or_pr_updates( let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead); if use_pr { - let repo_grasps = repo_ref.grasp_servers(); - let repo_grasp_clone_urls: Vec = repo_ref - .git_server - .iter() - .filter(|s| is_grasp_server_in_list(s, &repo_grasps)) - .cloned() - .collect(); - - if repo_grasp_clone_urls.is_empty() { - // TODO get grasp_default_set servers that aren't in repo_grasps - // cycle through until one succeeds TODO create - // personal-fork announcement with grasp servers and - // push, after a few seconds push ref/nostr/eventid. if - // one success break out of for loop and continue - - bail!( - "The repository doesnt list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request. Soon ngit will support pushing your changes to a different git / grasp git server." - ); - } - - if let (Some(events), _) = push_refs_and_generate_pr_or_pr_update_event( + select_servers_push_refs_and_generate_pr_or_pr_update_event( + client, git_repo, repo_ref, ahead.first().context("no commits to push")?, user_ref, root_proposal, &None, - &repo_grasp_clone_urls, - None, signer, + false, term, ) - .await.context( + .await + .context(format!( + "{} run `ngit send` for more options.", if parent_is_pr { - "couldn't generate PR update event" + "couldn't generate PR update event." } else { "a commit in your proposal is too big for a nostr patch so we tried to create it as a nostr PR instead. Unfortunately this failed." - } - )? { - Ok(events) - } else { - bail!( - "a commit in your proposal is too big for a nostr patch. tried to use submit as a nostr Pull Request but could not find a grasp server that would accept your changes" - ); - // TODO suggest `ngit send` where user could specify their own clone - // url to push to once that feature is added - } + }, + )) } else { generate_cover_letter_and_patch_events( None, diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs index 3ae941f..ba64f64 100644 --- a/src/bin/ngit/sub_commands/send.rs +++ b/src/bin/ngit/sub_commands/send.rs @@ -1,32 +1,14 @@ -use std::{path::Path, str::FromStr, thread, time::Duration}; +use std::path::Path; use anyhow::{Context, Result, bail}; use console::Style; use ngit::{ - cli_interactor::{ - PromptChoiceParms, multi_select_with_custom_value, show_multi_input_prompt_success, - }, client::{Params, send_events}, - git::nostr_url::CloneUrl, - git_events::{ - EventRefType, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, - generate_cover_letter_and_patch_events, - }, - push::push_refs_and_generate_pr_or_pr_update_event, - repo_ref::{ - format_grasp_server_url_as_clone_url, format_grasp_server_url_as_relay_url, - is_grasp_server_in_list, normalize_grasp_server_url, - }, + git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, + push::select_servers_push_refs_and_generate_pr_or_pr_update_event, utils::proposal_tip_is_pr_or_pr_update, }; -use nostr::{ - ToBech32, - event::{Event, Kind}, - nips::{ - nip01::Coordinate, - nip19::{Nip19Coordinate, Nip19Event}, - }, -}; +use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event}; use nostr_sdk::hashes::sha1::Hash as Sha1Hash; use crate::{ @@ -210,278 +192,19 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re commits.reverse(); let events = if as_pr { - let mut to_try = vec![]; - let mut tried = vec![]; - let repo_grasps = repo_ref.grasp_servers(); - // if the user already has a fork, or is a maintainer, use those git servers - let mut user_repo_ref = get_repo_ref_from_cache( - Some(git_repo_path), - &Nip19Coordinate { - coordinate: Coordinate { - kind: nostr::event::Kind::GitRepoAnnouncement, - public_key: user_ref.public_key, - identifier: repo_ref.identifier.clone(), - }, - relays: vec![], - }, + select_servers_push_refs_and_generate_pr_or_pr_update_event( + &client, + &git_repo, + &repo_ref, + commits.last().context("no commits")?, + &mut user_ref, + root_proposal.as_ref(), + &cover_letter_title_description, + &signer, + true, + &console::Term::stdout(), ) - .await - .ok(); - if let Some(user_repo_ref) = &user_repo_ref { - for url in &user_repo_ref.git_server { - if CloneUrl::from_str(url).is_ok() { - to_try.push(url.clone()); - } - } - } - if !to_try.is_empty() || !repo_grasps.is_empty() { - println!( - "pushing proposal refs to {}", - if repo_ref.maintainers.contains(&user_ref.public_key) { - "repository git servers" - } else if to_try.is_empty() { - "repository grasp servers" - } else if repo_grasps.is_empty() { - "the git servers listed in your fork" - } else { - "the git servers listed in your fork and repository grasp servers" - } - ); - } else { - println!( - "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." - ); - } - // also use repo grasp servers - for url in &repo_ref.git_server { - if is_grasp_server_in_list(url, &repo_grasps) && !to_try.contains(url) { - to_try.push(url.clone()); - } - } - - let mut git_ref = None; - let events = loop { - let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event( - &git_repo, - &repo_ref, - commits.last().context("no commits")?, - &user_ref, - root_proposal.as_ref(), - &cover_letter_title_description, - &to_try, - git_ref.clone(), - &signer, - &console::Term::stdout(), - ) - .await?; - for url in to_try { - tried.push(url); - } - to_try = vec![]; - if let Some(events) = events { - break events; - } - // fallback to creating user personal-fork on their grasp servers - let untried_user_grasp_servers: Vec = user_ref - .grasp_list - .urls - .iter() - .map(std::string::ToString::to_string) - .filter(|g| { - // is a grasp server not in list of tried - !is_grasp_server_in_list(g, &tried) - }) - .collect(); - - if untried_user_grasp_servers.is_empty() - && Interactor::default().choice( - PromptChoiceParms::default() - .with_prompt("choose alternative git server") - .dont_report() - .with_choices(vec![ - "choose grasp server(s)".to_string(), - "enter a git repo url with write permission".to_string(), - ]) - .with_default(0), - )? == 1 - { - loop { - let clone_url = Interactor::default() - .input( - PromptInputParms::default() - .with_prompt("git repo url with write permission"), - )? - .clone(); - if CloneUrl::from_str(&clone_url).is_ok() { - to_try.push(clone_url); - let mut git_ref_or_branch_name = Interactor::default() - .input( - PromptInputParms::default() - .with_prompt("ref / branch name") - .with_default( - git_ref.unwrap_or("refs/nostr/".to_string()), - ), - )? - .clone(); - if !git_ref_or_branch_name.starts_with("refs/") { - git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}"); - } - git_ref = Some(git_ref_or_branch_name); - break; - } - println!("invalid clone url"); - } - continue; - } - - let mut new_grasp_server_events: Vec = vec![]; - - let grasp_servers = if untried_user_grasp_servers.is_empty() { - let default_choices: Vec = client - .get_grasp_default_set() - .iter() - .filter(|g| !is_grasp_server_in_list(g, &tried)) - .cloned() - .collect(); - let selections = vec![true; default_choices.len()]; // all selected by default - let grasp_servers = multi_select_with_custom_value( - "alternative grasp server(s)", - "grasp server", - default_choices, - selections, - normalize_grasp_server_url, - )?; - show_multi_input_prompt_success("alternative grasp server(s)", &grasp_servers); - if grasp_servers.is_empty() { - // ask again - continue; - } - let normalised_grasp_servers: Vec = grasp_servers - .iter() - .filter_map(|g| normalize_grasp_server_url(g).ok()) - .collect(); - // if any grasp servers not listed in user grasp list prompt to update - let grasp_servers_not_in_user_prefs: Vec = normalised_grasp_servers - .iter() - .filter(|g| { - !user_ref.grasp_list.urls.contains( - // unwrap is safe as we constructed g - &nostr::Url::parse(&format_grasp_server_url_as_relay_url(g).unwrap()) - .unwrap(), - ) - }) - .cloned() - .collect(); - if !grasp_servers_not_in_user_prefs.is_empty() - && Interactor::default().confirm( - PromptConfirmParms::default() - .with_prompt( - "add these to your list of prefered grasp servers?".to_string(), - ) - .with_default(true), - )? - { - for g in &normalised_grasp_servers { - let as_url = nostr::Url::parse(&format_grasp_server_url_as_relay_url(g)?)?; - if !user_ref.grasp_list.urls.contains(&as_url) { - user_ref.grasp_list.urls.push(as_url); - } - } - new_grasp_server_events.push(user_ref.grasp_list.to_event(&signer).await?); - } - normalised_grasp_servers - } else { - untried_user_grasp_servers - }; - println!( - "{} personal-fork so we can push commits to your prefered grasp servers", - if user_repo_ref.is_some() { - "Updating" - } else { - "Creating a" - }, - ); - - let grasp_servers_as_personal_clone_url: Vec = grasp_servers - .iter() - .filter_map(|g| { - format_grasp_server_url_as_clone_url( - g, - &user_ref.public_key, - &repo_ref.identifier, - ) - .ok() - }) - .collect(); - - // create personal-fork / update existing user repo and add these grasp servers - let updated_user_repo_ref = { - if let Some(mut user_repo_ref) = user_repo_ref { - for g in &grasp_servers_as_personal_clone_url { - user_repo_ref.add_grasp_server(g)?; - } - user_repo_ref - } else { - // clone repo_ref and reset as personal-fork - let mut user_repo_ref = repo_ref.clone(); - user_repo_ref.trusted_maintainer = user_ref.public_key; - user_repo_ref.maintainers = vec![user_ref.public_key]; - user_repo_ref.git_server = vec![]; - user_repo_ref.relays = vec![]; - if !user_repo_ref - .hashtags - .contains(&"personal-fork".to_string()) - { - user_repo_ref.hashtags.push("personal-fork".to_string()); - } - user_repo_ref - } - }; - // pubish event to my-relays and my-fork-relays - new_grasp_server_events.push(updated_user_repo_ref.to_event(&signer).await?); - send_events( - &client, - Some(git_repo_path), - new_grasp_server_events, - user_ref.relays.write(), - updated_user_repo_ref.relays.clone(), - !cli_args.disable_cli_spinners, - false, - ) - .await?; - user_repo_ref = Some(updated_user_repo_ref); - // wait a few seconds - let countdown_start = 5; - let term = console::Term::stdout(); - for i in (1..=countdown_start).rev() { - term.write_line( - format!( - "waiting {i}s grasp servers to create your repo before we push your data" - ) - .as_str(), - )?; - thread::sleep(Duration::new(1, 0)); // Sleep for 1 second - term.clear_last_lines(1)?; - } - term.flush().unwrap(); // Ensure the output is flushed to the terminal - - // add grasp servers to to_try - for url in grasp_servers_as_personal_clone_url { - to_try.push(url); - } - // the loop with continue with the grasp servers - }; - println!( - "posting {}", - if events.iter().any(|e| e.kind.eq(&Kind::GitStatusClosed)) { - "proposal revision as new PR event, and a close status for the old patch" - } else if events.iter().any(|e| e.kind.eq(&KIND_PULL_REQUEST_UPDATE)) { - "proposal revision as PR update event" - } else { - "proposal as PR event" - } - ); - events + .await? } else { let events = generate_cover_letter_and_patch_events( cover_letter_title_description.clone(), diff --git a/src/lib/push.rs b/src/lib/push.rs index 8cb0212..28692f3 100644 --- a/src/lib/push.rs +++ b/src/lib/push.rs @@ -1,31 +1,39 @@ use std::{ collections::{HashMap, HashSet}, + str::FromStr, sync::{Arc, Mutex}, - time::Instant, + thread, + time::{Duration, Instant}, }; -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result, anyhow, bail}; use auth_git2::GitAuthenticator; use console::Term; use nostr::{ event::{Event, EventBuilder, Kind, Tag, TagStandard, UnsignedEvent}, hashes::sha1::Hash as Sha1Hash, key::PublicKey, - nips::nip10::Marker, + nips::{nip01::Coordinate, nip10::Marker, nip19::Nip19Coordinate}, signer::NostrSigner, }; use crate::{ - cli_interactor::count_lines_per_msg_vec, - client::{sign_draft_event, sign_event}, + cli_interactor::{ + Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms, PromptInputParms, + count_lines_per_msg_vec, multi_select_with_custom_value, show_multi_input_prompt_success, + }, + client::{Connect, get_repo_ref_from_cache, send_events, sign_draft_event, sign_event}, git::{ Repo, RepoActions, nostr_url::{CloneUrl, NostrUrlDecoded}, oid_to_shorthand_string, }, - git_events::generate_unsigned_pr_or_update_event, + git_events::{KIND_PULL_REQUEST_UPDATE, generate_unsigned_pr_or_update_event}, login::user::UserRef, - repo_ref::{RepoRef, is_grasp_server_clone_url, normalize_grasp_server_url}, + repo_ref::{ + RepoRef, format_grasp_server_url_as_clone_url, format_grasp_server_url_as_relay_url, + is_grasp_server_clone_url, is_grasp_server_in_list, normalize_grasp_server_url, + }, utils::{ Direction, get_short_git_server_name, get_write_protocols_to_try, join_with_and, set_protocol_preference, @@ -363,6 +371,303 @@ impl<'a> PushReporter<'a> { } } +#[allow(clippy::too_many_arguments)] +pub async fn select_servers_push_refs_and_generate_pr_or_pr_update_event( + #[cfg(test)] client: &crate::client::MockConnect, + #[cfg(not(test))] client: &crate::client::Client, + git_repo: &Repo, + repo_ref: &RepoRef, + tip: &Sha1Hash, + user_ref: &mut UserRef, + root_proposal: Option<&Event>, + title_description_overide: &Option<(String, String)>, + signer: &Arc, + interactive: bool, + term: &Term, +) -> Result> { + let git_repo_path = git_repo.get_path()?; + let mut to_try = vec![]; + let mut tried = vec![]; + let repo_grasps = repo_ref.grasp_servers(); + // if the user already has a fork, or is a maintainer, use those git servers + let mut user_repo_ref = get_repo_ref_from_cache( + Some(git_repo_path), + &Nip19Coordinate { + coordinate: Coordinate { + kind: nostr::event::Kind::GitRepoAnnouncement, + public_key: user_ref.public_key, + identifier: repo_ref.identifier.clone(), + }, + relays: vec![], + }, + ) + .await + .ok(); + if let Some(user_repo_ref) = &user_repo_ref { + for url in &user_repo_ref.git_server { + if CloneUrl::from_str(url).is_ok() { + to_try.push(url.clone()); + } + } + } + if !to_try.is_empty() || !repo_grasps.is_empty() { + println!( + "pushing proposal refs to {}", + if repo_ref.maintainers.contains(&user_ref.public_key) { + "repository git servers" + } else if to_try.is_empty() { + "repository grasp servers" + } else if repo_grasps.is_empty() { + "the git servers listed in your fork" + } else { + "the git servers listed in your fork and repository grasp servers" + } + ); + } else { + println!( + "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." + ); + } + // also use repo grasp servers + for url in &repo_ref.git_server { + if is_grasp_server_in_list(url, &repo_grasps) && !to_try.contains(url) { + to_try.push(url.clone()); + } + } + + let mut git_ref = None; + let events = loop { + let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event( + git_repo, + repo_ref, + tip, + user_ref, + root_proposal, + title_description_overide, + &to_try, + git_ref.clone(), + signer, + term, + ) + .await?; + for url in to_try { + tried.push(url); + } + to_try = vec![]; + if let Some(events) = events { + break events; + } + // fallback to creating user personal-fork on their grasp servers + let untried_user_grasp_servers: Vec = user_ref + .grasp_list + .urls + .iter() + .map(std::string::ToString::to_string) + .filter(|g| { + // is a grasp server not in list of tried + !is_grasp_server_in_list(g, &tried) + }) + .collect(); + + if untried_user_grasp_servers.is_empty() { + if !interactive { + if repo_grasps.is_empty() { + bail!( + "failed to write PR data. nostr repo doesnt lists any grasp servers which allow you to write PR branches. run `ngit send` to select an alternative git server to host your PR diff." + ) + } + bail!( + "failed to write PR data to git servers associated with this nostr repo. run `ngit send` to select an alternative git server to host your PR diff." + ) + } + if Interactor::default().choice( + PromptChoiceParms::default() + .with_prompt("choose alternative git server") + .dont_report() + .with_choices(vec![ + "choose grasp server(s)".to_string(), + "enter a git repo url with write permission".to_string(), + ]) + .with_default(0), + )? == 1 + { + loop { + let clone_url = Interactor::default() + .input( + PromptInputParms::default() + .with_prompt("git repo url with write permission"), + )? + .clone(); + if CloneUrl::from_str(&clone_url).is_ok() { + to_try.push(clone_url); + let mut git_ref_or_branch_name = Interactor::default() + .input( + PromptInputParms::default() + .with_prompt("ref / branch name") + .with_default( + git_ref.unwrap_or("refs/nostr/".to_string()), + ), + )? + .clone(); + if !git_ref_or_branch_name.starts_with("refs/") { + git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}"); + } + git_ref = Some(git_ref_or_branch_name); + break; + } + println!("invalid clone url"); + } + continue; + } + } + + let mut new_grasp_server_events: Vec = vec![]; + + let grasp_servers = if untried_user_grasp_servers.is_empty() { + let default_choices: Vec = client + .get_grasp_default_set() + .iter() + .filter(|g| !is_grasp_server_in_list(g, &tried)) + .cloned() + .collect(); + let selections = vec![true; default_choices.len()]; // all selected by default + let grasp_servers = multi_select_with_custom_value( + "alternative grasp server(s)", + "grasp server", + default_choices, + selections, + normalize_grasp_server_url, + )?; + show_multi_input_prompt_success("alternative grasp server(s)", &grasp_servers); + if grasp_servers.is_empty() { + // ask again + continue; + } + let normalised_grasp_servers: Vec = grasp_servers + .iter() + .filter_map(|g| normalize_grasp_server_url(g).ok()) + .collect(); + // if any grasp servers not listed in user grasp list prompt to update + let grasp_servers_not_in_user_prefs: Vec = normalised_grasp_servers + .iter() + .filter(|g| { + !user_ref.grasp_list.urls.contains( + // unwrap is safe as we constructed g + &nostr::Url::parse(&format_grasp_server_url_as_relay_url(g).unwrap()) + .unwrap(), + ) + }) + .cloned() + .collect(); + if !grasp_servers_not_in_user_prefs.is_empty() + && Interactor::default().confirm( + PromptConfirmParms::default() + .with_prompt( + "add these to your list of prefered grasp servers?".to_string(), + ) + .with_default(true), + )? + { + for g in &normalised_grasp_servers { + let as_url = nostr::Url::parse(&format_grasp_server_url_as_relay_url(g)?)?; + if !user_ref.grasp_list.urls.contains(&as_url) { + user_ref.grasp_list.urls.push(as_url); + } + } + new_grasp_server_events.push(user_ref.grasp_list.to_event(signer).await?); + } + normalised_grasp_servers + } else { + untried_user_grasp_servers + }; + println!( + "{} personal-fork so we can push commits to your prefered grasp servers", + if user_repo_ref.is_some() { + "Updating" + } else { + "Creating a" + }, + ); + + let grasp_servers_as_personal_clone_url: Vec = grasp_servers + .iter() + .filter_map(|g| { + format_grasp_server_url_as_clone_url(g, &user_ref.public_key, &repo_ref.identifier) + .ok() + }) + .collect(); + + // create personal-fork / update existing user repo and add these grasp servers + let updated_user_repo_ref = { + if let Some(mut user_repo_ref) = user_repo_ref { + for g in &grasp_servers_as_personal_clone_url { + user_repo_ref.add_grasp_server(g)?; + } + user_repo_ref + } else { + // clone repo_ref and reset as personal-fork + let mut user_repo_ref = repo_ref.clone(); + user_repo_ref.trusted_maintainer = user_ref.public_key; + user_repo_ref.maintainers = vec![user_ref.public_key]; + user_repo_ref.git_server = vec![]; + user_repo_ref.relays = vec![]; + if !user_repo_ref + .hashtags + .contains(&"personal-fork".to_string()) + { + user_repo_ref.hashtags.push("personal-fork".to_string()); + } + user_repo_ref + } + }; + // pubish event to my-relays and my-fork-relays + new_grasp_server_events.push(updated_user_repo_ref.to_event(signer).await?); + send_events( + client, + Some(git_repo_path), + new_grasp_server_events, + user_ref.relays.write(), + updated_user_repo_ref.relays.clone(), + #[cfg(test)] + true, + #[cfg(not(test))] + false, + false, + ) + .await?; + user_repo_ref = Some(updated_user_repo_ref); + // wait a few seconds + let countdown_start = 5; + let term = console::Term::stdout(); + for i in (1..=countdown_start).rev() { + term.write_line( + format!("waiting {i}s grasp servers to create your repo before we push your data") + .as_str(), + )?; + thread::sleep(Duration::new(1, 0)); // Sleep for 1 second + term.clear_last_lines(1)?; + } + term.flush().unwrap(); // Ensure the output is flushed to the terminal + + // add grasp servers to to_try + for url in grasp_servers_as_personal_clone_url { + to_try.push(url); + } + // the loop with continue with the grasp servers + }; + println!( + "posting {}", + if events.iter().any(|e| e.kind.eq(&Kind::GitStatusClosed)) { + "proposal revision as new PR event, and a close status for the old patch" + } else if events.iter().any(|e| e.kind.eq(&KIND_PULL_REQUEST_UPDATE)) { + "proposal revision as PR update event" + } else { + "proposal as PR event" + } + ); + Ok(events) +} + #[allow(clippy::too_many_arguments)] pub async fn push_refs_and_generate_pr_or_pr_update_event( git_repo: &Repo, -- cgit v1.2.3