diff options
Diffstat (limited to 'src/git_remote_helper.rs')
| -rw-r--r-- | src/git_remote_helper.rs | 181 |
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 | ||
| 521 | fn 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 | |||
| 529 | fn fetch_from_git_server( | 538 | fn 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 | ||
| 783 | fn 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 | |||
| 679 | type HashMapUrlRefspecs = HashMap<String, Vec<String>>; | 796 | type HashMapUrlRefspecs = HashMap<String, Vec<String>>; |
| 680 | 797 | ||
| 681 | #[allow(clippy::too_many_lines)] | 798 | #[allow(clippy::too_many_lines)] |