From f1aa1be738af0dc80eb3b5827249bb537de1e0cd Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 6 Aug 2024 10:12:26 +0100 Subject: feat(remote): `list` includes open proposals and filters out other branches in `prs/*` namespace --- src/git_remote_helper.rs | 100 ++++++++++++++++++++++++++++++++++++++++++--- test_utils/src/lib.rs | 16 ++++++++ tests/git_remote_helper.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 5 deletions(-) diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index a930617..f851c90 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs @@ -15,17 +15,25 @@ use std::{ use anyhow::{bail, Context, Result}; use auth_git2::GitAuthenticator; use client::{ - consolidate_fetch_reports, get_repo_ref_from_cache, get_state_from_cache, sign_event, Connect, - STATE_KIND, + consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache, + get_state_from_cache, sign_event, Connect, STATE_KIND, }; use git::RepoActions; use git2::{Oid, Repository}; use nostr::nips::nip01::Coordinate; -use nostr_sdk::{hashes::sha1::Hash as Sha1Hash, EventBuilder, PublicKey, Tag, Url}; +use nostr_sdk::{ + hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag, Url, +}; use nostr_signer::NostrSigner; use repo_ref::RepoRef; use repo_state::RepoState; -use sub_commands::send::send_events; +use sub_commands::{ + list::{ + get_all_proposal_patch_events_from_cache, get_commit_id_from_patch, + get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds, + }, + send::{event_is_revision_root, event_to_cover_letter, send_events}, +}; #[cfg(not(test))] use crate::client::Client; @@ -231,7 +239,7 @@ async fn list( let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server)?; - let state = if let Some(nostr_state) = nostr_state { + let mut state = if let Some(nostr_state) = nostr_state { for (name, value) in &nostr_state.state { for (url, remote_state) in &remote_states { let remote_name = get_short_git_server_name(git_repo, url); @@ -272,6 +280,27 @@ async fn list( .clone() }; + state.retain(|k, _| !k.starts_with("refs/heads/prs/")); + + let open_proposals = get_open_proposals(git_repo, repo_ref).await?; + + for (_, (proposal, patches)) in open_proposals { + if let Ok(cl) = event_to_cover_letter(&proposal) { + if let Ok(branch_name) = cl.get_branch_name() { + if let Some(patch) = patches.first() { + // TODO this isn't resilient because the commit id stated may not be correct + // we will need to check whether the commit id exists in the repo or apply the + // proposal and each patch to check + if let Ok(commit_id) = get_commit_id_from_patch(patch) { + state.insert(branch_name, commit_id); + } + } + } + } + } + + // TODO 'for push' should we check with the git servers to see if any of them + // allow push from the user? for (name, value) in state { if value.starts_with("ref: ") { if !for_push { @@ -341,6 +370,67 @@ fn get_ahead_behind( git_repo.get_commits_ahead_behind(&base, &latest) } +async fn get_open_proposals( + git_repo: &Repo, + repo_ref: &RepoRef, +) -> Result)>> { + let git_repo_path = git_repo.get_path()?; + let proposals: Vec = + get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()) + .await? + .iter() + .filter(|e| !event_is_revision_root(e)) + .cloned() + .collect(); + + let statuses: Vec = { + let mut statuses = get_events_from_cache( + git_repo_path, + vec![ + nostr::Filter::default() + .kinds(status_kinds().clone()) + .events(proposals.iter().map(nostr::Event::id)), + ], + ) + .await?; + statuses.sort_by_key(|e| e.created_at); + statuses.reverse(); + statuses + }; + let mut open_proposals = HashMap::new(); + + for proposal in proposals { + let status = if let Some(e) = statuses + .iter() + .filter(|e| { + status_kinds().contains(&e.kind()) + && e.iter_tags() + .any(|t| t.as_vec()[1].eq(&proposal.id.to_string())) + }) + .collect::>() + .first() + { + e.kind() + } else { + Kind::GitStatusOpen + }; + if status.eq(&Kind::GitStatusOpen) { + if let Ok(commits_events) = + get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id) + .await + { + if let Ok(most_recent_proposal_patch_chain) = + get_most_recent_patch_with_ancestors(commits_events.clone()) + { + open_proposals + .insert(proposal.id(), (proposal, most_recent_proposal_patch_chain)); + } + } + } + } + Ok(open_proposals) +} + fn fetch(git_repo: &Repository, repo_ref: &RepoRef, stdin: &Stdin, oid: &str) -> Result<()> { let oids = get_oids_from_fetch_batch(stdin, oid)?; diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index f463652..ff3833d 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1020,6 +1020,13 @@ pub fn get_proposal_branch_name( .hashtag("root"), ], ))?; + get_proposal_branch_name_from_events(&events, branch_name_in_event) +} + +pub fn get_proposal_branch_name_from_events( + events: &Vec, + branch_name_in_event: &str, +) -> Result { for event in events { if event.iter_tags().any(|t| { !t.as_vec()[1].eq("revision-root") @@ -1075,6 +1082,15 @@ pub fn cli_tester_create_proposals() -> Result { Ok(git_repo) } +pub fn cli_tester_create_proposal_branches_ready_to_send() -> Result { + let git_repo = GitTestRepo::default(); + git_repo.populate()?; + create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_1, "a", false)?; + create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_2, "b", false)?; + create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_3, "c", false)?; + Ok(git_repo) +} + pub fn create_and_populate_branch( test_repo: &GitTestRepo, branch_name: &str, diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs index 4a60e63..77d7ecb 100644 --- a/tests/git_remote_helper.rs +++ b/tests/git_remote_helper.rs @@ -447,6 +447,106 @@ mod list { Ok(()) } } + + mod when_there_are_open_proposals { + + use super::*; + + #[tokio::test] + #[serial] + async fn open_proposal_listed_in_prs_namespace() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let source_path = source_git_repo.dir.to_str().unwrap().to_string(); + + let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + let example_commit_id = + source_git_repo.get_tip_of_local_branch("example-branch")?; + + let git_repo = prep_git_repo()?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server(vec![ + source_git_repo.dir.to_str().unwrap().to_string(), + ]), + state_event, + ]; + // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) + let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( + Relay::new(8051, None, None), + Relay::new(8052, None, None), + Relay::new(8053, None, None), + Relay::new(8055, None, None), + Relay::new(8056, None, None), + Relay::new(8057, None, None), + ); + r51.events = events.clone(); + r55.events = events; + + let cli_tester_handle = std::thread::spawn(move || -> Result { + cli_tester_create_proposals()?; + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + p.expect(format!("fetching refs list: {}...\r\n\r", source_path).as_str())?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + let res = p.expect_eventually("\r\n\r\n")?; + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(res) + }); + // launch relays + let _ = join!( + r51.listen_until_close(), + r52.listen_until_close(), + r53.listen_until_close(), + r55.listen_until_close(), + r56.listen_until_close(), + r57.listen_until_close(), + ); + + let res = cli_tester_handle.join().unwrap()?; + + let proposal_creation_repo = cli_tester_create_proposal_branches_ready_to_send()?; + + let mut pr_refs = vec![]; + for name in [ + FEATURE_BRANCH_NAME_1, + FEATURE_BRANCH_NAME_2, + FEATURE_BRANCH_NAME_3, + ] { + pr_refs.push(format!( + "{} {}", + proposal_creation_repo.get_tip_of_local_branch(name)?, + get_proposal_branch_name_from_events(&r55.events, name)?, + )); + } + + assert_eq!( + res.split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + [ + vec![ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ], + pr_refs, + ] + .concat() + .iter() + .cloned() + .collect::>() + ); + + Ok(()) + } + } } } -- cgit v1.2.3