From dee39c39116773fde22c4fe30a87d54d1d3658e2 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 5 Aug 2025 11:08:26 +0100 Subject: feat(send): push PR to custom clone url if the repo doesnt list any grasp servers, or pushing to them fails --- src/bin/git_remote_nostr/push.rs | 44 +++++++++++++++++++-------- src/bin/ngit/sub_commands/send.rs | 64 ++++++++++++++++++++++++++++++--------- src/lib/push.rs | 55 ++++++++++++++------------------- 3 files changed, 103 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index 3967699..4552b91 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs @@ -450,18 +450,38 @@ async fn generate_patches_or_pr_event_or_pr_updates( signer: &Arc, term: &Term, ) -> Result> { - let mut events: Vec = vec![]; let parent_is_pr = root_proposal.is_some_and(|proposal| proposal.kind.eq(&KIND_PULL_REQUEST)); let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead); if use_pr { - for event in push_refs_and_generate_pr_or_pr_update_event( + let repo_grasps = repo_ref.grasp_servers(); + let repo_grasp_clone_urls: Vec = repo_ref + .git_server + .iter() + .filter(|s| is_grasp_server(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( git_repo, repo_ref, ahead.first().context("no commits to push")?, user_ref, root_proposal, &None, + &repo_grasp_clone_urls, signer, term, ) @@ -471,12 +491,17 @@ async fn generate_patches_or_pr_event_or_pr_updates( } 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." } - )? - { - events.push(event); + )? { + 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 { - for patch in generate_cover_letter_and_patch_events( + generate_cover_letter_and_patch_events( None, git_repo, ahead, @@ -485,13 +510,8 @@ async fn generate_patches_or_pr_event_or_pr_updates( &root_proposal.map(|proposal| proposal.id.to_string()), &[], ) - .await? - { - events.push(patch); - } + .await } - - Ok(events) } type HashMapUrlRefspecs = HashMap>; diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs index 0aefb03..69ad1e6 100644 --- a/src/bin/ngit/sub_commands/send.rs +++ b/src/bin/ngit/sub_commands/send.rs @@ -1,11 +1,13 @@ -use std::path::Path; +use std::{path::Path, str::FromStr}; use anyhow::{Context, Result, bail}; use console::Style; use ngit::{ client::{Params, send_events}, + git::nostr_url::CloneUrl, git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, push::push_refs_and_generate_pr_or_pr_update_event, + repo_ref::is_grasp_server, utils::proposal_tip_is_pr_or_pr_update, }; use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event}; @@ -192,20 +194,52 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re commits.reverse(); let events = if as_pr { - 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, - &signer, - &console::Term::stdout(), - ) - .await? - - // TODO - // - allow specifying clone url and ref + let repo_grasps = repo_ref.grasp_servers(); + let repo_grasp_clone_urls: Vec = repo_ref + .git_server + .iter() + .filter(|s| is_grasp_server(s, &repo_grasps)) + .cloned() + .collect(); + if repo_grasp_clone_urls.is_empty() { + println!( + "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." + ); + } + let mut to_try = repo_grasp_clone_urls.clone(); + let mut tried = vec![]; + 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, + &repo_grasp_clone_urls, + &signer, + &console::Term::stdout(), + ) + .await?; + for url in to_try { + tried.push(url); + } + to_try = vec![]; + if let Some(events) = events { + break events; + } + 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); + // TODO customise ref to push + } else { + println!("invalid clone url"); + } + } } 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 bcd368b..4c2d8f1 100644 --- a/src/lib/push.rs +++ b/src/lib/push.rs @@ -4,7 +4,7 @@ use std::{ time::Instant, }; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Result, anyhow}; use auth_git2::GitAuthenticator; use console::Term; use nostr::{ @@ -25,7 +25,7 @@ use crate::{ }, git_events::generate_unsigned_pr_or_update_event, login::user::UserRef, - repo_ref::{RepoRef, is_grasp_server, normalize_grasp_server_url}, + repo_ref::{RepoRef, normalize_grasp_server_url}, utils::{ Direction, get_short_git_server_name, get_write_protocols_to_try, join_with_and, set_protocol_preference, @@ -329,19 +329,14 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event( user_ref: &UserRef, root_proposal: Option<&Event>, title_description_overide: &Option<(String, String)>, + servers: &[String], signer: &Arc, term: &Term, -) -> Result> { - let mut events: Vec = vec![]; - let repo_grasps = repo_ref.grasp_servers(); - let repo_grasp_clone_urls = repo_ref - .git_server - .iter() - .filter(|s| is_grasp_server(s, &repo_grasps)); +) -> Result<(Option>, Vec<(String, Result<()>)>)> { + let mut responses = vec![]; let mut unsigned_pr_event: Option = None; - let mut failed_clone_urls = vec![]; - for clone_url in repo_grasp_clone_urls { + for clone_url in servers { let mut draft_pr_event = if let Some(ref unsigned_pr_event) = unsigned_pr_event { unsigned_pr_event.clone() } else { @@ -360,7 +355,6 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event( let refspec = format!("{}:refs/nostr/{}", tip, draft_pr_event.id()); if let Err(error) = push_to_remote_url(git_repo, clone_url, &[refspec], term) { - failed_clone_urls.push(clone_url); term.write_line( format!( "push: error sending commit data to {}: {error}", @@ -368,7 +362,9 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event( ) .as_str(), )?; + responses.push((clone_url.clone(), Err(error))); } else { + responses.push((clone_url.clone(), Ok(()))); term.write_line( format!( "push: commit data sent to {}", @@ -379,17 +375,6 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event( unsigned_pr_event = Some(draft_pr_event); } } - if unsigned_pr_event.is_none() { - 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." - ); - - // 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 - } if let Some(unsigned_pr_event) = unsigned_pr_event { let pr_event = sign_draft_event( unsigned_pr_event, @@ -404,21 +389,25 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event( .to_string(), ) .await?; - events.push(pr_event); if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) { - events.push( - create_close_status_for_original_patch(signer, repo_ref, root_proposal.unwrap()) + Ok(( + Some(vec![ + pr_event, + create_close_status_for_original_patch( + signer, + repo_ref, + root_proposal.unwrap(), + ) .await?, - ); + ]), + responses, + )) + } else { + Ok((Some(vec![pr_event]), responses)) } } 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 + Ok((None, responses)) } - Ok(events) } async fn create_close_status_for_original_patch( -- cgit v1.2.3