From 1180f0b3c3157b6fd8eb7bf08a255a114e961e5f Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 31 Jul 2024 17:14:25 +0100 Subject: fix(remote): updating `push` state event ensure refs are included in state event use `HashMap` to improve `RepoState` struct --- src/git_remote_helper.rs | 40 ++++------- src/repo_state.rs | 10 +-- tests/git_remote_helper.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 33 deletions(-) diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index 68aa681..08a59c1 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs @@ -6,7 +6,7 @@ use core::str; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, env, io::{self, Stdin}, path::PathBuf, @@ -261,41 +261,25 @@ async fn generate_updated_state( git_repo: &Repo, repo_ref: &RepoRef, refspecs: &Vec, -) -> Result> { +) -> Result> { let new_state = { if let Ok(mut repo_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await { for refspec in refspecs { let (from, to) = refspec_to_from_to(refspec)?; - if to.is_empty() { + if from.is_empty() { // delete - repo_state.state.retain(|(name, _)| !name.eq(to)); - } else if repo_state.state.iter().any(|(name, _)| name.eq(from)) { - // update - repo_state.state = repo_state - .state - .iter() - .map(|(name, value)| { - ( - name.clone(), - if name.eq(to) { - reference_to_ref_value(&git_repo.git_repo, to).unwrap() - } else { - value.to_string() - }, - ) - }) - .collect(); + repo_state.state.remove(to); } else { - // add - repo_state.state.push(( + // add or update + repo_state.state.insert( to.to_string(), reference_to_ref_value(&git_repo.git_repo, to).unwrap(), - )); + ); } } repo_state.state } else { - let mut state = vec![]; + let mut state = HashMap::new(); let git_server_url = repo_ref .git_server .first() @@ -303,14 +287,14 @@ async fn generate_updated_state( let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?; git_server_remote.connect(git2::Direction::Fetch)?; for head in git_server_remote.list()? { - state.push(( + state.insert( head.name().to_string(), if let Some(symbolic_ref) = head.symref_target() { - format!("ref: {}", symbolic_ref) + format!("ref: {symbolic_ref}") } else { head.oid().to_string() }, - )); + ); } git_server_remote.disconnect()?; state @@ -460,7 +444,7 @@ fn get_refspecs_from_push_batch(stdin: &Stdin, initial_refspec: &str) -> Result< impl RepoState { pub async fn build( identifier: String, - state: Vec<(String, String)>, + state: HashMap, signer: &NostrSigner, ) -> Result { let mut tags = vec![Tag::identifier(identifier.clone())]; diff --git a/src/repo_state.rs b/src/repo_state.rs index 0c1aa30..a5cebab 100644 --- a/src/repo_state.rs +++ b/src/repo_state.rs @@ -1,9 +1,11 @@ +use std::collections::HashMap; + use anyhow::{Context, Result}; use git2::Oid; pub struct RepoState { pub identifier: String, - pub state: Vec<(String, String)>, + pub state: HashMap, pub event: nostr::Event, } @@ -11,7 +13,7 @@ impl RepoState { pub fn try_from(mut state_events: Vec) -> Result { state_events.sort_by_key(|e| e.created_at); let event = state_events.first().context("no state events")?; - let mut state = vec![]; + let mut state = HashMap::new(); for tag in &event.tags { if let Some(name) = tag.as_vec().first() { if ["refs/heads/", "refs/tags", "HEAD"] @@ -19,8 +21,8 @@ impl RepoState { .any(|s| name.starts_with(*s)) { if let Some(value) = tag.as_vec().get(1) { - if Oid::from_str(value).is_ok() { - state.push((name.to_owned(), value.to_owned())); + if Oid::from_str(value).is_ok() || value.contains("ref: refs/") { + state.insert(name.to_owned(), value.to_owned()); } } } diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs index ea76445..fffcaa1 100644 --- a/tests/git_remote_helper.rs +++ b/tests/git_remote_helper.rs @@ -51,6 +51,77 @@ fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result { Ok(p) } +async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { + let git_repo = prep_git_repo()?; + let commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); + let source_git_repo = GitTestRepo::recreate_as_bare(&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(source_git_repo.dir.to_str().unwrap()), + ]; + // 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<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("")?; + p.expect("ok refs/heads/main\r\n")?; + p.expect("\r\n")?; + + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // 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(), + ); + cli_tester_handle.join().unwrap()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), commit_id,], + ]), + ); + + // wait for bigger timestamp + std::thread::sleep(std::time::Duration::from_millis(1000)); + + Ok((state_event.clone(), source_git_repo)) +} + mod initially_runs_fetch { use super::*; @@ -523,7 +594,103 @@ mod push { #[tokio::test] #[serial] - async fn sate_event_reflects_git_server_state() -> Result<()> { + async fn state_event_reflects_git_server_state() -> Result<()> { + async_run_test().await + } + } + } + mod existing_state_event { + use super::*; + + mod state_on_git_server_published_in_nostr_state_event { + + use super::*; + + async fn async_run_test() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + + let git_repo = prep_git_repo()?; + + std::fs::write(git_repo.dir.join("new.md"), "some content")?; + let main_commit_id = git_repo.stage_and_commit("new.md")?; + git_repo.create_branch("vnext")?; + git_repo.checkout("vnext")?; + std::fs::write(git_repo.dir.join("more.md"), "some content")?; + let vnext_commit_id = git_repo.stage_and_commit("more.md")?; + + let events = vec![ + generate_test_key_1_metadata_event("fred"), + generate_test_key_1_relay_list_event(), + generate_repo_ref_event_with_git_server( + source_git_repo.dir.to_str().unwrap(), + ), + state_event.clone(), + ]; + + // 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<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("push refs/heads/main:refs/heads/main")?; + p.send_line("push refs/heads/vnext:refs/heads/vnext")?; + p.send_line("")?; + p.expect("ok refs/heads/main\r\n")?; + p.expect("ok refs/heads/vnext\r\n")?; + p.expect("\r\n")?; + p.exit()?; + for p in [51, 52, 53, 55, 56, 57] { + relay::shutdown_relay(8000 + p)?; + } + Ok(()) + }); + // 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(), + ); + + cli_tester_handle.join().unwrap()?; + + let state_event = r56 + .events + .iter() + .find(|e| e.kind().eq(&STATE_KIND)) + .context("state event not created")?; + + // println!("{:#?}", state_event); + assert_eq!( + state_event + .tags + .iter() + .filter(|t| t.kind().to_string().as_str().ne("d")) + .map(|t| t.as_vec().to_vec()) + .collect::>>(), + HashSet::from([ + vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], + vec!["refs/heads/main".to_string(), main_commit_id.to_string()], + vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()], + ]), + ); + Ok(()) + } + + #[tokio::test] + #[serial] + async fn state_event_reflects_updated_with_pushed_changes() -> Result<()> { async_run_test().await } } -- cgit v1.2.3