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-04 08:50:54 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2025-08-04 08:50:54 +0100
commitf76fe63da5f2c2f85215e86c8ecc63eda7c93902 (patch)
tree8afbad088dcc48e0d4b8517544ffab6af87546f4 /src
parent3d04fb224b68187a67b9db0a37f662b5c5382f1e (diff)
refactor: move generate pr event fn into lib
for future use in `ngit send`
Diffstat (limited to 'src')
-rw-r--r--src/bin/git_remote_nostr/push.rs164
-rw-r--r--src/lib/push.rs168
2 files changed, 172 insertions, 160 deletions
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index 3478608..e588a5a 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -10,25 +10,24 @@ use client::{get_events_from_local_cache, get_state_from_cache, send_events, sig
10use console::Term; 10use console::Term;
11use git::{RepoActions, sha1_to_oid}; 11use git::{RepoActions, sha1_to_oid};
12use git_events::{ 12use git_events::{
13 generate_cover_letter_and_patch_events, generate_patch_event, 13 generate_cover_letter_and_patch_events, generate_patch_event, get_commit_id_from_patch,
14 generate_unsigned_pr_or_update_event, get_commit_id_from_patch,
15}; 14};
16use git2::{Oid, Repository}; 15use git2::{Oid, Repository};
17use ngit::{ 16use ngit::{
18 client::{self, get_event_from_cache_by_id, sign_draft_event}, 17 client::{self, get_event_from_cache_by_id},
19 git::{self, nostr_url::NostrUrlDecoded}, 18 git::{self, nostr_url::NostrUrlDecoded},
20 git_events::{self, KIND_PULL_REQUEST, event_to_cover_letter, get_event_root}, 19 git_events::{self, KIND_PULL_REQUEST, event_to_cover_letter, get_event_root},
21 list::list_from_remotes, 20 list::list_from_remotes,
22 login::{self, user::UserRef}, 21 login::{self, user::UserRef},
23 push::{push_to_remote, push_to_remote_url}, 22 push::{push_refs_and_generate_pr_or_pr_update_event, push_to_remote},
24 repo_ref::{self, get_repo_config_from_yaml, is_grasp_server, normalize_grasp_server_url}, 23 repo_ref::{self, get_repo_config_from_yaml, is_grasp_server},
25 repo_state, 24 repo_state,
26 utils::{ 25 utils::{
27 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, 26 find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url,
28 get_short_git_server_name, read_line, 27 get_short_git_server_name, read_line,
29 }, 28 },
30}; 29};
31use nostr::{event::UnsignedEvent, nips::nip10::Marker}; 30use nostr::nips::nip10::Marker;
32use nostr_sdk::{ 31use nostr_sdk::{
33 Event, EventBuilder, EventId, Kind, NostrSigner, PublicKey, RelayUrl, Tag, TagStandard, 32 Event, EventBuilder, EventId, Kind, NostrSigner, PublicKey, RelayUrl, Tag, TagStandard,
34 hashes::sha1::Hash as Sha1Hash, 33 hashes::sha1::Hash as Sha1Hash,
@@ -494,103 +493,6 @@ async fn generate_patches_or_pr_event_or_pr_updates(
494 Ok(events) 493 Ok(events)
495} 494}
496 495
497async fn push_refs_and_generate_pr_or_pr_update_event(
498 git_repo: &Repo,
499 repo_ref: &RepoRef,
500 tip: &Sha1Hash,
501 user_ref: &UserRef,
502 root_proposal: Option<&Event>,
503 signer: &Arc<dyn NostrSigner>,
504 term: &Term,
505) -> Result<Vec<Event>> {
506 let mut events: Vec<Event> = vec![];
507 let repo_grasps = repo_ref.grasp_servers();
508 let repo_grasp_clone_urls = repo_ref
509 .git_server
510 .iter()
511 .filter(|s| is_grasp_server(s, &repo_grasps));
512
513 let mut unsigned_pr_event: Option<UnsignedEvent> = None;
514 let mut failed_clone_urls = vec![];
515 for clone_url in repo_grasp_clone_urls {
516 let mut draft_pr_event = if let Some(ref unsigned_pr_event) = unsigned_pr_event {
517 unsigned_pr_event.clone()
518 } else {
519 generate_unsigned_pr_or_update_event(
520 git_repo,
521 repo_ref,
522 &user_ref.public_key,
523 root_proposal,
524 tip,
525 &[clone_url],
526 &[],
527 )?
528 };
529
530 let refspec = format!("{}:refs/nostr/{}", tip, draft_pr_event.id());
531
532 if let Err(error) = push_to_remote_url(git_repo, clone_url, &[refspec], term) {
533 failed_clone_urls.push(clone_url);
534 term.write_line(
535 format!(
536 "push: error sending commit data to {}: {error}",
537 normalize_grasp_server_url(clone_url)?
538 )
539 .as_str(),
540 )?;
541 } else {
542 term.write_line(
543 format!(
544 "push: commit data sent to {}",
545 normalize_grasp_server_url(clone_url)?
546 )
547 .as_str(),
548 )?;
549 unsigned_pr_event = Some(draft_pr_event);
550 }
551 }
552 if unsigned_pr_event.is_none() {
553 bail!(
554 "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."
555 );
556
557 // TODO get grasp_default_set servers that aren't in repo_grasps
558 // cycle through until one succeeds TODO create
559 // personal-fork announcement with grasp servers and
560 // push, after a few seconds push ref/nostr/eventid. if
561 // one success break out of for loop and continue
562 }
563 if let Some(unsigned_pr_event) = unsigned_pr_event {
564 let pr_event = sign_draft_event(
565 unsigned_pr_event,
566 signer,
567 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) {
568 "Pull Request Replacing Original Patch"
569 } else if root_proposal.is_some() {
570 "Pull Request Update"
571 } else {
572 "Pull Request"
573 }
574 .to_string(),
575 )
576 .await?;
577 events.push(pr_event);
578 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) {
579 events.push(
580 create_close_status_for_original_patch(signer, repo_ref, root_proposal.unwrap())
581 .await?,
582 );
583 }
584 } else {
585 bail!(
586 "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"
587 );
588 // TODO suggest `ngit send` where user could specify their own clone
589 // url to push to once that feature is added
590 }
591 Ok(events)
592}
593
594type HashMapUrlRefspecs = HashMap<String, Vec<String>>; 496type HashMapUrlRefspecs = HashMap<String, Vec<String>>;
595 497
596#[allow(clippy::too_many_lines)] 498#[allow(clippy::too_many_lines)]
@@ -1283,62 +1185,6 @@ async fn create_merge_status(
1283 .await 1185 .await
1284} 1186}
1285 1187
1286async fn create_close_status_for_original_patch(
1287 signer: &Arc<dyn NostrSigner>,
1288 repo_ref: &RepoRef,
1289 proposal: &Event,
1290) -> Result<Event> {
1291 let mut public_keys = repo_ref
1292 .maintainers
1293 .iter()
1294 .copied()
1295 .collect::<HashSet<PublicKey>>();
1296 public_keys.insert(proposal.pubkey);
1297
1298 sign_event(
1299 EventBuilder::new(nostr::event::Kind::GitStatusClosed, String::new()).tags(
1300 [
1301 vec![
1302 Tag::custom(
1303 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")),
1304 vec![
1305 "Git patch closed as forthcoming update is too large. Replacing with Pull Request"
1306 .to_string(),
1307 ],
1308 ),
1309 Tag::from_standardized(nostr::TagStandard::Event {
1310 event_id: proposal.id,
1311 relay_url: repo_ref.relays.first().cloned(),
1312 marker: Some(Marker::Root),
1313 public_key: None,
1314 uppercase: false,
1315 }),
1316 ],
1317 public_keys.iter().map(|pk| Tag::public_key(*pk)).collect(),
1318 repo_ref
1319 .coordinates()
1320 .iter()
1321 .map(|c| {
1322 Tag::from_standardized(TagStandard::Coordinate {
1323 coordinate: c.coordinate.clone(),
1324 relay_url: c.relays.first().cloned(),
1325 uppercase: false,
1326 })
1327 })
1328 .collect::<Vec<Tag>>(),
1329 vec![
1330 Tag::from_standardized(nostr::TagStandard::Reference(
1331 repo_ref.root_commit.to_string(),
1332 )),
1333 ],
1334 ]
1335 .concat(),
1336 ),
1337 signer,
1338 "close status for original patch".to_string(),
1339 )
1340 .await
1341}
1342async fn get_proposal_and_revision_root_from_patch( 1188async fn get_proposal_and_revision_root_from_patch(
1343 git_repo: &Repo, 1189 git_repo: &Repo,
1344 patch: &Event, 1190 patch: &Event,
diff --git a/src/lib/push.rs b/src/lib/push.rs
index 0d0ec93..1c09555 100644
--- a/src/lib/push.rs
+++ b/src/lib/push.rs
@@ -1,19 +1,31 @@
1use std::{ 1use std::{
2 collections::HashSet,
2 sync::{Arc, Mutex}, 3 sync::{Arc, Mutex},
3 time::Instant, 4 time::Instant,
4}; 5};
5 6
6use anyhow::{Result, anyhow}; 7use anyhow::{Result, anyhow, bail};
7use auth_git2::GitAuthenticator; 8use auth_git2::GitAuthenticator;
8use console::Term; 9use console::Term;
10use nostr::{
11 event::{Event, EventBuilder, Kind, Tag, TagStandard, UnsignedEvent},
12 hashes::sha1::Hash as Sha1Hash,
13 key::PublicKey,
14 nips::nip10::Marker,
15 signer::NostrSigner,
16};
9 17
10use crate::{ 18use crate::{
11 cli_interactor::count_lines_per_msg_vec, 19 cli_interactor::count_lines_per_msg_vec,
20 client::{sign_draft_event, sign_event},
12 git::{ 21 git::{
13 Repo, 22 Repo,
14 nostr_url::{CloneUrl, NostrUrlDecoded}, 23 nostr_url::{CloneUrl, NostrUrlDecoded},
15 oid_to_shorthand_string, 24 oid_to_shorthand_string,
16 }, 25 },
26 git_events::generate_unsigned_pr_or_update_event,
27 login::user::UserRef,
28 repo_ref::{RepoRef, is_grasp_server, normalize_grasp_server_url},
17 utils::{ 29 utils::{
18 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,
19 set_protocol_preference, 31 set_protocol_preference,
@@ -308,3 +320,157 @@ impl<'a> PushReporter<'a> {
308 } 320 }
309 } 321 }
310} 322}
323
324pub async fn push_refs_and_generate_pr_or_pr_update_event(
325 git_repo: &Repo,
326 repo_ref: &RepoRef,
327 tip: &Sha1Hash,
328 user_ref: &UserRef,
329 root_proposal: Option<&Event>,
330 signer: &Arc<dyn NostrSigner>,
331 term: &Term,
332) -> Result<Vec<Event>> {
333 let mut events: Vec<Event> = vec![];
334 let repo_grasps = repo_ref.grasp_servers();
335 let repo_grasp_clone_urls = repo_ref
336 .git_server
337 .iter()
338 .filter(|s| is_grasp_server(s, &repo_grasps));
339
340 let mut unsigned_pr_event: Option<UnsignedEvent> = None;
341 let mut failed_clone_urls = vec![];
342 for clone_url in repo_grasp_clone_urls {
343 let mut draft_pr_event = if let Some(ref unsigned_pr_event) = unsigned_pr_event {
344 unsigned_pr_event.clone()
345 } else {
346 generate_unsigned_pr_or_update_event(
347 git_repo,
348 repo_ref,
349 &user_ref.public_key,
350 root_proposal,
351 tip,
352 &[clone_url],
353 &[],
354 )?
355 };
356
357 let refspec = format!("{}:refs/nostr/{}", tip, draft_pr_event.id());
358
359 if let Err(error) = push_to_remote_url(git_repo, clone_url, &[refspec], term) {
360 failed_clone_urls.push(clone_url);
361 term.write_line(
362 format!(
363 "push: error sending commit data to {}: {error}",
364 normalize_grasp_server_url(clone_url)?
365 )
366 .as_str(),
367 )?;
368 } else {
369 term.write_line(
370 format!(
371 "push: commit data sent to {}",
372 normalize_grasp_server_url(clone_url)?
373 )
374 .as_str(),
375 )?;
376 unsigned_pr_event = Some(draft_pr_event);
377 }
378 }
379 if unsigned_pr_event.is_none() {
380 bail!(
381 "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."
382 );
383
384 // TODO get grasp_default_set servers that aren't in repo_grasps
385 // cycle through until one succeeds TODO create
386 // personal-fork announcement with grasp servers and
387 // push, after a few seconds push ref/nostr/eventid. if
388 // one success break out of for loop and continue
389 }
390 if let Some(unsigned_pr_event) = unsigned_pr_event {
391 let pr_event = sign_draft_event(
392 unsigned_pr_event,
393 signer,
394 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) {
395 "Pull Request Replacing Original Patch"
396 } else if root_proposal.is_some() {
397 "Pull Request Update"
398 } else {
399 "Pull Request"
400 }
401 .to_string(),
402 )
403 .await?;
404 events.push(pr_event);
405 if root_proposal.is_some_and(|proposal| proposal.kind.eq(&Kind::GitPatch)) {
406 events.push(
407 create_close_status_for_original_patch(signer, repo_ref, root_proposal.unwrap())
408 .await?,
409 );
410 }
411 } else {
412 bail!(
413 "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"
414 );
415 // TODO suggest `ngit send` where user could specify their own clone
416 // url to push to once that feature is added
417 }
418 Ok(events)
419}
420
421async fn create_close_status_for_original_patch(
422 signer: &Arc<dyn NostrSigner>,
423 repo_ref: &RepoRef,
424 proposal: &Event,
425) -> Result<Event> {
426 let mut public_keys = repo_ref
427 .maintainers
428 .iter()
429 .copied()
430 .collect::<HashSet<PublicKey>>();
431 public_keys.insert(proposal.pubkey);
432
433 sign_event(
434 EventBuilder::new(nostr::event::Kind::GitStatusClosed, String::new()).tags(
435 [
436 vec![
437 Tag::custom(
438 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")),
439 vec![
440 "Git patch closed as forthcoming update is too large. Replacing with Pull Request"
441 .to_string(),
442 ],
443 ),
444 Tag::from_standardized(nostr::TagStandard::Event {
445 event_id: proposal.id,
446 relay_url: repo_ref.relays.first().cloned(),
447 marker: Some(Marker::Root),
448 public_key: None,
449 uppercase: false,
450 }),
451 ],
452 public_keys.iter().map(|pk| Tag::public_key(*pk)).collect(),
453 repo_ref
454 .coordinates()
455 .iter()
456 .map(|c| {
457 Tag::from_standardized(TagStandard::Coordinate {
458 coordinate: c.coordinate.clone(),
459 relay_url: c.relays.first().cloned(),
460 uppercase: false,
461 })
462 })
463 .collect::<Vec<Tag>>(),
464 vec![
465 Tag::from_standardized(nostr::TagStandard::Reference(
466 repo_ref.root_commit.to_string(),
467 )),
468 ],
469 ]
470 .concat(),
471 ),
472 signer,
473 "close status for original patch".to_string(),
474 )
475 .await
476}