diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-22 12:18:01 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-22 12:18:01 +0000 |
| commit | 312786fbdacd61fc9f3ed59612d9a6add9112b7f (patch) | |
| tree | 660300df36b970c46c69d59893856bb6f03142b0 | |
| parent | 79896914357ee322b08b302c3dde08c7a24a0a09 (diff) | |
feat(pull): support `--in-reply-to` revisions
added tests to cover one of these rebase scenarios
| -rw-r--r-- | src/git.rs | 34 | ||||
| -rw-r--r-- | src/sub_commands/pull.rs | 141 | ||||
| -rw-r--r-- | tests/pull.rs | 296 |
3 files changed, 434 insertions, 37 deletions
| @@ -344,6 +344,12 @@ impl RepoActions for Repo { | |||
| 344 | } | 344 | } |
| 345 | 345 | ||
| 346 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> { | 346 | fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> { |
| 347 | let branch_checkedout = self.get_checked_out_branch_name()?.eq(branch_name); | ||
| 348 | if branch_checkedout { | ||
| 349 | let (name, _) = self.get_main_or_master_branch()?; | ||
| 350 | self.checkout(name)?; | ||
| 351 | } | ||
| 352 | |||
| 347 | self.git_repo | 353 | self.git_repo |
| 348 | .branch( | 354 | .branch( |
| 349 | branch_name, | 355 | branch_name, |
| @@ -351,6 +357,10 @@ impl RepoActions for Repo { | |||
| 351 | true, | 357 | true, |
| 352 | ) | 358 | ) |
| 353 | .context("branch could not be created")?; | 359 | .context("branch could not be created")?; |
| 360 | |||
| 361 | if branch_checkedout { | ||
| 362 | self.checkout(branch_name)?; | ||
| 363 | } | ||
| 354 | Ok(()) | 364 | Ok(()) |
| 355 | } | 365 | } |
| 356 | /* returns patches applied */ | 366 | /* returns patches applied */ |
| @@ -1292,6 +1302,30 @@ mod tests { | |||
| 1292 | assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid); | 1302 | assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid); |
| 1293 | Ok(()) | 1303 | Ok(()) |
| 1294 | } | 1304 | } |
| 1305 | |||
| 1306 | #[test] | ||
| 1307 | fn when_branch_is_checkedout_new_tip_specified_it_is_updated() -> Result<()> { | ||
| 1308 | let test_repo = GitTestRepo::default(); | ||
| 1309 | test_repo.populate()?; | ||
| 1310 | // create feature branch and add 2 commits | ||
| 1311 | test_repo.create_branch("feature")?; | ||
| 1312 | test_repo.checkout("feature")?; | ||
| 1313 | std::fs::write(test_repo.dir.join("t3.md"), "some content")?; | ||
| 1314 | let ahead_1_oid = test_repo.stage_and_commit("add t3.md")?; | ||
| 1315 | std::fs::write(test_repo.dir.join("t4.md"), "some content")?; | ||
| 1316 | let ahead_2_oid = test_repo.stage_and_commit("add t4.md")?; | ||
| 1317 | |||
| 1318 | let git_repo = Repo::from_path(&test_repo.dir)?; | ||
| 1319 | |||
| 1320 | let branch_name = "test-name-1"; | ||
| 1321 | git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?; | ||
| 1322 | test_repo.checkout(branch_name)?; | ||
| 1323 | git_repo.create_branch_at_commit(branch_name, &ahead_2_oid.to_string())?; | ||
| 1324 | test_repo.checkout("main")?; | ||
| 1325 | |||
| 1326 | assert_eq!(test_repo.checkout(branch_name)?, ahead_2_oid); | ||
| 1327 | Ok(()) | ||
| 1328 | } | ||
| 1295 | } | 1329 | } |
| 1296 | } | 1330 | } |
| 1297 | 1331 | ||
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs index de078e3..d832f6e 100644 --- a/src/sub_commands/pull.rs +++ b/src/sub_commands/pull.rs | |||
| @@ -1,12 +1,13 @@ | |||
| 1 | use anyhow::{bail, Context, Result}; | 1 | use anyhow::{bail, Context, Result}; |
| 2 | 2 | ||
| 3 | use super::list::{get_commit_id_from_patch, tag_value}; | ||
| 3 | #[cfg(not(test))] | 4 | #[cfg(not(test))] |
| 4 | use crate::client::Client; | 5 | use crate::client::Client; |
| 5 | #[cfg(test)] | 6 | #[cfg(test)] |
| 6 | use crate::client::MockConnect; | 7 | use crate::client::MockConnect; |
| 7 | use crate::{ | 8 | use crate::{ |
| 8 | client::Connect, | 9 | client::Connect, |
| 9 | git::{Repo, RepoActions}, | 10 | git::{str_to_sha1, Repo, RepoActions}, |
| 10 | repo_ref, | 11 | repo_ref, |
| 11 | sub_commands::{ | 12 | sub_commands::{ |
| 12 | list::get_most_recent_patch_with_ancestors, | 13 | list::get_most_recent_patch_with_ancestors, |
| @@ -14,6 +15,7 @@ use crate::{ | |||
| 14 | }, | 15 | }, |
| 15 | }; | 16 | }; |
| 16 | 17 | ||
| 18 | #[allow(clippy::too_many_lines)] | ||
| 17 | pub async fn launch() -> Result<()> { | 19 | pub async fn launch() -> Result<()> { |
| 18 | let git_repo = Repo::discover().context("cannot find a git repository")?; | 20 | let git_repo = Repo::discover().context("cannot find a git repository")?; |
| 19 | 21 | ||
| @@ -53,22 +55,139 @@ pub async fn launch() -> Result<()> { | |||
| 53 | ) | 55 | ) |
| 54 | .await?; | 56 | .await?; |
| 55 | 57 | ||
| 56 | if git_repo.has_outstanding_changes()? { | 58 | let most_recent_proposal_patch_chain = |
| 57 | bail!("cannot pull changes when repository is not clean. discard changes and try again."); | 59 | get_most_recent_patch_with_ancestors(commit_events.clone()) |
| 58 | } | 60 | .context("cannot get most recent patch for proposal")?; |
| 61 | |||
| 62 | let local_branch_tip = git_repo.get_tip_of_local_branch(&branch_name)?; | ||
| 63 | |||
| 64 | let (main_branch_name, master_tip) = git_repo.get_main_or_master_branch()?; | ||
| 65 | |||
| 66 | let (local_ahead_of_main, local_beind_main) = | ||
| 67 | git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?; | ||
| 59 | 68 | ||
| 60 | let most_recent_proposal_patch_chain = get_most_recent_patch_with_ancestors(commit_events) | 69 | let proposal_base_commit = str_to_sha1(&tag_value( |
| 61 | .context("cannot get most recent patch for proposal")?; | 70 | most_recent_proposal_patch_chain |
| 71 | .last() | ||
| 72 | .context("there should be at least one patch as we have already checked for this")?, | ||
| 73 | "parent-commit", | ||
| 74 | )?) | ||
| 75 | .context("cannot get valid parent commit id from patch")?; | ||
| 62 | 76 | ||
| 63 | let applied = git_repo | 77 | let (_, proposal_behind_main) = |
| 64 | .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain) | 78 | git_repo.get_commits_ahead_behind(&master_tip, &proposal_base_commit)?; |
| 65 | .context("cannot apply patch chain")?; | ||
| 66 | 79 | ||
| 67 | if applied.is_empty() { | 80 | let proposal_tip = |
| 81 | str_to_sha1( | ||
| 82 | &get_commit_id_from_patch(most_recent_proposal_patch_chain.first().context( | ||
| 83 | "there should be at least one patch as we have already checked for this", | ||
| 84 | )?) | ||
| 85 | .context("cannot get valid commit_id from patch")?, | ||
| 86 | ) | ||
| 87 | .context("cannot get valid commit_id from patch")?; | ||
| 88 | |||
| 89 | // if uptodate | ||
| 90 | if proposal_tip.eq(&local_branch_tip) { | ||
| 68 | println!("branch already up-to-date"); | 91 | println!("branch already up-to-date"); |
| 69 | } else { | 92 | } |
| 93 | // if new appendments | ||
| 94 | else if most_recent_proposal_patch_chain.iter().any(|patch| { | ||
| 95 | get_commit_id_from_patch(patch) | ||
| 96 | .unwrap_or_default() | ||
| 97 | .eq(&local_branch_tip.to_string()) | ||
| 98 | }) { | ||
| 99 | check_clean(&git_repo)?; | ||
| 100 | let applied = git_repo | ||
| 101 | .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain) | ||
| 102 | .context("cannot apply patch chain")?; | ||
| 70 | println!("applied {} new commits", applied.len(),); | 103 | println!("applied {} new commits", applied.len(),); |
| 71 | } | 104 | } |
| 105 | // if parent commit doesnt exist | ||
| 106 | else if !git_repo.does_commit_exist(&proposal_base_commit.to_string())? { | ||
| 107 | println!( | ||
| 108 | "a new version of the proposal has a prant commit that doesnt exist in your local repository." | ||
| 109 | ); | ||
| 110 | println!("your '{main_branch_name}' branch may not be up-to-date."); | ||
| 111 | println!("manually run `git pull` on '{main_branch_name}' and try again"); | ||
| 112 | } | ||
| 113 | // if tip of local in proposal history (new, ammended or rebased version but no | ||
| 114 | // local changes) | ||
| 115 | else if commit_events.iter().any(|patch| { | ||
| 116 | get_commit_id_from_patch(patch) | ||
| 117 | .unwrap_or_default() | ||
| 118 | .eq(&local_branch_tip.to_string()) | ||
| 119 | }) { | ||
| 120 | check_clean(&git_repo)?; | ||
| 121 | |||
| 122 | git_repo.create_branch_at_commit(&branch_name, &proposal_base_commit.to_string())?; | ||
| 123 | let applied = git_repo | ||
| 124 | .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain) | ||
| 125 | .context("cannot apply patch chain")?; | ||
| 126 | |||
| 127 | println!( | ||
| 128 | "pulled new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')", | ||
| 129 | applied.len(), | ||
| 130 | proposal_behind_main.len(), | ||
| 131 | local_ahead_of_main.len(), | ||
| 132 | local_beind_main.len(), | ||
| 133 | ); | ||
| 134 | } | ||
| 135 | // if tip of proposal in branch in history (local appendments made to up-to-date | ||
| 136 | // proposal) | ||
| 137 | else if let Ok((local_ahead_of_proposal, _)) = | ||
| 138 | git_repo.get_commits_ahead_behind(&proposal_tip, &local_branch_tip) | ||
| 139 | { | ||
| 140 | println!( | ||
| 141 | "local proposal branch exists with {} unpublished commits on top of the most up-to-date version of the proposal", | ||
| 142 | local_ahead_of_proposal.len() | ||
| 143 | ); | ||
| 144 | } | ||
| 145 | // user has probably has an unpublished rebase of the latest proposal version | ||
| 146 | // if tip of proposal commits exist (were once part of branch but have been | ||
| 147 | // ammended and git clean up job hasn't removed them) | ||
| 148 | else if git_repo.does_commit_exist(&proposal_tip.to_string())? { | ||
| 149 | println!( | ||
| 150 | "you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has other unpublished changes ({} ahead {} behind '{main_branch_name}')", | ||
| 151 | most_recent_proposal_patch_chain.len(), | ||
| 152 | proposal_behind_main.len(), | ||
| 153 | local_ahead_of_main.len(), | ||
| 154 | local_beind_main.len(), | ||
| 155 | ); | ||
| 156 | println!( | ||
| 157 | "if this sounds right then consider publishing your rebase `ngit push --force` or discarding your local branch" | ||
| 158 | ); | ||
| 159 | } | ||
| 160 | // user has probaly has an unpublished rebase of an older version of the | ||
| 161 | // proposal | ||
| 162 | else { | ||
| 163 | println!( | ||
| 164 | "your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')", | ||
| 165 | local_ahead_of_main.len(), | ||
| 166 | local_beind_main.len(), | ||
| 167 | most_recent_proposal_patch_chain.len(), | ||
| 168 | proposal_behind_main.len(), | ||
| 169 | ); | ||
| 170 | println!( | ||
| 171 | "its likely that you are working off an old proposal version because git has no record of the latest proposal commit." | ||
| 172 | ); | ||
| 173 | println!( | ||
| 174 | "it is possible that you have ammended the latest version and git has delete this commit as part of a clean up" | ||
| 175 | ); | ||
| 176 | |||
| 177 | println!("to view the latest proposal but retain your changes:"); | ||
| 178 | println!(" 1) create a new branch off the tip commit of this one to store your changes"); | ||
| 179 | println!(" 2) run `ngit list` and checkout the latest published version of this proposal"); | ||
| 180 | |||
| 181 | println!("if you are confident in your changes consider running `ngit push --force`"); | ||
| 182 | } | ||
| 183 | Ok(()) | ||
| 184 | } | ||
| 72 | 185 | ||
| 186 | fn check_clean(git_repo: &Repo) -> Result<()> { | ||
| 187 | if git_repo.has_outstanding_changes()? { | ||
| 188 | bail!( | ||
| 189 | "cannot pull proposal branch when repository is not clean. discard or stash (un)staged changes and try again." | ||
| 190 | ); | ||
| 191 | } | ||
| 73 | Ok(()) | 192 | Ok(()) |
| 74 | } | 193 | } |
diff --git a/tests/pull.rs b/tests/pull.rs index c4bc169..39f3ef4 100644 --- a/tests/pull.rs +++ b/tests/pull.rs | |||
| @@ -18,22 +18,22 @@ fn cli_tester_create_proposals() -> Result<GitTestRepo> { | |||
| 18 | &git_repo, | 18 | &git_repo, |
| 19 | FEATURE_BRANCH_NAME_1, | 19 | FEATURE_BRANCH_NAME_1, |
| 20 | "a", | 20 | "a", |
| 21 | PROPOSAL_TITLE_1, | 21 | Some((PROPOSAL_TITLE_1, "proposal a description")), |
| 22 | "proposal a description", | 22 | None, |
| 23 | )?; | 23 | )?; |
| 24 | cli_tester_create_proposal( | 24 | cli_tester_create_proposal( |
| 25 | &git_repo, | 25 | &git_repo, |
| 26 | FEATURE_BRANCH_NAME_2, | 26 | FEATURE_BRANCH_NAME_2, |
| 27 | "b", | 27 | "b", |
| 28 | PROPOSAL_TITLE_2, | 28 | Some((PROPOSAL_TITLE_2, "proposal b description")), |
| 29 | "proposal b description", | 29 | None, |
| 30 | )?; | 30 | )?; |
| 31 | cli_tester_create_proposal( | 31 | cli_tester_create_proposal( |
| 32 | &git_repo, | 32 | &git_repo, |
| 33 | FEATURE_BRANCH_NAME_3, | 33 | FEATURE_BRANCH_NAME_3, |
| 34 | "c", | 34 | "c", |
| 35 | PROPOSAL_TITLE_3, | 35 | Some((PROPOSAL_TITLE_3, "proposal c description")), |
| 36 | "proposal c description", | 36 | None, |
| 37 | )?; | 37 | )?; |
| 38 | Ok(git_repo) | 38 | Ok(git_repo) |
| 39 | } | 39 | } |
| @@ -66,27 +66,59 @@ fn cli_tester_create_proposal( | |||
| 66 | test_repo: &GitTestRepo, | 66 | test_repo: &GitTestRepo, |
| 67 | branch_name: &str, | 67 | branch_name: &str, |
| 68 | prefix: &str, | 68 | prefix: &str, |
| 69 | title: &str, | 69 | cover_letter_title_and_description: Option<(&str, &str)>, |
| 70 | description: &str, | 70 | in_reply_to: Option<String>, |
| 71 | ) -> Result<()> { | 71 | ) -> Result<()> { |
| 72 | create_and_populate_branch(test_repo, branch_name, prefix, false)?; | 72 | create_and_populate_branch(test_repo, branch_name, prefix, false)?; |
| 73 | 73 | ||
| 74 | let mut p = CliTester::new_from_dir( | 74 | if let Some(in_reply_to) = in_reply_to { |
| 75 | &test_repo.dir, | 75 | let mut p = CliTester::new_from_dir( |
| 76 | [ | 76 | &test_repo.dir, |
| 77 | "--nsec", | 77 | [ |
| 78 | TEST_KEY_1_NSEC, | 78 | "--nsec", |
| 79 | "--password", | 79 | TEST_KEY_1_NSEC, |
| 80 | TEST_PASSWORD, | 80 | "--password", |
| 81 | "--disable-cli-spinners", | 81 | TEST_PASSWORD, |
| 82 | "send", | 82 | "--disable-cli-spinners", |
| 83 | "--title", | 83 | "send", |
| 84 | format!("\"{title}\"").as_str(), | 84 | "--no-cover-letter", |
| 85 | "--description", | 85 | "--in-reply-to", |
| 86 | format!("\"{description}\"").as_str(), | 86 | in_reply_to.as_str(), |
| 87 | ], | 87 | ], |
| 88 | ); | 88 | ); |
| 89 | p.expect_end_eventually()?; | 89 | p.expect_end_eventually()?; |
| 90 | } else if let Some((title, description)) = cover_letter_title_and_description { | ||
| 91 | let mut p = CliTester::new_from_dir( | ||
| 92 | &test_repo.dir, | ||
| 93 | [ | ||
| 94 | "--nsec", | ||
| 95 | TEST_KEY_1_NSEC, | ||
| 96 | "--password", | ||
| 97 | TEST_PASSWORD, | ||
| 98 | "--disable-cli-spinners", | ||
| 99 | "send", | ||
| 100 | "--title", | ||
| 101 | format!("\"{title}\"").as_str(), | ||
| 102 | "--description", | ||
| 103 | format!("\"{description}\"").as_str(), | ||
| 104 | ], | ||
| 105 | ); | ||
| 106 | p.expect_end_eventually()?; | ||
| 107 | } else { | ||
| 108 | let mut p = CliTester::new_from_dir( | ||
| 109 | &test_repo.dir, | ||
| 110 | [ | ||
| 111 | "--nsec", | ||
| 112 | TEST_KEY_1_NSEC, | ||
| 113 | "--password", | ||
| 114 | TEST_PASSWORD, | ||
| 115 | "--disable-cli-spinners", | ||
| 116 | "send", | ||
| 117 | "--no-cover-letter", | ||
| 118 | ], | ||
| 119 | ); | ||
| 120 | p.expect_end_eventually()?; | ||
| 121 | } | ||
| 90 | Ok(()) | 122 | Ok(()) |
| 91 | } | 123 | } |
| 92 | 124 | ||
| @@ -415,7 +447,219 @@ mod when_branch_is_checked_out { | |||
| 415 | } | 447 | } |
| 416 | 448 | ||
| 417 | mod when_latest_event_rebases_branch { | 449 | mod when_latest_event_rebases_branch { |
| 418 | // use super::*; | 450 | use std::time::Duration; |
| 419 | // TODO | 451 | |
| 452 | use nostr_sdk::blocking::Client; | ||
| 453 | |||
| 454 | use super::*; | ||
| 455 | |||
| 456 | async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { | ||
| 457 | // fallback (51,52) user write (53, 55) repo (55, 56) | ||
| 458 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 459 | Relay::new(8051, None, None), | ||
| 460 | Relay::new(8052, None, None), | ||
| 461 | Relay::new(8053, None, None), | ||
| 462 | Relay::new(8055, None, None), | ||
| 463 | Relay::new(8056, None, None), | ||
| 464 | ); | ||
| 465 | |||
| 466 | r51.events.push(generate_test_key_1_relay_list_event()); | ||
| 467 | r51.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 468 | r51.events.push(generate_repo_ref_event()); | ||
| 469 | |||
| 470 | r55.events.push(generate_repo_ref_event()); | ||
| 471 | r55.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 472 | r55.events.push(generate_test_key_1_relay_list_event()); | ||
| 473 | |||
| 474 | let cli_tester_handle = | ||
| 475 | std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> { | ||
| 476 | // create 3 proposals | ||
| 477 | let _ = cli_tester_create_proposals()?; | ||
| 478 | // get proposal id of first | ||
| 479 | let client = Client::new(&nostr::Keys::generate()); | ||
| 480 | client.add_relay("ws://localhost:8055")?; | ||
| 481 | client.connect_relay("ws://localhost:8055")?; | ||
| 482 | let proposals = client.get_events_of( | ||
| 483 | vec![ | ||
| 484 | nostr::Filter::default() | ||
| 485 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 486 | .custom_tag(nostr::Alphabet::T, vec!["root"]), | ||
| 487 | ], | ||
| 488 | Some(Duration::from_millis(500)), | ||
| 489 | )?; | ||
| 490 | client.disconnect()?; | ||
| 491 | |||
| 492 | let proposal_1_id = proposals | ||
| 493 | .iter() | ||
| 494 | .find(|e| { | ||
| 495 | e.tags | ||
| 496 | .iter() | ||
| 497 | .any(|t| t.as_vec()[1].eq(&FEATURE_BRANCH_NAME_1)) | ||
| 498 | }) | ||
| 499 | .unwrap() | ||
| 500 | .id; | ||
| 501 | // recreate proposal 1 on top of a another commit (like a rebase on top | ||
| 502 | // of one extra commit) | ||
| 503 | let second_originating_repo = GitTestRepo::default(); | ||
| 504 | second_originating_repo.populate()?; | ||
| 505 | std::fs::write( | ||
| 506 | second_originating_repo.dir.join("amazing.md"), | ||
| 507 | "some content", | ||
| 508 | )?; | ||
| 509 | second_originating_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 510 | cli_tester_create_proposal( | ||
| 511 | &second_originating_repo, | ||
| 512 | FEATURE_BRANCH_NAME_1, | ||
| 513 | "a", | ||
| 514 | Some((PROPOSAL_TITLE_1, "proposal a description")), | ||
| 515 | Some(proposal_1_id.to_string()), | ||
| 516 | )?; | ||
| 517 | |||
| 518 | // pretend we have downloaded the origianl version of the first proposal | ||
| 519 | let test_repo = GitTestRepo::default(); | ||
| 520 | test_repo.populate()?; | ||
| 521 | create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?; | ||
| 522 | // pretend we have pulled the updated main branch | ||
| 523 | test_repo.checkout("main")?; | ||
| 524 | std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; | ||
| 525 | test_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 526 | test_repo.checkout(FEATURE_BRANCH_NAME_1)?; | ||
| 527 | |||
| 528 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); | ||
| 529 | p.expect_end_eventually_and_print()?; | ||
| 530 | |||
| 531 | for p in [51, 52, 53, 55, 56] { | ||
| 532 | relay::shutdown_relay(8000 + p)?; | ||
| 533 | } | ||
| 534 | Ok((second_originating_repo, test_repo)) | ||
| 535 | }); | ||
| 536 | |||
| 537 | // launch relay | ||
| 538 | let _ = join!( | ||
| 539 | r51.listen_until_close(), | ||
| 540 | r52.listen_until_close(), | ||
| 541 | r53.listen_until_close(), | ||
| 542 | r55.listen_until_close(), | ||
| 543 | r56.listen_until_close(), | ||
| 544 | ); | ||
| 545 | let res = cli_tester_handle.join().unwrap()?; | ||
| 546 | |||
| 547 | Ok(res) | ||
| 548 | } | ||
| 549 | |||
| 550 | mod cli_prompts { | ||
| 551 | use super::*; | ||
| 552 | async fn run_async_prompts_to_choose_from_proposal_titles() -> Result<()> { | ||
| 553 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 554 | Relay::new(8051, None, None), | ||
| 555 | Relay::new(8052, None, None), | ||
| 556 | Relay::new(8053, None, None), | ||
| 557 | Relay::new(8055, None, None), | ||
| 558 | Relay::new(8056, None, None), | ||
| 559 | ); | ||
| 560 | |||
| 561 | r51.events.push(generate_test_key_1_relay_list_event()); | ||
| 562 | r51.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 563 | r51.events.push(generate_repo_ref_event()); | ||
| 564 | |||
| 565 | r55.events.push(generate_repo_ref_event()); | ||
| 566 | r55.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 567 | r55.events.push(generate_test_key_1_relay_list_event()); | ||
| 568 | |||
| 569 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 570 | // create 3 proposals | ||
| 571 | let _ = cli_tester_create_proposals()?; | ||
| 572 | // get proposal id of first | ||
| 573 | let client = Client::new(&nostr::Keys::generate()); | ||
| 574 | client.add_relay("ws://localhost:8055")?; | ||
| 575 | client.connect_relay("ws://localhost:8055")?; | ||
| 576 | let proposals = client.get_events_of( | ||
| 577 | vec![ | ||
| 578 | nostr::Filter::default() | ||
| 579 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 580 | .custom_tag(nostr::Alphabet::T, vec!["root"]), | ||
| 581 | ], | ||
| 582 | Some(Duration::from_millis(500)), | ||
| 583 | )?; | ||
| 584 | client.disconnect()?; | ||
| 585 | |||
| 586 | let proposal_1_id = proposals | ||
| 587 | .iter() | ||
| 588 | .find(|e| { | ||
| 589 | e.tags | ||
| 590 | .iter() | ||
| 591 | .any(|t| t.as_vec()[1].eq(&FEATURE_BRANCH_NAME_1)) | ||
| 592 | }) | ||
| 593 | .unwrap() | ||
| 594 | .id; | ||
| 595 | // recreate proposal 1 on top of a another commit (like a rebase on top | ||
| 596 | // of one extra commit) | ||
| 597 | let second_originating_repo = GitTestRepo::default(); | ||
| 598 | second_originating_repo.populate()?; | ||
| 599 | std::fs::write( | ||
| 600 | second_originating_repo.dir.join("amazing.md"), | ||
| 601 | "some content", | ||
| 602 | )?; | ||
| 603 | second_originating_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 604 | cli_tester_create_proposal( | ||
| 605 | &second_originating_repo, | ||
| 606 | FEATURE_BRANCH_NAME_1, | ||
| 607 | "a", | ||
| 608 | Some((PROPOSAL_TITLE_1, "proposal a description")), | ||
| 609 | Some(proposal_1_id.to_string()), | ||
| 610 | )?; | ||
| 611 | |||
| 612 | // pretend we have downloaded the origianl version of the first proposal | ||
| 613 | let test_repo = GitTestRepo::default(); | ||
| 614 | test_repo.populate()?; | ||
| 615 | create_and_populate_branch(&test_repo, FEATURE_BRANCH_NAME_1, "a", false)?; | ||
| 616 | // pretend we have pulled the updated main branch | ||
| 617 | test_repo.checkout("main")?; | ||
| 618 | std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; | ||
| 619 | test_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 620 | test_repo.checkout(FEATURE_BRANCH_NAME_1)?; | ||
| 621 | |||
| 622 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["pull"]); | ||
| 623 | p.expect("finding proposal root event...\r\n")?; | ||
| 624 | p.expect("found proposal root event. finding commits...\r\n")?; | ||
| 625 | p.expect_end_with("pulled new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; | ||
| 626 | |||
| 627 | for p in [51, 52, 53, 55, 56] { | ||
| 628 | relay::shutdown_relay(8000 + p)?; | ||
| 629 | } | ||
| 630 | Ok(()) | ||
| 631 | }); | ||
| 632 | |||
| 633 | // launch relay | ||
| 634 | let _ = join!( | ||
| 635 | r51.listen_until_close(), | ||
| 636 | r52.listen_until_close(), | ||
| 637 | r53.listen_until_close(), | ||
| 638 | r55.listen_until_close(), | ||
| 639 | r56.listen_until_close(), | ||
| 640 | ); | ||
| 641 | cli_tester_handle.join().unwrap()?; | ||
| 642 | println!("{:?}", r55.events); | ||
| 643 | Ok(()) | ||
| 644 | } | ||
| 645 | |||
| 646 | #[tokio::test] | ||
| 647 | #[serial] | ||
| 648 | async fn prompts_to_choose_from_proposal_titles() -> Result<()> { | ||
| 649 | let _ = run_async_prompts_to_choose_from_proposal_titles().await; | ||
| 650 | Ok(()) | ||
| 651 | } | ||
| 652 | } | ||
| 653 | |||
| 654 | #[tokio::test] | ||
| 655 | #[serial] | ||
| 656 | async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { | ||
| 657 | let (originating_repo, test_repo) = prep_and_run().await?; | ||
| 658 | assert_eq!( | ||
| 659 | originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, | ||
| 660 | test_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, | ||
| 661 | ); | ||
| 662 | Ok(()) | ||
| 663 | } | ||
| 420 | } | 664 | } |
| 421 | } | 665 | } |