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:
Diffstat (limited to 'src')
-rw-r--r--src/git_remote_helper.rs181
1 files changed, 149 insertions, 32 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
index 2333b66..06eeff5 100644
--- a/src/git_remote_helper.rs
+++ b/src/git_remote_helper.rs
@@ -33,7 +33,7 @@ use sub_commands::{
33 get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds, 33 get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds,
34 tag_value, 34 tag_value,
35 }, 35 },
36 send::{event_is_revision_root, event_to_cover_letter, send_events}, 36 send::{event_is_revision_root, event_to_cover_letter, generate_patch_event, send_events},
37}; 37};
38 38
39#[cfg(not(test))] 39#[cfg(not(test))]
@@ -489,17 +489,9 @@ async fn fetch(
489 fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/prs/")); 489 fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/prs/"));
490 490
491 for (refstr, oid) in fetch_batch { 491 for (refstr, oid) in fetch_batch {
492 if let Some((_, (_, patches))) = open_proposals.iter().find(|(_, (proposal, _))| { 492 if let Some((_, (_, patches))) =
493 if let Ok(cl) = event_to_cover_letter(proposal) { 493 find_proposal_and_patches_by_branch_name(&refstr, &open_proposals)
494 if let Ok(branch_name) = cl.get_branch_name() { 494 {
495 branch_name.eq(&refstr.replace("refs/heads/", ""))
496 } else {
497 false
498 }
499 } else {
500 false
501 }
502 }) {
503 if !git_repo.does_commit_exist(&oid)? { 495 if !git_repo.does_commit_exist(&oid)? {
504 let mut patches_ancestor_first = patches.clone(); 496 let mut patches_ancestor_first = patches.clone();
505 patches_ancestor_first.reverse(); 497 patches_ancestor_first.reverse();
@@ -526,6 +518,23 @@ async fn fetch(
526 Ok(()) 518 Ok(())
527} 519}
528 520
521fn find_proposal_and_patches_by_branch_name<'a>(
522 refstr: &'a str,
523 open_proposals: &'a HashMap<EventId, (Event, Vec<Event>)>,
524) -> Option<(&'a EventId, &'a (Event, Vec<Event>))> {
525 open_proposals.iter().find(|(_, (proposal, _))| {
526 if let Ok(cl) = event_to_cover_letter(proposal) {
527 if let Ok(branch_name) = cl.get_branch_name() {
528 branch_name.eq(&refstr.replace("refs/heads/", ""))
529 } else {
530 false
531 }
532 } else {
533 false
534 }
535 })
536}
537
529fn fetch_from_git_server( 538fn fetch_from_git_server(
530 git_repo: &Repository, 539 git_repo: &Repository,
531 oids: &[String], 540 oids: &[String],
@@ -549,7 +558,19 @@ async fn push(
549 #[cfg(not(test))] client: &Client, 558 #[cfg(not(test))] client: &Client,
550 list_outputs: Option<HashMap<String, HashMap<String, String>>>, 559 list_outputs: Option<HashMap<String, HashMap<String, String>>>,
551) -> Result<()> { 560) -> Result<()> {
552 let mut refspecs = get_refspecs_from_push_batch(stdin, initial_refspec)?; 561 let refspecs = get_refspecs_from_push_batch(stdin, initial_refspec)?;
562
563 let proposal_refspecs = refspecs
564 .iter()
565 .filter(|r| r.contains("refs/heads/prs/"))
566 .cloned()
567 .collect::<Vec<String>>();
568
569 let mut git_server_refspecs = refspecs
570 .iter()
571 .filter(|r| !r.contains("refs/heads/prs/"))
572 .cloned()
573 .collect::<Vec<String>>();
553 574
554 let term = console::Term::stderr(); 575 let term = console::Term::stderr();
555 576
@@ -581,12 +602,12 @@ async fn push(
581 let (rejected_refspecs, remote_refspecs) = create_rejected_refspecs_and_remotes_refspecs( 602 let (rejected_refspecs, remote_refspecs) = create_rejected_refspecs_and_remotes_refspecs(
582 &term, 603 &term,
583 git_repo, 604 git_repo,
584 &refspecs, 605 &git_server_refspecs,
585 &existing_state, 606 &existing_state,
586 &list_outputs, 607 &list_outputs,
587 )?; 608 )?;
588 609
589 refspecs.retain(|refspec| { 610 git_server_refspecs.retain(|refspec| {
590 if let Some(rejected) = rejected_refspecs.get(&refspec.to_string()) { 611 if let Some(rejected) = rejected_refspecs.get(&refspec.to_string()) {
591 let (_, to) = refspec_to_from_to(refspec).unwrap(); 612 let (_, to) = refspec_to_from_to(refspec).unwrap();
592 println!("error {to} {} out of sync with nostr", rejected.join(" ")); 613 println!("error {to} {} out of sync with nostr", rejected.join(" "));
@@ -596,14 +617,14 @@ async fn push(
596 } 617 }
597 }); 618 });
598 619
599 if refspecs.is_empty() { 620 let mut events = vec![];
621
622 if refspecs.is_empty() && proposal_refspecs.is_empty() {
600 // all refspecs rejected 623 // all refspecs rejected
601 println!(); 624 println!();
602 return Ok(()); 625 return Ok(());
603 } 626 }
604 627
605 let new_state = generate_updated_state(git_repo, &existing_state, &refspecs)?;
606
607 let (signer, user_ref) = login::launch( 628 let (signer, user_ref) = login::launch(
608 git_repo, 629 git_repo,
609 &None, 630 &None,
@@ -616,23 +637,106 @@ async fn push(
616 ) 637 )
617 .await?; 638 .await?;
618 639
619 let new_repo_state = RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?; 640 if !refspecs.is_empty() {
641 let new_state = generate_updated_state(git_repo, &existing_state, &git_server_refspecs)?;
642
643 let new_repo_state =
644 RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?;
645
646 events.push(new_repo_state.event);
647 }
648
649 let mut rejected_proposal_refspecs = vec![];
650 if !proposal_refspecs.is_empty() {
651 let open_proposals = get_open_proposals(git_repo, repo_ref).await?;
652
653 for refspec in &proposal_refspecs {
654 let (from, to) = refspec_to_from_to(refspec).unwrap();
655
656 if let Some((_, (proposal, patches))) =
657 find_proposal_and_patches_by_branch_name(to, &open_proposals)
658 {
659 if to.starts_with('+') {
660 // TODO do force push - issue as revision
661 } else {
662 let tip_patch = patches.first().unwrap();
663 let tip_of_proposal = get_commit_id_from_patch(tip_patch)?;
664 let tip_of_proposal_commit =
665 git_repo.get_commit_or_tip_of_reference(&tip_of_proposal)?;
666 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?;
667 let (ahead, behind) = git_repo
668 .get_commits_ahead_behind(&tip_of_proposal_commit, &tip_of_pushed_branch)?;
669 if behind.is_empty() {
670 let thread_id = if patches.len().eq(&1) {
671 tip_patch.id()
672 } else {
673 get_event_root(tip_patch)?
674 };
675 // TODO do I have permission?
676 let mut parent_patch = tip_patch.clone();
677 for (i, commit) in ahead.iter().enumerate() {
678 let new_patch = generate_patch_event(
679 git_repo,
680 &git_repo.get_root_commit()?,
681 commit,
682 Some(thread_id),
683 &signer,
684 repo_ref,
685 Some(parent_patch.id()),
686 Some((
687 (patches.len() + i + 1).try_into().unwrap(),
688 (patches.len() + ahead.len()).try_into().unwrap(),
689 )),
690 None,
691 &None,
692 &[],
693 )
694 .await
695 .context("cannot make patch event from commit")?;
696 events.push(new_patch.clone());
697 parent_patch = new_patch;
698 }
699 } else {
700 // we shouldn't get here
701 term.write_line(
702 format!(
703 "WARNING: failed to push {from} as nostr proposal. Try and force push ",
704 )
705 .as_str(),
706 )
707 .unwrap();
708 println!(
709 "error {to} cannot fastforward as newer patches found on proposal"
710 );
711 rejected_proposal_refspecs.push(refspec.to_string());
712 }
713 }
714 } else {
715 // TODO new proposal / proposal no longeer open
716 // / we couldn't
717 }
718 }
719 }
620 720
621 // TODO check whether tip of each branch pushed is on at least one git server 721 // TODO check whether tip of each branch pushed is on at least one git server
622 // before broadcasting the nostr state 722 // before broadcasting the nostr state
723 if !events.is_empty() {
724 send_events(
725 client,
726 git_repo.get_path()?,
727 events,
728 user_ref.relays.write(),
729 repo_ref.relays.clone(),
730 false,
731 true,
732 )
733 .await?;
734 }
623 735
624 send_events( 736 for refspec in &[git_server_refspecs.clone(), proposal_refspecs.clone()].concat() {
625 client, 737 if rejected_proposal_refspecs.contains(refspec) {
626 git_repo.get_path()?, 738 continue;
627 vec![new_repo_state.event], 739 }
628 user_ref.relays.write(),
629 repo_ref.relays.clone(),
630 false,
631 true,
632 )
633 .await?;
634
635 for refspec in &refspecs {
636 let (_, to) = refspec_to_from_to(refspec)?; 740 let (_, to) = refspec_to_from_to(refspec)?;
637 println!("ok {to}"); 741 println!("ok {to}");
638 update_remote_refs_pushed(&git_repo.git_repo, refspec, nostr_remote_url) 742 update_remote_refs_pushed(&git_repo.git_repo, refspec, nostr_remote_url)
@@ -644,7 +748,7 @@ async fn push(
644 for (git_server_url, remote_refspecs) in remote_refspecs { 748 for (git_server_url, remote_refspecs) in remote_refspecs {
645 let remote_refspecs = remote_refspecs 749 let remote_refspecs = remote_refspecs
646 .iter() 750 .iter()
647 .filter(|refspec| refspecs.contains(refspec)) 751 .filter(|refspec| git_server_refspecs.contains(refspec))
648 .cloned() 752 .cloned()
649 .collect::<Vec<String>>(); 753 .collect::<Vec<String>>();
650 if !refspecs.is_empty() { 754 if !refspecs.is_empty() {
@@ -676,6 +780,19 @@ async fn push(
676 Ok(()) 780 Ok(())
677} 781}
678 782
783fn get_event_root(event: &nostr::Event) -> Result<EventId> {
784 Ok(EventId::parse(
785 event
786 .tags()
787 .iter()
788 .find(|t| t.is_root())
789 .context("no thread root in event")?
790 .as_vec()
791 .get(1)
792 .unwrap(),
793 )?)
794}
795
679type HashMapUrlRefspecs = HashMap<String, Vec<String>>; 796type HashMapUrlRefspecs = HashMap<String, Vec<String>>;
680 797
681#[allow(clippy::too_many_lines)] 798#[allow(clippy::too_many_lines)]