From 640fb41902c140a63f46b6126d17416b7dccf344 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 1 Aug 2024 11:09:51 +0100 Subject: feat(remote): `fetch` fallsback to other servers listed in the announcement `clone` tag --- src/git_remote_helper.rs | 35 ++++++++++++++++---- test_utils/src/git.rs | 6 ++++ tests/git_remote_helper.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index d0c0005..7b471b1 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs @@ -183,15 +183,36 @@ async fn list(git_repo: &Repo, repo_ref: &RepoRef, for_push: bool) -> Result<()> } fn fetch(git_repo: &Repository, repo_ref: &RepoRef, stdin: &Stdin, oid: &str) -> 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)?; + let oids = get_oids_from_fetch_batch(stdin, oid)?; + + let mut errors = HashMap::new(); + for git_server_url in &repo_ref.git_server { + if let Err(e) = fetch_from_git_server(git_repo, &oids, git_server_url) { + errors.insert(git_server_url.to_string(), e); + } else { + println!(); + return Ok(()); + } + } + bail!( + "failed to fetch objects in nostr state event from:\r\n{}", + errors + .iter() + .map(|(url, error)| format!("{url}: {error}")) + .collect::>() + .join("\r\n") + ); +} + +fn fetch_from_git_server( + git_repo: &Repository, + oids: &[String], + git_server_url: &str, +) -> Result<()> { + let mut git_server_remote = git_repo.remote_anonymous(git_server_url)?; git_server_remote.connect(git2::Direction::Fetch)?; - git_server_remote.download(&get_oids_from_fetch_batch(stdin, oid)?, None)?; + git_server_remote.download(oids, None)?; git_server_remote.disconnect()?; - println!(); Ok(()) } diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs index e522ffd..058eb31 100644 --- a/test_utils/src/git.rs +++ b/test_utils/src/git.rs @@ -154,6 +154,12 @@ impl GitTestRepo { self.stage_and_commit("add t2.md") } + pub fn populate_minus_1(&self) -> Result { + self.initial_commit()?; + fs::write(self.dir.join("t1.md"), "some content")?; + self.stage_and_commit("add t1.md") + } + pub fn populate_with_test_branch(&self) -> Result { self.populate()?; self.create_branch("add-example-feature")?; diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs index 69f290a..fda393f 100644 --- a/tests/git_remote_helper.rs +++ b/tests/git_remote_helper.rs @@ -39,6 +39,19 @@ fn prep_git_repo() -> Result { Ok(test_repo) } +fn prep_git_repo_minus_1_commit() -> Result { + let test_repo = GitTestRepo::without_repo_in_git_config(); + let mut config = test_repo + .git_repo + .config() + .context("cannot open git config")?; + config.set_str("nostr.nsec", TEST_KEY_1_NSEC)?; + config.set_str("nostr.npub", TEST_KEY_1_NPUB)?; + test_repo.add_remote(NOSTR_REMOTE_NAME, &get_nostr_remote_url()?)?; + test_repo.populate_minus_1()?; + Ok(test_repo) +} + fn cli_tester(git_repo: &GitTestRepo) -> CliTester { CliTester::new_remote_helper_from_dir(&git_repo.dir, &get_nostr_remote_url().unwrap()) } @@ -493,9 +506,75 @@ mod fetch { #[tokio::test] #[serial] - async fn fetch_downloads_speficied_branch_ref_commits_from_git_server() -> Result<()> { + async fn fetch_downloads_speficied_commits_from_git_server() -> Result<()> { async_run_test().await } + + mod when_first_git_server_fails_ { + 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 git_repo = prep_git_repo_minus_1_commit()?; + + 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![ + "./path-doesnt-exist".to_string(), + 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<()> { + assert!(git_repo.git_repo.find_commit(main_commit_id).is_err()); + + let mut p = cli_tester_after_fetch(&git_repo)?; + p.send_line(format!("fetch {main_commit_id} main").as_str())?; + p.send_line("")?; + p.expect("\r\n")?; + + assert!(git_repo.git_repo.find_commit(main_commit_id).is_ok()); + + 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 fetch_downloads_speficied_commits_from_second_git_server() -> Result<()> { + async_run_test().await + } + } } mod push { -- cgit v1.2.3