From c494242fbf7308e50e38ed7b4750a4fe2638ed18 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 1 Aug 2024 09:39:08 +0100 Subject: feat(remote): `fetch` uses state event and falls back to git server is state event cant be found --- src/git_remote_helper.rs | 43 +++++-- test_utils/src/git.rs | 9 ++ tests/git_remote_helper.rs | 315 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 291 insertions(+), 76 deletions(-) diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index 08a59c1..38a0aa5 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs @@ -108,10 +108,10 @@ async fn main() -> Result<()> { .await?; } ["list"] => { - list(&git_repo.git_repo, &repo_ref, false)?; + list(&git_repo, &repo_ref, false).await?; } ["list", "for-push"] => { - list(&git_repo.git_repo, &repo_ref, true)?; + list(&git_repo, &repo_ref, true).await?; } [] => { return Ok(()); @@ -148,19 +148,36 @@ fn nostr_git_url_to_repo_coordinates(url: &str) -> Result> { Ok(repo_coordinattes) } -fn list(git_repo: &Repository, repo_ref: &RepoRef, for_push: bool) -> Result<()> { - let git_server_remote_url = repo_ref - .git_server - .first() - .context("no git server listed in nostr repository announcement")?; - let mut git_server_remote = git_repo.remote_anonymous(git_server_remote_url)?; - git_server_remote.connect(git2::Direction::Fetch)?; - for head in git_server_remote.list()? { - if !for_push || head.name() != "HEAD" { - println!("{} {}", head.oid(), head.name()); +async fn list(git_repo: &Repo, repo_ref: &RepoRef, for_push: bool) -> Result<()> { + if let Ok(repo_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await { + for (name, value) in &repo_state.state { + if value.starts_with("ref: ") { + if !for_push { + println!("{} {name}", value.replace("ref: ", "@")); + } + } else { + println!("{value} {name}"); + } } + } else { + let git_server_remote_url = repo_ref + .git_server + .first() + .context("no git server listed in nostr repository announcement")?; + let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_remote_url)?; + git_server_remote.connect(git2::Direction::Fetch)?; + + for head in git_server_remote.list()? { + if let Some(symbolic_reference) = head.symref_target() { + if !for_push { + println!("@{} {}", symbolic_reference, head.name()); + } + } else { + println!("{} {}", head.oid(), head.name()); + } + } + git_server_remote.disconnect()?; } - git_server_remote.disconnect()?; println!(); Ok(()) } diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs index 7dfa62b..4a4aaa8 100644 --- a/test_utils/src/git.rs +++ b/test_utils/src/git.rs @@ -123,6 +123,15 @@ impl GitTestRepo { }) } + pub fn clone_repo(existing_repo: &GitTestRepo) -> Result { + let path = current_dir()?.join(format!("tmpgit-{}", rand::random::())); + let git_repo = git2::Repository::clone(existing_repo.dir.to_str().unwrap(), path.clone())?; + Ok(Self { + dir: path, + git_repo, + }) + } + pub fn initial_commit(&self) -> Result { let oid = self.git_repo.index()?.write_tree()?; let tree = self.git_repo.find_tree(oid)?; diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs index 1ca4296..20ecad7 100644 --- a/tests/git_remote_helper.rs +++ b/tests/git_remote_helper.rs @@ -54,7 +54,6 @@ fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result { async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { let git_repo = prep_git_repo()?; git_repo.create_branch("example-branch")?; - let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // TODO recreate_as_bare isn't creating other branches let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?; @@ -185,74 +184,234 @@ mod list { use super::*; - async fn async_run_test() -> Result<()> { - let source_git_repo = prep_git_repo()?; - std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; - let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; - - source_git_repo.create_branch("vnext")?; - source_git_repo.checkout("vnext")?; - std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; - let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; - - let head_commit_id = source_git_repo.checkout("main")?; + mod without_state_announcement { - 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(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; + use super::*; - let cli_tester_handle = std::thread::spawn(move || -> Result<()> { - let mut p = cli_tester_after_fetch(&git_repo)?; - p.send_line("list")?; - // println!("{}", p.expect_eventually("\r\n\r\n")?); - assert_eq!( - p.expect_eventually("\r\n\r\n")? - .split("\r\n") - .map(|e| e.to_string()) - .collect::>(), - HashSet::from([ - format!("{} HEAD", head_commit_id), - format!("{} refs/heads/main", main_commit_id), - format!("{} refs/heads/vnext", vnext_commit_id), - ]), + async fn async_run_test() -> Result<()> { + let source_git_repo = prep_git_repo()?; + std::fs::write(source_git_repo.dir.join("commit.md"), "some content")?; + let main_commit_id = source_git_repo.stage_and_commit("commit.md")?; + + source_git_repo.create_branch("vnext")?; + source_git_repo.checkout("vnext")?; + std::fs::write(source_git_repo.dir.join("vnext.md"), "some content")?; + let vnext_commit_id = source_git_repo.stage_and_commit("vnext.md")?; + source_git_repo.checkout("main")?; + + 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(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), ); - p.exit()?; - for p in [51, 52, 53, 55, 56, 57] { - relay::shutdown_relay(8000 + p)?; - } + 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("list")?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + assert_eq!( + p.expect_eventually("\r\n\r\n")? + .split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/vnext", vnext_commit_id), + ]), + ); + 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()?; 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()?; - Ok(()) + } + + #[tokio::test] + #[serial] + async fn lists_head_and_2_branches_and_commit_ids_from_git_server() -> Result<()> { + async_run_test().await + } } + mod with_state_announcement { - #[tokio::test] - #[serial] - async fn lists_head_and_2_branches_and_commit_ids_from_git_server() -> Result<()> { - async_run_test().await + use super::*; + + mod when_announcement_matches_git_server { + + use super::*; + + async fn async_run_test() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + + 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(source_git_repo.dir.to_str().unwrap()), + 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<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + assert_eq!( + p.expect_eventually("\r\n\r\n")? + .split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ]), + ); + 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()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn lists_head_and_2_branches_and_commit_ids_announcement() -> Result<()> { + async_run_test().await + } + } + mod when_announcement_doesnt_match_git_server { + + use super::*; + + async fn async_run_test() -> Result<()> { + let (state_event, source_git_repo) = generate_repo_with_state_event().await?; + let main_original_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + + { + // add commit to main on git server + let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?; + std::fs::write(tmp_repo.dir.join("commitx.md"), "some content")?; + tmp_repo.stage_and_commit("commitx.md")?; + let mut remote = tmp_repo.git_repo.find_remote("origin")?; + remote.push(&["refs/heads/main:refs/heads/main"], None)?; + } + + let main_updated_commit_id = source_git_repo.get_tip_of_local_branch("main")?; + assert_ne!(main_original_commit_id, main_updated_commit_id); + 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(source_git_repo.dir.to_str().unwrap()), + 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<()> { + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line("list")?; + // println!("{}", p.expect_eventually("\r\n\r\n")?); + assert_eq!( + p.expect_eventually("\r\n\r\n")? + .split("\r\n") + .map(|e| e.to_string()) + .collect::>(), + HashSet::from([ + "@refs/heads/main HEAD".to_string(), + format!("{} refs/heads/main", main_original_commit_id), + format!("{} refs/heads/example-branch", example_commit_id), + ]), + ); + 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()?; + Ok(()) + } + + #[tokio::test] + #[serial] + async fn anouncement_state_is_used() -> Result<()> { + async_run_test().await + } + } } } @@ -656,6 +815,24 @@ mod push { p.expect("ok refs/heads/vnext\r\n")?; p.expect("\r\n")?; p.exit()?; + // local refs updated + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/main")? + .peel_to_commit()? + .id(), + main_commit_id, + ); + + assert_eq!( + git_repo + .git_repo + .find_reference("refs/remotes/nostr/vnext")? + .peel_to_commit()? + .id(), + vnext_commit_id + ); for p in [51, 52, 53, 55, 56, 57] { relay::shutdown_relay(8000 + p)?; } @@ -673,6 +850,18 @@ mod push { cli_tester_handle.join().unwrap()?; + // git_server updated + assert_eq!( + source_git_repo.get_tip_of_local_branch("main")?, + main_commit_id + ); + + assert_eq!( + source_git_repo.get_tip_of_local_branch("vnext")?, + vnext_commit_id + ); + + // state annoucement updated let state_event = r56 .events .iter() -- cgit v1.2.3