upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/git.rs16
-rw-r--r--src/git_remote_helper.rs339
-rw-r--r--tests/git_remote_helper.rs265
3 files changed, 447 insertions, 173 deletions
diff --git a/src/git.rs b/src/git.rs
index eaea512..0794788 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -39,6 +39,7 @@ pub trait RepoActions {
39 fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)>; 39 fn get_main_or_master_branch(&self) -> Result<(&str, Sha1Hash)>;
40 fn get_checked_out_branch_name(&self) -> Result<String>; 40 fn get_checked_out_branch_name(&self) -> Result<String>;
41 fn get_tip_of_branch(&self, branch_name: &str) -> Result<Sha1Hash>; 41 fn get_tip_of_branch(&self, branch_name: &str) -> Result<Sha1Hash>;
42 fn get_commit_or_tip_of_reference(&self, reference: &str) -> Result<Sha1Hash>;
42 fn get_root_commit(&self) -> Result<Sha1Hash>; 43 fn get_root_commit(&self) -> Result<Sha1Hash>;
43 fn does_commit_exist(&self, commit: &str) -> Result<bool>; 44 fn does_commit_exist(&self, commit: &str) -> Result<bool>;
44 fn get_head_commit(&self) -> Result<Sha1Hash>; 45 fn get_head_commit(&self) -> Result<Sha1Hash>;
@@ -215,6 +216,21 @@ impl RepoActions for Repo {
215 Ok(oid_to_sha1(&branch.into_reference().peel_to_commit()?.id())) 216 Ok(oid_to_sha1(&branch.into_reference().peel_to_commit()?.id()))
216 } 217 }
217 218
219 fn get_commit_or_tip_of_reference(&self, sha1_or_reference: &str) -> Result<Sha1Hash> {
220 let oid = {
221 if let Ok(oid) = Oid::from_str(sha1_or_reference) {
222 self.git_repo.find_commit(oid)?;
223 oid
224 } else {
225 self.git_repo
226 .find_reference(sha1_or_reference)?
227 .peel_to_commit()?
228 .id()
229 }
230 };
231 Ok(oid_to_sha1(&oid))
232 }
233
218 fn get_root_commit(&self) -> Result<Sha1Hash> { 234 fn get_root_commit(&self) -> Result<Sha1Hash> {
219 let mut revwalk = self 235 let mut revwalk = self
220 .git_repo 236 .git_repo
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
index 13d6c03..6f645da 100644
--- a/src/git_remote_helper.rs
+++ b/src/git_remote_helper.rs
@@ -78,6 +78,7 @@ async fn main() -> Result<()> {
78 let stdin = io::stdin(); 78 let stdin = io::stdin();
79 let mut line = String::new(); 79 let mut line = String::new();
80 80
81 let mut list_outputs = None;
81 loop { 82 loop {
82 let tokens = read_line(&stdin, &mut line)?; 83 let tokens = read_line(&stdin, &mut line)?;
83 84
@@ -105,14 +106,15 @@ async fn main() -> Result<()> {
105 &stdin, 106 &stdin,
106 refspec, 107 refspec,
107 &client, 108 &client,
109 list_outputs.clone(),
108 ) 110 )
109 .await?; 111 .await?;
110 } 112 }
111 ["list"] => { 113 ["list"] => {
112 list(&git_repo, &repo_ref, false).await?; 114 list_outputs = Some(list(&git_repo, &repo_ref, false).await?);
113 } 115 }
114 ["list", "for-push"] => { 116 ["list", "for-push"] => {
115 list(&git_repo, &repo_ref, true).await?; 117 list_outputs = Some(list(&git_repo, &repo_ref, true).await?);
116 } 118 }
117 [] => { 119 [] => {
118 return Ok(()); 120 return Ok(());
@@ -351,6 +353,7 @@ fn fetch_from_git_server(
351 Ok(()) 353 Ok(())
352} 354}
353 355
356#[allow(clippy::too_many_lines)]
354async fn push( 357async fn push(
355 git_repo: &Repo, 358 git_repo: &Repo,
356 repo_ref: &RepoRef, 359 repo_ref: &RepoRef,
@@ -359,56 +362,63 @@ async fn push(
359 initial_refspec: &str, 362 initial_refspec: &str,
360 #[cfg(test)] client: &crate::client::MockConnect, 363 #[cfg(test)] client: &crate::client::MockConnect,
361 #[cfg(not(test))] client: &Client, 364 #[cfg(not(test))] client: &Client,
365 list_outputs: Option<HashMap<String, HashMap<String, String>>>,
362) -> Result<()> { 366) -> Result<()> {
363 // TODO check 367 let mut refspecs = get_refspecs_from_push_batch(stdin, initial_refspec)?;
364 // bail!( 368
365 // "git server {} tip for branch {} conflicts with nostr and local branch. 369 let term = console::Term::stderr();
366 // to resolve either:\r\n 1. pull from that git server and resolve\r\n 2. 370
367 // force push your branch to the git server before pushing to nostr remote" 371 let list_outputs = match list_outputs {
368 // )?; 372 Some(outputs) => outputs,
369 373 _ => list_from_remotes(&term, git_repo, &repo_ref.git_server)?,
370 // if no state events - create from first git server listed 374 };
371 let refspecs = get_refspecs_from_push_batch(stdin, initial_refspec)?; 375
372 let git_server_url = repo_ref 376 let nostr_state = get_state_from_cache(git_repo.get_path()?, repo_ref).await;
373 .git_server 377
374 .first() 378 let existing_state = {
375 .context("no git server listed in nostr repository announcement")?; 379 // if no state events - create from first git server listed
376 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?; 380 if let Ok(nostr_state) = &nostr_state {
377 381 nostr_state.state.clone()
378 let auth = GitAuthenticator::default(); 382 } else if let Some(url) = repo_ref
379 let git_config = git_repo.git_repo.config()?; 383 .git_server
380 let mut push_options = git2::PushOptions::new(); 384 .iter()
381 let mut remote_callbacks = git2::RemoteCallbacks::new(); 385 .find(|&url| list_outputs.contains_key(url))
382 remote_callbacks.credentials(auth.credentials(&git_config)); 386 {
383 remote_callbacks.push_update_reference(|name, error| { 387 list_outputs.get(url).unwrap().to_owned()
384 if let Some(error) = error {
385 println!("error {name} {error}");
386 } else { 388 } else {
387 if let Some(refspec) = refspecs 389 bail!(
388 .iter() 390 "cannot connect to git servers: {}",
389 .find(|r| r.contains(format!(":{name}").as_str())) 391 repo_ref.git_server.join(" ")
390 { 392 );
391 if let Err(e) = 393 }
392 update_remote_refs_pushed(&git_repo.git_repo, refspec, nostr_remote_url) 394 };
393 .context("could not update remote_ref locally") 395
394 { 396 let (rejected_refspecs, remote_refspecs) = create_rejected_refspecs_and_remotes_refspecs(
395 return Err(git2::Error::from_str(e.to_string().as_str())); 397 &term,
396 } 398 git_repo,
397 } 399 &refspecs,
398 println!("ok {name}",); 400 &existing_state,
401 &list_outputs,
402 )?;
403
404 refspecs.retain(|refspec| {
405 if let Some(rejected) = rejected_refspecs.get(&refspec.to_string()) {
406 let (_, to) = refspec_to_from_to(refspec).unwrap();
407 println!("error {to} {} out of sync with nostr", rejected.join(" "));
408 false
409 } else {
410 true
399 } 411 }
400 Ok(())
401 }); 412 });
402 push_options.remote_callbacks(remote_callbacks);
403 git_server_remote.push(&refspecs, Some(&mut push_options))?;
404 git_server_remote.disconnect()?;
405 413
406 // TODO check whether push was succesful before proceeding - geting outcome from 414 if refspecs.is_empty() {
407 // callback isn't straightforward 415 // all refspecs rejected
416 println!();
417 return Ok(());
418 }
408 419
409 let new_state = generate_updated_state(git_repo, repo_ref, &refspecs).await?; 420 let new_state = generate_updated_state(git_repo, &existing_state, &refspecs)?;
410 421
411 // TODO enable interactive login
412 let (signer, user_ref) = login::launch( 422 let (signer, user_ref) = login::launch(
413 git_repo, 423 git_repo,
414 &None, 424 &None,
@@ -420,8 +430,12 @@ async fn push(
420 true, 430 true,
421 ) 431 )
422 .await?; 432 .await?;
433
423 let new_repo_state = RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?; 434 let new_repo_state = RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?;
424 435
436 // TODO check whether tip of each branch pushed is on at least one git server
437 // before broadcasting the nostr state
438
425 send_events( 439 send_events(
426 client, 440 client,
427 git_repo.get_path()?, 441 git_repo.get_path()?,
@@ -433,71 +447,218 @@ async fn push(
433 ) 447 )
434 .await?; 448 .await?;
435 449
436 // silently push to any other git servers 450 for refspec in &refspecs {
437 for (i, git_server_url) in repo_ref.git_server.iter().enumerate() { 451 let (_, to) = refspec_to_from_to(refspec)?;
438 // we have already pushed to the first one 452 println!("ok {to}");
439 if i.gt(&0) { 453 update_remote_refs_pushed(&git_repo.git_repo, refspec, nostr_remote_url)
440 if let Ok(mut git_server_remote) = git_repo.git_repo.remote_anonymous(git_server_url) { 454 .context("could not update remote_ref locally")?;
455 }
456
457 // TODO make async - check gitlib2 callbacks work async
458 let git_config = git_repo.git_repo.config()?;
459 for (git_server_url, refspecs) in remote_refspecs {
460 if !refspecs.is_empty() {
461 if let Ok(mut git_server_remote) = git_repo.git_repo.remote_anonymous(&git_server_url) {
441 let auth = GitAuthenticator::default(); 462 let auth = GitAuthenticator::default();
442 let git_config = git_repo.git_repo.config()?;
443 let mut push_options = git2::PushOptions::new(); 463 let mut push_options = git2::PushOptions::new();
444 let mut remote_callbacks = git2::RemoteCallbacks::new(); 464 let mut remote_callbacks = git2::RemoteCallbacks::new();
445 remote_callbacks.credentials(auth.credentials(&git_config)); 465 remote_callbacks.credentials(auth.credentials(&git_config));
466 remote_callbacks.push_update_reference(|name, error| {
467 if let Some(error) = error {
468 term.write_line(
469 format!("WARNING: error pushing {name} to {git_server_url} {error}")
470 .as_str(),
471 )
472 .unwrap();
473 }
474 Ok(())
475 });
446 push_options.remote_callbacks(remote_callbacks); 476 push_options.remote_callbacks(remote_callbacks);
447 let _ = git_server_remote.push(&refspecs, Some(&mut push_options)); 477 let _ = git_server_remote.push(&refspecs, Some(&mut push_options));
448 let _ = git_server_remote.disconnect(); 478 let _ = git_server_remote.disconnect();
449 } 479 }
450 } 480 }
451 } 481 }
452 // todo report on errors
453
454 println!(); 482 println!();
455 Ok(()) 483 Ok(())
456} 484}
457 485
458async fn generate_updated_state( 486type HashMapUrlRefspecs = HashMap<String, Vec<String>>;
487
488#[allow(clippy::too_many_lines)]
489fn create_rejected_refspecs_and_remotes_refspecs(
490 term: &console::Term,
459 git_repo: &Repo, 491 git_repo: &Repo,
460 repo_ref: &RepoRef,
461 refspecs: &Vec<String>, 492 refspecs: &Vec<String>,
462) -> Result<HashMap<String, String>> { 493 nostr_state: &HashMap<String, String>,
463 let new_state = { 494 list_outputs: &HashMap<String, HashMap<String, String>>,
464 if let Ok(mut repo_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await { 495) -> Result<(HashMapUrlRefspecs, HashMapUrlRefspecs)> {
465 for refspec in refspecs { 496 let mut refspecs_for_remotes = HashMap::new();
466 let (from, to) = refspec_to_from_to(refspec)?; 497
467 if from.is_empty() { 498 let mut rejected_refspecs: HashMapUrlRefspecs = HashMap::new();
468 // delete 499
469 repo_state.state.remove(to); 500 for (url, remote_state) in list_outputs {
470 } else { 501 let mut refspecs_for_remote = vec![];
471 // add or update 502 for refspec in refspecs {
472 repo_state.state.insert( 503 let (from, to) = refspec_to_from_to(refspec)?;
473 to.to_string(), 504 let nostr_value = nostr_state.get(to);
474 reference_to_ref_value(&git_repo.git_repo, to).unwrap(), 505 let remote_value = remote_state.get(to);
475 ); 506 if from.is_empty() {
507 if remote_value.is_some() {
508 // delete remote branch
509 refspecs_for_remote.push(refspec.clone());
476 } 510 }
511 continue;
477 } 512 }
478 repo_state.state 513 let from_tip = git_repo.get_commit_or_tip_of_reference(from)?;
479 } else { 514 if let Some(nostr_value) = nostr_value {
480 let mut state = HashMap::new(); 515 if let Some(remote_value) = remote_value {
481 let git_server_url = repo_ref 516 if nostr_value.eq(remote_value) {
482 .git_server 517 // in sync - existing branch at same state
483 .first() 518 let is_remote_tip_ancestor_of_commit = if let Ok(remote_value_tip) =
484 .context("no git server listed in nostr repository announcement")?; 519 git_repo.get_commit_or_tip_of_reference(remote_value)
485 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?; 520 {
486 git_server_remote.connect(git2::Direction::Fetch)?; 521 if let Ok((_, behind)) =
487 for head in git_server_remote.list()? { 522 git_repo.get_commits_ahead_behind(&remote_value_tip, &from_tip)
488 state.insert( 523 {
489 head.name().to_string(), 524 behind.is_empty()
490 if let Some(symbolic_ref) = head.symref_target() { 525 } else {
491 format!("ref: {symbolic_ref}") 526 false
527 }
528 } else {
529 false
530 };
531 if is_remote_tip_ancestor_of_commit {
532 refspecs_for_remote.push(refspec.clone());
533 } else {
534 // this is a force push so we need to force push to git server too
535 if refspec.starts_with('+') {
536 refspecs_for_remote.push(refspec.clone());
537 } else {
538 refspecs_for_remote.push(format!("+{refspec}"));
539 }
540 }
541 // TODO do we need to force push to this remote?
542 } else if let Ok(remote_value_tip) =
543 git_repo.get_commit_or_tip_of_reference(remote_value)
544 {
545 if from_tip.eq(&remote_value_tip) {
546 // remote already at correct state
547 term.write_line(
548 format!("{to} already at pushed commit state on {url}").as_str(),
549 )?;
550 }
551 let (_, behind) =
552 git_repo.get_commits_ahead_behind(&remote_value_tip, &from_tip)?;
553 if behind.is_empty() {
554 // can soft push
555 refspecs_for_remote.push(refspec.clone());
556 } else {
557 // cant soft push
558 rejected_refspecs
559 .entry(refspec.to_string())
560 .and_modify(|a| a.push(url.to_string()))
561 .or_insert(vec![url.to_string()]);
562 term.write_line(
563 format!("ERROR: {to} on {url} conflicts with nostr and is {} behind local branch. either:\r\n 1. pull from that git server and resolve\r\n 2. force push your branch to the git server before pushing to nostr remote", behind.len()).as_str(),
564 )?;
565 };
492 } else { 566 } else {
493 head.oid().to_string() 567 // remote_value oid is not present locally
494 }, 568 // TODO can we download the remote reference?
495 ); 569
570 // cant soft push
571 rejected_refspecs
572 .entry(refspec.to_string())
573 .and_modify(|a| a.push(url.to_string()))
574 .or_insert(vec![url.to_string()]);
575 term.write_line(
576 format!("ERROR: {to} on {url} conflicts with nostr and is not an ancestor of local branch. either:\r\n 1. pull from that git server and resolve\r\n 2. force push your branch to the git server before pushing to nostr remote").as_str(),
577 )?;
578 }
579 } else {
580 // existing nostr branch not on remote
581 // report - creating new branch
582 term.write_line(format!("pushing {to} as new branch on {url}").as_str())?;
583 refspecs_for_remote.push(refspec.clone());
584 }
585 } else if let Some(remote_value) = remote_value {
586 // new to nostr but on remote
587 if let Ok(remote_value_tip) = git_repo.get_commit_or_tip_of_reference(remote_value)
588 {
589 let (_, behind) =
590 git_repo.get_commits_ahead_behind(&remote_value_tip, &from_tip)?;
591 if behind.is_empty() {
592 // can soft push
593 refspecs_for_remote.push(refspec.clone());
594 } else {
595 // cant soft push
596 rejected_refspecs
597 .entry(refspec.to_string())
598 .and_modify(|a| a.push(url.to_string()))
599 .or_insert(vec![url.to_string()]);
600 term.write_line(
601 format!("ERROR: {to} not on nostr but on {url} is {} behind local branch. either:\r\n 1. pull from that git server and resolve\r\n 2. force push your branch to the git server before pushing to nostr remote", behind.len()).as_str(),
602 )?;
603 }
604 } else {
605 // havn't fetched oid from remote
606 // TODO fetch oid from remote
607 // cant soft push
608 rejected_refspecs
609 .entry(refspec.to_string())
610 .and_modify(|a| a.push(url.to_string()))
611 .or_insert(vec![url.to_string()]);
612 term.write_line(
613 format!("ERROR: {to} not on nostr but on {url} is not an ancestor of local branch. either:\r\n 1. pull from that git server and resolve\r\n 2. force push your branch to the git server before pushing to nostr remote").as_str(),
614 )?;
615 }
616 } else {
617 // in sync - new branch
618 refspecs_for_remote.push(refspec.clone());
496 } 619 }
497 git_server_remote.disconnect()?;
498 state
499 } 620 }
500 }; 621 refspecs_for_remotes.insert(url.to_string(), refspecs_for_remote);
622 }
623
624 // remove rejected refspecs so they dont get pushed to some remotes
625 let mut remotes_refspecs_without_rejected = HashMap::new();
626 for (url, value) in &refspecs_for_remotes {
627 remotes_refspecs_without_rejected.insert(
628 url.to_string(),
629 value
630 .iter()
631 .filter(|refspec| !rejected_refspecs.contains_key(*refspec))
632 .cloned()
633 .collect(),
634 );
635 }
636 Ok((rejected_refspecs, remotes_refspecs_without_rejected))
637}
638
639fn generate_updated_state(
640 git_repo: &Repo,
641 existing_state: &HashMap<String, String>,
642 refspecs: &Vec<String>,
643) -> Result<HashMap<String, String>> {
644 let mut new_state = existing_state.clone();
645
646 for refspec in refspecs {
647 let (from, to) = refspec_to_from_to(refspec)?;
648 if from.is_empty() {
649 // delete
650 new_state.remove(to);
651 } else {
652 // add or update
653 new_state.insert(
654 to.to_string(),
655 git_repo
656 .get_commit_or_tip_of_reference(from)
657 .unwrap()
658 .to_string(),
659 );
660 }
661 }
501 Ok(new_state) 662 Ok(new_state)
502} 663}
503 664
diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs
index 5a6c5f5..f2fa95f 100644
--- a/tests/git_remote_helper.rs
+++ b/tests/git_remote_helper.rs
@@ -64,6 +64,19 @@ fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result<CliTester> {
64 Ok(p) 64 Ok(p)
65} 65}
66 66
67/// git runs `list for-push` before `push`. in `push` we use the git server
68/// remote refs downloaded by `list` to assess how to push to git servers.
69/// we are therefore running it this way in our tests
70fn cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(
71 git_repo: &GitTestRepo,
72) -> Result<CliTester> {
73 let mut p = cli_tester_after_fetch(git_repo)?;
74
75 p.send_line("list for-push")?;
76 p.expect_eventually_and_print("\r\n\r\n")?;
77 Ok(p)
78}
79
67async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { 80async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> {
68 let git_repo = prep_git_repo()?; 81 let git_repo = prep_git_repo()?;
69 git_repo.create_branch("example-branch")?; 82 git_repo.create_branch("example-branch")?;
@@ -93,12 +106,11 @@ async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)>
93 r55.events = events; 106 r55.events = events;
94 107
95 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 108 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
96 let mut p = cli_tester_after_fetch(&git_repo)?; 109 let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
97 p.send_line("push refs/heads/main:refs/heads/main")?; 110 p.send_line("push refs/heads/main:refs/heads/main")?;
98 p.send_line("")?; 111 p.send_line("")?;
99 p.expect("ok refs/heads/main\r\n")?; 112 // p.expect("ok refs/heads/main\r\n")?;
100 p.expect("\r\n")?; 113 p.expect_eventually_and_print("\r\n\r\n")?;
101
102 p.exit()?; 114 p.exit()?;
103 for p in [51, 52, 53, 55, 56, 57] { 115 for p in [51, 52, 53, 55, 56, 57] {
104 relay::shutdown_relay(8000 + p)?; 116 relay::shutdown_relay(8000 + p)?;
@@ -574,19 +586,12 @@ mod push {
574 586
575 use super::*; 587 use super::*;
576 588
577 /// git runs `list for-push` before `push`. in `push` we use the git server 589 #[tokio::test]
578 /// remote refs downloaded by `list` to assess how to push to git servers. 590 #[serial]
579 /// we are therefore running it this way in our tests 591 async fn new_branch_when_no_state_event_exists() -> Result<()> {
580 fn cli_tester_after_nostr_fetch_and_sent_list_for_push_responds( 592 generate_repo_with_state_event().await?;
581 git_repo: &GitTestRepo, 593 Ok(())
582 ) -> Result<CliTester> {
583 let mut p = cli_tester_after_fetch(git_repo)?;
584
585 p.send_line("list for-push")?;
586 p.expect_eventually_and_print("\r\n\r\n")?;
587 Ok(p)
588 } 594 }
589
590 mod two_branches_in_batch_one_added_one_updated { 595 mod two_branches_in_batch_one_added_one_updated {
591 596
592 use super::*; 597 use super::*;
@@ -861,7 +866,7 @@ mod push {
861 p.send_line("push refs/heads/main:refs/heads/main")?; 866 p.send_line("push refs/heads/main:refs/heads/main")?;
862 p.send_line("push refs/heads/vnext:refs/heads/vnext")?; 867 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
863 p.send_line("")?; 868 p.send_line("")?;
864 p.expect_eventually("\r\n\r\n")?; 869 p.expect_eventually_and_print("\r\n\r\n")?;
865 p.exit()?; 870 p.exit()?;
866 for p in [51, 52, 53, 55, 56, 57] { 871 for p in [51, 52, 53, 55, 56, 57] {
867 relay::shutdown_relay(8000 + p)?; 872 relay::shutdown_relay(8000 + p)?;
@@ -1073,7 +1078,7 @@ mod push {
1073 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; 1078 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1074 p.send_line("push :refs/heads/vnext")?; 1079 p.send_line("push :refs/heads/vnext")?;
1075 p.send_line("")?; 1080 p.send_line("")?;
1076 p.expect_eventually("\r\n\r\n")?; 1081 p.expect_eventually_and_print("\r\n\r\n")?;
1077 p.exit()?; 1082 p.exit()?;
1078 for p in [51, 52, 53, 55, 56, 57] { 1083 for p in [51, 52, 53, 55, 56, 57] {
1079 relay::shutdown_relay(8000 + p)?; 1084 relay::shutdown_relay(8000 + p)?;
@@ -1238,80 +1243,172 @@ mod push {
1238 Ok(()) 1243 Ok(())
1239 } 1244 }
1240 1245
1241 #[tokio::test] 1246 mod when_existing_state_event {
1242 #[serial] 1247 use super::*;
1243 async fn existing_state_event_updated_with_branch_deleted_and_ok_printed() -> Result<()> {
1244 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1245 1248
1246 let git_repo = prep_git_repo()?; 1249 #[tokio::test]
1247 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example 1250 #[serial]
1251 async fn state_event_updated_and_branch_deleted_and_ok_printed() -> Result<()> {
1252 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1248 1253
1249 let events = vec![ 1254 let git_repo = prep_git_repo()?;
1250 generate_test_key_1_metadata_event("fred"), 1255 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example
1251 generate_test_key_1_relay_list_event(),
1252 generate_repo_ref_event_with_git_server(vec![
1253 source_git_repo.dir.to_str().unwrap().to_string(),
1254 ]),
1255 state_event.clone(),
1256 ];
1257 1256
1258 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57) 1257 let events = vec![
1259 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( 1258 generate_test_key_1_metadata_event("fred"),
1260 Relay::new(8051, None, None), 1259 generate_test_key_1_relay_list_event(),
1261 Relay::new(8052, None, None), 1260 generate_repo_ref_event_with_git_server(vec![
1262 Relay::new(8053, None, None), 1261 source_git_repo.dir.to_str().unwrap().to_string(),
1263 Relay::new(8055, None, None), 1262 ]),
1264 Relay::new(8056, None, None), 1263 state_event.clone(),
1265 Relay::new(8057, None, None), 1264 ];
1266 );
1267 r51.events = events.clone();
1268 r55.events = events;
1269 1265
1270 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 1266 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1271 let mut p = 1267 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1272 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?; 1268 Relay::new(8051, None, None),
1273 p.send_line("push :refs/heads/example-branch")?; 1269 Relay::new(8052, None, None),
1274 p.send_line("")?; 1270 Relay::new(8053, None, None),
1275 p.expect("ok refs/heads/example-branch\r\n")?; 1271 Relay::new(8055, None, None),
1276 p.expect("\r\n")?; 1272 Relay::new(8056, None, None),
1277 p.exit()?; 1273 Relay::new(8057, None, None),
1278 for p in [51, 52, 53, 55, 56, 57] { 1274 );
1279 relay::shutdown_relay(8000 + p)?; 1275 r51.events = events.clone();
1280 } 1276 r55.events = events;
1281 Ok(())
1282 });
1283 // launch relays
1284 let _ = join!(
1285 r51.listen_until_close(),
1286 r52.listen_until_close(),
1287 r53.listen_until_close(),
1288 r55.listen_until_close(),
1289 r56.listen_until_close(),
1290 r57.listen_until_close(),
1291 );
1292 1277
1293 cli_tester_handle.join().unwrap()?; 1278 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1279 let mut p =
1280 cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(&git_repo)?;
1281 p.send_line("push :refs/heads/example-branch")?;
1282 p.send_line("")?;
1283 p.expect("ok refs/heads/example-branch\r\n")?;
1284 p.expect("\r\n")?;
1285 p.exit()?;
1286 for p in [51, 52, 53, 55, 56, 57] {
1287 relay::shutdown_relay(8000 + p)?;
1288 }
1289 Ok(())
1290 });
1291 // launch relays
1292 let _ = join!(
1293 r51.listen_until_close(),
1294 r52.listen_until_close(),
1295 r53.listen_until_close(),
1296 r55.listen_until_close(),
1297 r56.listen_until_close(),
1298 r57.listen_until_close(),
1299 );
1294 1300
1295 let state_event = r56 1301 cli_tester_handle.join().unwrap()?;
1296 .events
1297 .iter()
1298 .find(|e| e.kind().eq(&STATE_KIND))
1299 .context("state event not created")?;
1300 1302
1301 // println!("{:#?}", state_event); 1303 let state_event = r56
1302 assert_eq!( 1304 .events
1303 state_event
1304 .tags
1305 .iter() 1305 .iter()
1306 .filter(|t| t.kind().to_string().as_str().ne("d")) 1306 .find(|e| e.kind().eq(&STATE_KIND))
1307 .map(|t| t.as_vec().to_vec()) 1307 .context("state event not created")?;
1308 .collect::<HashSet<Vec<String>>>(), 1308
1309 HashSet::from([ 1309 // println!("{:#?}", state_event);
1310 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()], 1310 assert_eq!(
1311 vec!["refs/heads/main".to_string(), main_commit_id.to_string()], 1311 state_event
1312 ]), 1312 .tags
1313 ); 1313 .iter()
1314 Ok(()) 1314 .filter(|t| t.kind().to_string().as_str().ne("d"))
1315 .map(|t| t.as_vec().to_vec())
1316 .collect::<HashSet<Vec<String>>>(),
1317 HashSet::from([
1318 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1319 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1320 ]),
1321 );
1322 Ok(())
1323 }
1324
1325 mod already_deleted_on_git_server {
1326 use super::*;
1327
1328 #[tokio::test]
1329 #[serial]
1330 async fn existing_state_event_updated_and_ok_printed() -> Result<()> {
1331 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
1332
1333 {
1334 // delete branch on git server
1335 let tmp_repo = GitTestRepo::clone_repo(&source_git_repo)?;
1336 let mut remote = tmp_repo.git_repo.find_remote("origin")?;
1337 remote.push(&[":refs/heads/example-branch"], None)?;
1338 }
1339
1340 let git_repo = prep_git_repo()?;
1341 let main_commit_id = git_repo.get_tip_of_local_branch("main")?.to_string(); // same as example
1342
1343 let events = vec![
1344 generate_test_key_1_metadata_event("fred"),
1345 generate_test_key_1_relay_list_event(),
1346 generate_repo_ref_event_with_git_server(vec![
1347 source_git_repo.dir.to_str().unwrap().to_string(),
1348 ]),
1349 state_event.clone(),
1350 ];
1351
1352 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
1353 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
1354 Relay::new(8051, None, None),
1355 Relay::new(8052, None, None),
1356 Relay::new(8053, None, None),
1357 Relay::new(8055, None, None),
1358 Relay::new(8056, None, None),
1359 Relay::new(8057, None, None),
1360 );
1361 r51.events = events.clone();
1362 r55.events = events;
1363
1364 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1365 let mut p = cli_tester_after_nostr_fetch_and_sent_list_for_push_responds(
1366 &git_repo,
1367 )?;
1368 p.send_line("push :refs/heads/example-branch")?;
1369 p.send_line("")?;
1370 p.expect("ok refs/heads/example-branch\r\n")?;
1371 p.expect("\r\n")?;
1372 p.exit()?;
1373 for p in [51, 52, 53, 55, 56, 57] {
1374 relay::shutdown_relay(8000 + p)?;
1375 }
1376 Ok(())
1377 });
1378 // launch relays
1379 let _ = join!(
1380 r51.listen_until_close(),
1381 r52.listen_until_close(),
1382 r53.listen_until_close(),
1383 r55.listen_until_close(),
1384 r56.listen_until_close(),
1385 r57.listen_until_close(),
1386 );
1387
1388 cli_tester_handle.join().unwrap()?;
1389
1390 let state_event = r56
1391 .events
1392 .iter()
1393 .find(|e| e.kind().eq(&STATE_KIND))
1394 .context("state event not created")?;
1395
1396 // println!("{:#?}", state_event);
1397 assert_eq!(
1398 state_event
1399 .tags
1400 .iter()
1401 .filter(|t| t.kind().to_string().as_str().ne("d"))
1402 .map(|t| t.as_vec().to_vec())
1403 .collect::<HashSet<Vec<String>>>(),
1404 HashSet::from([
1405 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
1406 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
1407 ]),
1408 );
1409 Ok(())
1410 }
1411 }
1315 } 1412 }
1316 } 1413 }
1317 1414