diff options
| -rw-r--r-- | src/git.rs | 16 | ||||
| -rw-r--r-- | src/git_remote_helper.rs | 339 | ||||
| -rw-r--r-- | tests/git_remote_helper.rs | 265 |
3 files changed, 447 insertions, 173 deletions
| @@ -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)] | ||
| 354 | async fn push( | 357 | async 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 | ||
| 458 | async fn generate_updated_state( | 486 | type HashMapUrlRefspecs = HashMap<String, Vec<String>>; |
| 487 | |||
| 488 | #[allow(clippy::too_many_lines)] | ||
| 489 | fn 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 | |||
| 639 | fn 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 | ||
| 70 | fn 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 | |||
| 67 | async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> { | 80 | async 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 | ||