upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-08-05 11:08:26 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2025-08-05 11:08:26 +0100
commitdee39c39116773fde22c4fe30a87d54d1d3658e2 (patch)
tree0185d6e9991fe535fd0a1000bedcfef69d8850be /src
parentf48677bad3f3dabb80992806e0e4c8ad4d45c716 (diff)
feat(send): push PR to custom clone url
if the repo doesnt list any grasp servers, or pushing to them fails
Diffstat (limited to 'src')
-rw-r--r--src/bin/git_remote_nostr/push.rs44
-rw-r--r--src/bin/ngit/sub_commands/send.rs64
-rw-r--r--src/lib/push.rs55
3 files changed, 103 insertions, 60 deletions
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(
450 signer: &Arc<dyn NostrSigner>, 450 signer: &Arc<dyn NostrSigner>,
451 term: &Term, 451 term: &Term,
452) -> Result<Vec<Event>> { 452) -> Result<Vec<Event>> {
453 let mut events: Vec<Event> = vec![];
454 let parent_is_pr = root_proposal.is_some_and(|proposal| proposal.kind.eq(&KIND_PULL_REQUEST)); 453 let parent_is_pr = root_proposal.is_some_and(|proposal| proposal.kind.eq(&KIND_PULL_REQUEST));
455 let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead); 454 let use_pr = parent_is_pr || git_repo.are_commits_too_big_for_patches(ahead);
456 455
457 if use_pr { 456 if use_pr {
458 for event in push_refs_and_generate_pr_or_pr_update_event( 457 let repo_grasps = repo_ref.grasp_servers();
458 let repo_grasp_clone_urls: Vec<String> = repo_ref
459 .git_server
460 .iter()
461 .filter(|s| is_grasp_server(s, &repo_grasps))
462 .cloned()
463 .collect();
464
465 if repo_grasp_clone_urls.is_empty() {
466 // TODO get grasp_default_set servers that aren't in repo_grasps
467 // cycle through until one succeeds TODO create
468 // personal-fork announcement with grasp servers and
469 // push, after a few seconds push ref/nostr/eventid. if
470 // one success break out of for loop and continue
471
472 bail!(
473 "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."
474 );
475 }
476
477 if let (Some(events), _) = push_refs_and_generate_pr_or_pr_update_event(
459 git_repo, 478 git_repo,
460 repo_ref, 479 repo_ref,
461 ahead.first().context("no commits to push")?, 480 ahead.first().context("no commits to push")?,
462 user_ref, 481 user_ref,
463 root_proposal, 482 root_proposal,
464 &None, 483 &None,
484 &repo_grasp_clone_urls,
465 signer, 485 signer,
466 term, 486 term,
467 ) 487 )
@@ -471,12 +491,17 @@ async fn generate_patches_or_pr_event_or_pr_updates(
471 } else { 491 } else {
472 "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." 492 "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."
473 } 493 }
474 )? 494 )? {
475 { 495 Ok(events)
476 events.push(event); 496 } else {
497 bail!(
498 "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"
499 );
500 // TODO suggest `ngit send` where user could specify their own clone
501 // url to push to once that feature is added
477 } 502 }
478 } else { 503 } else {
479 for patch in generate_cover_letter_and_patch_events( 504 generate_cover_letter_and_patch_events(
480 None, 505 None,
481 git_repo, 506 git_repo,
482 ahead, 507 ahead,
@@ -485,13 +510,8 @@ async fn generate_patches_or_pr_event_or_pr_updates(
485 &root_proposal.map(|proposal| proposal.id.to_string()), 510 &root_proposal.map(|proposal| proposal.id.to_string()),
486 &[], 511 &[],
487 ) 512 )
488 .await? 513 .await
489 {
490 events.push(patch);
491 }
492 } 514 }
493
494 Ok(events)
495} 515}
496 516
497type HashMapUrlRefspecs = HashMap<String, Vec<String>>; 517type HashMapUrlRefspecs = HashMap<String, Vec<String>>;
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 @@
1use std::path::Path; 1use std::{path::Path, str::FromStr};
2 2
3use anyhow::{Context, Result, bail}; 3use anyhow::{Context, Result, bail};
4use console::Style; 4use console::Style;
5use ngit::{ 5use ngit::{
6 client::{Params, send_events}, 6 client::{Params, send_events},
7 git::nostr_url::CloneUrl,
7 git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, 8 git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events},
8 push::push_refs_and_generate_pr_or_pr_update_event, 9 push::push_refs_and_generate_pr_or_pr_update_event,
10 repo_ref::is_grasp_server,
9 utils::proposal_tip_is_pr_or_pr_update, 11 utils::proposal_tip_is_pr_or_pr_update,
10}; 12};
11use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event}; 13use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event};
@@ -192,20 +194,52 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
192 commits.reverse(); 194 commits.reverse();
193 195
194 let events = if as_pr { 196 let events = if as_pr {
195 push_refs_and_generate_pr_or_pr_update_event( 197 let repo_grasps = repo_ref.grasp_servers();
196 &git_repo, 198 let repo_grasp_clone_urls: Vec<String> = repo_ref
197 &repo_ref, 199 .git_server
198 commits.last().context("no commits")?, 200 .iter()
199 &user_ref, 201 .filter(|s| is_grasp_server(s, &repo_grasps))
200 root_proposal.as_ref(), 202 .cloned()
201 &cover_letter_title_description, 203 .collect();
202 &signer, 204 if repo_grasp_clone_urls.is_empty() {
203 &console::Term::stdout(), 205 println!(
204 ) 206 "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request."
205 .await? 207 );
206 208 }
207 // TODO 209 let mut to_try = repo_grasp_clone_urls.clone();
208 // - allow specifying clone url and ref 210 let mut tried = vec![];
211 loop {
212 let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event(
213 &git_repo,
214 &repo_ref,
215 commits.last().context("no commits")?,
216 &user_ref,
217 root_proposal.as_ref(),
218 &cover_letter_title_description,
219 &repo_grasp_clone_urls,
220 &signer,
221 &console::Term::stdout(),
222 )
223 .await?;
224 for url in to_try {
225 tried.push(url);
226 }
227 to_try = vec![];
228 if let Some(events) = events {
229 break events;
230 }
231 let clone_url = Interactor::default()
232 .input(
233 PromptInputParms::default().with_prompt("git repo url with write permission"),
234 )?
235 .clone();
236 if CloneUrl::from_str(&clone_url).is_ok() {
237 to_try.push(clone_url);
238 // TODO customise ref to push
239 } else {
240 println!("invalid clone url");
241 }
242 }
209 } else { 243 } else {
210 let events = generate_cover_letter_and_patch_events( 244 let events = generate_cover_letter_and_patch_events(
211 cover_letter_title_description.clone(), 245 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::{
4 time::Instant, 4 time::Instant,
5}; 5};
6 6
7use anyhow::{Result, anyhow, bail}; 7use anyhow::{Result, anyhow};
8use auth_git2::GitAuthenticator; 8use auth_git2::GitAuthenticator;
9use console::Term; 9use console::Term;
10use nostr::{ 10use nostr::{
@@ -25,7 +25,7 @@ use crate::{
25 }, 25 },
26 git_events::generate_unsigned_pr_or_update_event, 26 git_events::generate_unsigned_pr_or_update_event,
27 login::user::UserRef, 27 login::user::UserRef,
28 repo_ref::{RepoRef, is_grasp_server, normalize_grasp_server_url}, 28 repo_ref::{RepoRef, normalize_grasp_server_url},
29 utils::{ 29 utils::{
30 Direction, get_short_git_server_name, get_write_protocols_to_try, join_with_and, 30 Direction, get_short_git_server_name, get_write_protocols_to_try, join_with_and,
31 set_protocol_preference, 31 set_protocol_preference,
@@ -329,19 +329,14 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event(
329 user_ref: &UserRef, 329 user_ref: &UserRef,
330 root_proposal: Option<&Event>, 330 root_proposal: Option<&Event>,
331 title_description_overide: &Option<(String, String)>, 331 title_description_overide: &Option<(String, String)>,
332 servers: &[String],
332 signer: &Arc<dyn NostrSigner>, 333 signer: &Arc<dyn NostrSigner>,
333 term: &Term, 334 term: &Term,
334) -> Result<Vec<Event>> { 335) -> Result<(Option<Vec<Event>>, Vec<(String, Result<()>)>)> {
335 let mut events: Vec<Event> = vec![]; 336 let mut responses = vec![];
336 let repo_grasps = repo_ref.grasp_servers();
337 let repo_grasp_clone_urls = repo_ref
338 .git_server
339 .iter()
340 .filter(|s| is_grasp_server(s, &repo_grasps));
341 337
342 let mut unsigned_pr_event: Option<UnsignedEvent> = None; 338 let mut unsigned_pr_event: Option<UnsignedEvent> = None;
343 let mut failed_clone_urls = vec![]; 339 for clone_url in servers {
344 for clone_url in repo_grasp_clone_urls {
345 let mut draft_pr_event = if let Some(ref unsigned_pr_event) = unsigned_pr_event { 340 let mut draft_pr_event = if let Some(ref unsigned_pr_event) = unsigned_pr_event {
346 unsigned_pr_event.clone() 341 unsigned_pr_event.clone()
347 } else { 342 } else {
@@ -360,7 +355,6 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event(
360 let refspec = format!("{}:refs/nostr/{}", tip, draft_pr_event.id()); 355 let refspec = format!("{}:refs/nostr/{}", tip, draft_pr_event.id());
361 356
362 if let Err(error) = push_to_remote_url(git_repo, clone_url, &[refspec], term) { 357 if let Err(error) = push_to_remote_url(git_repo, clone_url, &[refspec], term) {
363 failed_clone_urls.push(clone_url);
364 term.write_line( 358 term.write_line(
365 format!( 359 format!(
366 "push: error sending commit data to {}: {error}", 360 "push: error sending commit data to {}: {error}",
@@ -368,7 +362,9 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event(
368 ) 362 )
369 .as_str(), 363 .as_str(),
370 )?; 364 )?;
365 responses.push((clone_url.clone(), Err(error)));
371 } else { 366 } else {
367 responses.push((clone_url.clone(), Ok(())));
372 term.write_line( 368 term.write_line(
373 format!( 369 format!(
374 "push: commit data sent to {}", 370 "push: commit data sent to {}",
@@ -379,17 +375,6 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event(
379 unsigned_pr_event = Some(draft_pr_event); 375 unsigned_pr_event = Some(draft_pr_event);
380 } 376 }
381 } 377 }
382 if unsigned_pr_event.is_none() {
383 bail!(
384 "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."
385 );
386
387 // TODO get grasp_default_set servers that aren't in repo_grasps
388 // cycle through until one succeeds TODO create
389 // personal-fork announcement with grasp servers and
390 // push, after a few seconds push ref/nostr/eventid. if
391 // one success break out of for loop and continue
392 }
393 if let Some(unsigned_pr_event) = unsigned_pr_event { 378 if let Some(unsigned_pr_event) = unsigned_pr_event {
394 let pr_event = sign_draft_event( 379 let pr_event = sign_draft_event(
395 unsigned_pr_event, 380 unsigned_pr_event,
@@ -404,21 +389,25 @@ pub async fn push_refs_and_generate_pr_or_pr_update_event(
404 .to_string(), 389 .to_string(),
405 ) 390 )
406 .await?; 391 .await?;
407 events.push(pr_event);
408 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) { 392 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) {
409 events.push( 393 Ok((
410 create_close_status_for_original_patch(signer, repo_ref, root_proposal.unwrap()) 394 Some(vec![
395 pr_event,
396 create_close_status_for_original_patch(
397 signer,
398 repo_ref,
399 root_proposal.unwrap(),
400 )
411 .await?, 401 .await?,
412 ); 402 ]),
403 responses,
404 ))
405 } else {
406 Ok((Some(vec![pr_event]), responses))
413 } 407 }
414 } else { 408 } else {
415 bail!( 409 Ok((None, responses))
416 "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"
417 );
418 // TODO suggest `ngit send` where user could specify their own clone
419 // url to push to once that feature is added
420 } 410 }
421 Ok(events)
422} 411}
423 412
424async fn create_close_status_for_original_patch( 413async fn create_close_status_for_original_patch(