upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/push.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/push.rs')
-rw-r--r--src/lib/push.rs168
1 files changed, 167 insertions, 1 deletions
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}