diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-22 10:11:39 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-22 10:11:39 +0000 |
| commit | fdc15cb017b022a3b932ac5a337c649cb63df93c (patch) | |
| tree | a0883ab2e027042df43c15892f837fffe159cf49 | |
| parent | ea5aa6993d4c906c1703563ddc304c324c4ae079 (diff) | |
fix(list): support `--in-reply-to` latest revision
update list to support rebases via proposal revisions
as created by `ngit send --in-reply-to`
or upcoming change `ngit push --force`
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | src/sub_commands/list.rs | 171 | ||||
| -rw-r--r-- | src/sub_commands/push.rs | 35 | ||||
| -rw-r--r-- | src/sub_commands/send.rs | 5 | ||||
| -rw-r--r-- | test_utils/Cargo.toml | 1 | ||||
| -rw-r--r-- | tests/list.rs | 311 |
6 files changed, 445 insertions, 79 deletions
| @@ -2817,6 +2817,7 @@ dependencies = [ | |||
| 2817 | "directories", | 2817 | "directories", |
| 2818 | "git2", | 2818 | "git2", |
| 2819 | "nostr", | 2819 | "nostr", |
| 2820 | "nostr-sdk", | ||
| 2820 | "once_cell", | 2821 | "once_cell", |
| 2821 | "rand", | 2822 | "rand", |
| 2822 | "rexpect 0.5.0 (git+https://github.com/rust-cli/rexpect.git?rev=9eb61dd)", | 2823 | "rexpect 0.5.0 (git+https://github.com/rust-cli/rexpect.git?rev=9eb61dd)", |
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs index b556d5a..88ad52f 100644 --- a/src/sub_commands/list.rs +++ b/src/sub_commands/list.rs | |||
| @@ -13,8 +13,8 @@ use crate::{ | |||
| 13 | git::{str_to_sha1, Repo, RepoActions}, | 13 | git::{str_to_sha1, Repo, RepoActions}, |
| 14 | repo_ref::{self, RepoRef, REPO_REF_KIND}, | 14 | repo_ref::{self, RepoRef, REPO_REF_KIND}, |
| 15 | sub_commands::send::{ | 15 | sub_commands::send::{ |
| 16 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_to_cover_letter, | 16 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, |
| 17 | patch_supports_commit_ids, PATCH_KIND, | 17 | event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, |
| 18 | }, | 18 | }, |
| 19 | Cli, | 19 | Cli, |
| 20 | }; | 20 | }; |
| @@ -53,13 +53,17 @@ pub async fn launch(_cli_args: &Cli, _args: &SubCommandArgs) -> Result<()> { | |||
| 53 | 53 | ||
| 54 | println!("finding proposals..."); | 54 | println!("finding proposals..."); |
| 55 | 55 | ||
| 56 | let proposal_events: Vec<nostr::Event> = | 56 | let proposal_events_and_revisions: Vec<nostr::Event> = |
| 57 | find_proposal_events(&client, &repo_ref, &root_commit.to_string()).await?; | 57 | find_proposal_events(&client, &repo_ref, &root_commit.to_string()).await?; |
| 58 | 58 | ||
| 59 | if proposal_events.is_empty() { | 59 | if proposal_events_and_revisions.is_empty() { |
| 60 | println!("no proposals found... create one? try `ngit send`"); | 60 | println!("no proposals found... create one? try `ngit send`"); |
| 61 | return Ok(()); | 61 | return Ok(()); |
| 62 | } | 62 | } |
| 63 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions | ||
| 64 | .iter() | ||
| 65 | .filter(|e| !event_is_revision_root(e)) | ||
| 66 | .collect::<Vec<&nostr::Event>>(); | ||
| 63 | 67 | ||
| 64 | loop { | 68 | loop { |
| 65 | let selected_index = Interactor::default().choice( | 69 | let selected_index = Interactor::default().choice( |
| @@ -81,17 +85,32 @@ pub async fn launch(_cli_args: &Cli, _args: &SubCommandArgs) -> Result<()> { | |||
| 81 | ), | 85 | ), |
| 82 | )?; | 86 | )?; |
| 83 | 87 | ||
| 84 | let cover_letter = event_to_cover_letter(&proposal_events[selected_index]) | 88 | let cover_letter = event_to_cover_letter(proposal_events[selected_index]) |
| 85 | .context("cannot extract proposal details from proposal root event")?; | 89 | .context("cannot extract proposal details from proposal root event")?; |
| 86 | 90 | ||
| 87 | println!("finding commits..."); | 91 | println!("finding commits..."); |
| 88 | 92 | ||
| 89 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_event( | 93 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( |
| 90 | &client, | 94 | &client, |
| 91 | &proposal_events[selected_index], | 95 | &[ |
| 96 | vec![proposal_events[selected_index]], | ||
| 97 | proposal_events_and_revisions | ||
| 98 | .iter() | ||
| 99 | .filter(|e| { | ||
| 100 | e.tags.iter().any(|t| { | ||
| 101 | t.as_vec().len().gt(&1) | ||
| 102 | && t.as_vec()[1].eq(&proposal_events[selected_index].id.to_string()) | ||
| 103 | }) | ||
| 104 | }) | ||
| 105 | .collect::<Vec<&nostr::Event>>(), | ||
| 106 | ] | ||
| 107 | .concat(), | ||
| 92 | &repo_ref, | 108 | &repo_ref, |
| 93 | ) | 109 | ) |
| 94 | .await?; | 110 | .await?; |
| 111 | // for commit in &commits_events { | ||
| 112 | // println!("cevent: {:?}", commit.as_json()); | ||
| 113 | // } | ||
| 95 | 114 | ||
| 96 | let Ok(most_recent_proposal_patch_chain) = | 115 | let Ok(most_recent_proposal_patch_chain) = |
| 97 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 116 | get_most_recent_patch_with_ancestors(commits_events.clone()) |
| @@ -107,6 +126,9 @@ pub async fn launch(_cli_args: &Cli, _args: &SubCommandArgs) -> Result<()> { | |||
| 107 | } | 126 | } |
| 108 | return Ok(()); | 127 | return Ok(()); |
| 109 | }; | 128 | }; |
| 129 | // for commit in &most_recent_proposal_patch_chain { | ||
| 130 | // println!("recent_event: {:?}", commit.as_json()); | ||
| 131 | // } | ||
| 110 | 132 | ||
| 111 | let binding_patch_text_ref = format!("{} commits", most_recent_proposal_patch_chain.len()); | 133 | let binding_patch_text_ref = format!("{} commits", most_recent_proposal_patch_chain.len()); |
| 112 | let patch_text_ref = if most_recent_proposal_patch_chain.len().gt(&1) { | 134 | let patch_text_ref = if most_recent_proposal_patch_chain.len().gt(&1) { |
| @@ -354,59 +376,63 @@ pub async fn launch(_cli_args: &Cli, _args: &SubCommandArgs) -> Result<()> { | |||
| 354 | .unwrap_or_default() | 376 | .unwrap_or_default() |
| 355 | .eq(&local_branch_tip.to_string()) | 377 | .eq(&local_branch_tip.to_string()) |
| 356 | }) { | 378 | }) { |
| 379 | println!( | ||
| 380 | "updated proposal available ({} ahead {} behind '{main_branch_name}'). existing version is {} ahead {} behind '{main_branch_name}'", | ||
| 381 | most_recent_proposal_patch_chain.len(), | ||
| 382 | proposal_behind_main.len(), | ||
| 383 | local_ahead_of_main.len(), | ||
| 384 | local_beind_main.len(), | ||
| 385 | ); | ||
| 357 | return match Interactor::default().choice( | 386 | return match Interactor::default().choice( |
| 358 | PromptChoiceParms::default().with_default(0) | 387 | PromptChoiceParms::default() |
| 359 | .with_choices( | 388 | .with_default(0) |
| 360 | vec![ | 389 | .with_choices(vec![ |
| 361 | format!( | 390 | format!("checkout and overwrite existing proposal branch"), |
| 362 | "checkout new version of proposal branch ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')", | 391 | format!("checkout existing outdated proposal branch"), |
| 363 | most_recent_proposal_patch_chain.len(), | 392 | format!("apply to current branch with `git am`"), |
| 364 | proposal_behind_main.len(), | 393 | format!("download to ./patches"), |
| 365 | local_ahead_of_main.len(), | 394 | "back".to_string(), |
| 366 | local_beind_main.len(), | 395 | ]), |
| 367 | ), | ||
| 368 | format!( | ||
| 369 | "checkout existing outdated proposal branch ({} ahead {} behind '{main_branch_name}')", | ||
| 370 | local_ahead_of_main.len(), | ||
| 371 | local_beind_main.len(), | ||
| 372 | ), | ||
| 373 | format!("apply to current branch with `git am`"), | ||
| 374 | format!("download to ./patches"), | ||
| 375 | "back".to_string(), | ||
| 376 | ], | ||
| 377 | ) | ||
| 378 | )? { | 396 | )? { |
| 379 | 0 => { | 397 | 0 => { |
| 380 | check_clean(&git_repo)?; | 398 | check_clean(&git_repo)?; |
| 381 | git_repo.create_branch_at_commit(&cover_letter.branch_name, &proposal_base_commit.to_string())?; | 399 | git_repo.create_branch_at_commit( |
| 382 | git_repo.checkout(&cover_letter.branch_name)?; | 400 | &cover_letter.branch_name, |
| 383 | let chain_length = most_recent_proposal_patch_chain.len(); | 401 | &proposal_base_commit.to_string(), |
| 384 | let _ = git_repo | 402 | )?; |
| 385 | .apply_patch_chain(&cover_letter.branch_name, most_recent_proposal_patch_chain) | 403 | git_repo.checkout(&cover_letter.branch_name)?; |
| 386 | .context("cannot apply patch chain")?; | 404 | let chain_length = most_recent_proposal_patch_chain.len(); |
| 387 | format!( | 405 | let _ = git_repo |
| 388 | "checked out new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')", | 406 | .apply_patch_chain( |
| 389 | chain_length, | 407 | &cover_letter.branch_name, |
| 390 | proposal_behind_main.len(), | 408 | most_recent_proposal_patch_chain, |
| 391 | local_ahead_of_main.len(), | 409 | ) |
| 392 | local_beind_main.len(), | 410 | .context("cannot apply patch chain")?; |
| 393 | ); | 411 | println!( |
| 394 | Ok(()) | 412 | "checked out new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')", |
| 395 | }, | 413 | chain_length, |
| 396 | 1 => { | 414 | proposal_behind_main.len(), |
| 397 | check_clean(&git_repo)?; | 415 | local_ahead_of_main.len(), |
| 398 | git_repo.checkout(&cover_letter.branch_name)?; | 416 | local_beind_main.len(), |
| 399 | format!( | 417 | ); |
| 400 | "checked out old proposal in existing branch ({} ahead {} behind '{main_branch_name}')", | 418 | Ok(()) |
| 401 | local_ahead_of_main.len(), | 419 | } |
| 402 | local_beind_main.len(), | 420 | 1 => { |
| 403 | ); | 421 | check_clean(&git_repo)?; |
| 404 | Ok(()) | 422 | git_repo.checkout(&cover_letter.branch_name)?; |
| 405 | }, | 423 | println!( |
| 406 | 2 => {launch_git_am_with_patches(most_recent_proposal_patch_chain)}, | 424 | "checked out old proposal in existing branch ({} ahead {} behind '{main_branch_name}')", |
| 407 | 3 => {save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo)}, | 425 | local_ahead_of_main.len(), |
| 408 | 4 => { continue }, | 426 | local_beind_main.len(), |
| 409 | _ => { bail!("unexpected choice")} | 427 | ); |
| 428 | Ok(()) | ||
| 429 | } | ||
| 430 | 2 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | ||
| 431 | 3 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | ||
| 432 | 4 => continue, | ||
| 433 | _ => { | ||
| 434 | bail!("unexpected choice") | ||
| 435 | } | ||
| 410 | }; | 436 | }; |
| 411 | } | 437 | } |
| 412 | 438 | ||
| @@ -699,11 +725,11 @@ pub fn get_most_recent_patch_with_ancestors( | |||
| 699 | ) -> Result<Vec<nostr::Event>> { | 725 | ) -> Result<Vec<nostr::Event>> { |
| 700 | patches.sort_by_key(|e| e.created_at); | 726 | patches.sort_by_key(|e| e.created_at); |
| 701 | 727 | ||
| 702 | let first_patch = patches.first().context("no patches found")?; | 728 | let youngest_patch = patches.last().context("no patches found")?; |
| 703 | 729 | ||
| 704 | let patches_with_youngest_created_at: Vec<&nostr::Event> = patches | 730 | let patches_with_youngest_created_at: Vec<&nostr::Event> = patches |
| 705 | .iter() | 731 | .iter() |
| 706 | .filter(|p| p.created_at.eq(&first_patch.created_at)) | 732 | .filter(|p| p.created_at.eq(&youngest_patch.created_at)) |
| 707 | .collect(); | 733 | .collect(); |
| 708 | 734 | ||
| 709 | let mut res = vec![]; | 735 | let mut res = vec![]; |
| @@ -787,10 +813,10 @@ pub async fn find_proposal_events( | |||
| 787 | .collect::<Vec<nostr::Event>>()) | 813 | .collect::<Vec<nostr::Event>>()) |
| 788 | } | 814 | } |
| 789 | 815 | ||
| 790 | pub async fn find_commits_for_proposal_root_event( | 816 | pub async fn find_commits_for_proposal_root_events( |
| 791 | #[cfg(test)] client: &crate::client::MockConnect, | 817 | #[cfg(test)] client: &crate::client::MockConnect, |
| 792 | #[cfg(not(test))] client: &Client, | 818 | #[cfg(not(test))] client: &Client, |
| 793 | proposal_root_event: &nostr::Event, | 819 | proposal_root_events: &Vec<&nostr::Event>, |
| 794 | repo_ref: &RepoRef, | 820 | repo_ref: &RepoRef, |
| 795 | ) -> Result<Vec<nostr::Event>> { | 821 | ) -> Result<Vec<nostr::Event>> { |
| 796 | let mut patch_events: Vec<nostr::Event> = client | 822 | let mut patch_events: Vec<nostr::Event> = client |
| @@ -799,10 +825,12 @@ pub async fn find_commits_for_proposal_root_event( | |||
| 799 | vec![ | 825 | vec![ |
| 800 | nostr::Filter::default() | 826 | nostr::Filter::default() |
| 801 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 827 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 802 | // this requires every patch to reference the root event | 828 | .events( |
| 803 | // this will not pick up v2,v3 patch sets | 829 | proposal_root_events |
| 804 | // TODO: fetch commits for v2.. patch sets | 830 | .iter() |
| 805 | .event(proposal_root_event.id), | 831 | .map(|e| e.id) |
| 832 | .collect::<Vec<nostr::EventId>>(), | ||
| 833 | ), | ||
| 806 | ], | 834 | ], |
| 807 | ) | 835 | ) |
| 808 | .await | 836 | .await |
| @@ -811,14 +839,19 @@ pub async fn find_commits_for_proposal_root_event( | |||
| 811 | .filter(|e| { | 839 | .filter(|e| { |
| 812 | e.kind.as_u64() == PATCH_KIND | 840 | e.kind.as_u64() == PATCH_KIND |
| 813 | && e.tags.iter().any(|t| { | 841 | && e.tags.iter().any(|t| { |
| 814 | t.as_vec().len() > 2 && t.as_vec()[1].eq(&proposal_root_event.id.to_string()) | 842 | t.as_vec().len() > 2 |
| 843 | && proposal_root_events | ||
| 844 | .iter() | ||
| 845 | .any(|e| t.as_vec()[1].eq(&e.id.to_string())) | ||
| 815 | }) | 846 | }) |
| 816 | }) | 847 | }) |
| 817 | .map(std::borrow::ToOwned::to_owned) | 848 | .map(std::borrow::ToOwned::to_owned) |
| 818 | .collect(); | 849 | .collect(); |
| 819 | 850 | ||
| 820 | if !event_is_cover_letter(proposal_root_event) { | 851 | for e in proposal_root_events { |
| 821 | patch_events.push(proposal_root_event.clone()); | 852 | if !event_is_cover_letter(e) && !patch_events.iter().any(|e2| e2.id.eq(&e.id)) { |
| 853 | patch_events.push(e.to_owned().clone()); | ||
| 854 | } | ||
| 822 | } | 855 | } |
| 823 | Ok(patch_events) | 856 | Ok(patch_events) |
| 824 | } | 857 | } |
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs index 73bdb38..dd32b2c 100644 --- a/src/sub_commands/push.rs +++ b/src/sub_commands/push.rs | |||
| @@ -12,10 +12,10 @@ use crate::{ | |||
| 12 | repo_ref::{self, RepoRef}, | 12 | repo_ref::{self, RepoRef}, |
| 13 | sub_commands::{ | 13 | sub_commands::{ |
| 14 | list::{ | 14 | list::{ |
| 15 | find_commits_for_proposal_root_event, find_proposal_events, get_commit_id_from_patch, | 15 | find_commits_for_proposal_root_events, find_proposal_events, get_commit_id_from_patch, |
| 16 | get_most_recent_patch_with_ancestors, tag_value, | 16 | get_most_recent_patch_with_ancestors, tag_value, |
| 17 | }, | 17 | }, |
| 18 | send::{event_to_cover_letter, generate_patch_event, send_events}, | 18 | send::{event_is_revision_root, event_to_cover_letter, generate_patch_event, send_events}, |
| 19 | }, | 19 | }, |
| 20 | Cli, | 20 | Cli, |
| 21 | }; | 21 | }; |
| @@ -149,12 +149,17 @@ pub async fn fetch_proposal_root_and_most_recent_patch_chain( | |||
| 149 | ) -> Result<(nostr::Event, Vec<nostr::Event>)> { | 149 | ) -> Result<(nostr::Event, Vec<nostr::Event>)> { |
| 150 | println!("finding proposal root event..."); | 150 | println!("finding proposal root event..."); |
| 151 | 151 | ||
| 152 | let proposal_events: Vec<nostr::Event> = | 152 | let proposal_events_and_revisions: Vec<nostr::Event> = |
| 153 | find_proposal_events(client, repo_ref, &root_commit.to_string()) | 153 | find_proposal_events(client, repo_ref, &root_commit.to_string()) |
| 154 | .await | 154 | .await |
| 155 | .context("cannot get proposal events for repo")?; | 155 | .context("cannot get proposal events for repo")?; |
| 156 | 156 | ||
| 157 | let proposal_root_event: nostr::Event = proposal_events | 157 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions |
| 158 | .iter() | ||
| 159 | .filter(|e| !event_is_revision_root(e)) | ||
| 160 | .collect::<Vec<&nostr::Event>>(); | ||
| 161 | |||
| 162 | let proposal_root_event: &nostr::Event = proposal_events | ||
| 158 | .iter() | 163 | .iter() |
| 159 | .find(|e| { | 164 | .find(|e| { |
| 160 | event_to_cover_letter(e).is_ok_and(|cl| cl.branch_name.eq(branch_name)) | 165 | event_to_cover_letter(e).is_ok_and(|cl| cl.branch_name.eq(branch_name)) |
| @@ -166,8 +171,24 @@ pub async fn fetch_proposal_root_and_most_recent_patch_chain( | |||
| 166 | 171 | ||
| 167 | println!("found proposal root event. finding commits..."); | 172 | println!("found proposal root event. finding commits..."); |
| 168 | 173 | ||
| 169 | let commits_events: Vec<nostr::Event> = | 174 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( |
| 170 | find_commits_for_proposal_root_event(client, &proposal_root_event, repo_ref).await?; | 175 | client, |
| 176 | &[ | ||
| 177 | vec![proposal_root_event], | ||
| 178 | proposal_events_and_revisions | ||
| 179 | .iter() | ||
| 180 | .filter(|e| { | ||
| 181 | e.tags.iter().any(|t| { | ||
| 182 | t.as_vec().len().gt(&1) | ||
| 183 | && t.as_vec()[1].eq(&proposal_root_event.id.to_string()) | ||
| 184 | }) | ||
| 185 | }) | ||
| 186 | .collect::<Vec<&nostr::Event>>(), | ||
| 187 | ] | ||
| 188 | .concat(), | ||
| 189 | repo_ref, | ||
| 190 | ) | ||
| 191 | .await?; | ||
| 171 | 192 | ||
| 172 | Ok((proposal_root_event, commits_events)) | 193 | Ok((proposal_root_event.clone(), commits_events)) |
| 173 | } | 194 | } |
diff --git a/src/sub_commands/send.rs b/src/sub_commands/send.rs index c9c81ee..1ccb1f4 100644 --- a/src/sub_commands/send.rs +++ b/src/sub_commands/send.rs | |||
| @@ -613,6 +613,11 @@ pub fn event_is_patch_set_root(event: &nostr::Event) -> bool { | |||
| 613 | event.kind.as_u64().eq(&PATCH_KIND) && event.iter_tags().any(|t| t.as_vec()[1].eq("root")) | 613 | event.kind.as_u64().eq(&PATCH_KIND) && event.iter_tags().any(|t| t.as_vec()[1].eq("root")) |
| 614 | } | 614 | } |
| 615 | 615 | ||
| 616 | pub fn event_is_revision_root(event: &nostr::Event) -> bool { | ||
| 617 | event.kind.as_u64().eq(&PATCH_KIND) | ||
| 618 | && event.iter_tags().any(|t| t.as_vec()[1].eq("revision-root")) | ||
| 619 | } | ||
| 620 | |||
| 616 | pub fn patch_supports_commit_ids(event: &nostr::Event) -> bool { | 621 | pub fn patch_supports_commit_ids(event: &nostr::Event) -> bool { |
| 617 | event.kind.as_u64().eq(&PATCH_KIND) | 622 | event.kind.as_u64().eq(&PATCH_KIND) |
| 618 | && event | 623 | && event |
diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index adb8909..8928f67 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml | |||
| @@ -10,6 +10,7 @@ dialoguer = "0.10.4" | |||
| 10 | directories = "5.0.1" | 10 | directories = "5.0.1" |
| 11 | git2 = "0.18.1" | 11 | git2 = "0.18.1" |
| 12 | nostr = "0.27.0" | 12 | nostr = "0.27.0" |
| 13 | nostr-sdk = { version = "0.27.0", features = ["blocking"] } | ||
| 13 | once_cell = "1.18.0" | 14 | once_cell = "1.18.0" |
| 14 | rand = "0.8" | 15 | rand = "0.8" |
| 15 | rexpect = { git = "https://github.com/rust-cli/rexpect.git", rev = "9eb61dd" } | 16 | rexpect = { git = "https://github.com/rust-cli/rexpect.git", rev = "9eb61dd" } |
diff --git a/tests/list.rs b/tests/list.rs index 7762b1c..eb6dc3d 100644 --- a/tests/list.rs +++ b/tests/list.rs | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | use anyhow::Result; | 1 | use anyhow::Result; |
| 2 | use futures::join; | 2 | use futures::join; |
| 3 | use nostr_sdk::client::blocking::Client; | ||
| 3 | use serial_test::serial; | 4 | use serial_test::serial; |
| 4 | use test_utils::{git::GitTestRepo, relay::Relay, *}; | 5 | use test_utils::{git::GitTestRepo, relay::Relay, *}; |
| 5 | 6 | ||
| @@ -20,18 +21,21 @@ fn cli_tester_create_proposals() -> Result<GitTestRepo> { | |||
| 20 | FEATURE_BRANCH_NAME_1, | 21 | FEATURE_BRANCH_NAME_1, |
| 21 | "a", | 22 | "a", |
| 22 | Some((PROPOSAL_TITLE_1, "proposal a description")), | 23 | Some((PROPOSAL_TITLE_1, "proposal a description")), |
| 24 | None, | ||
| 23 | )?; | 25 | )?; |
| 24 | cli_tester_create_proposal( | 26 | cli_tester_create_proposal( |
| 25 | &git_repo, | 27 | &git_repo, |
| 26 | FEATURE_BRANCH_NAME_2, | 28 | FEATURE_BRANCH_NAME_2, |
| 27 | "b", | 29 | "b", |
| 28 | Some((PROPOSAL_TITLE_2, "proposal b description")), | 30 | Some((PROPOSAL_TITLE_2, "proposal b description")), |
| 31 | None, | ||
| 29 | )?; | 32 | )?; |
| 30 | cli_tester_create_proposal( | 33 | cli_tester_create_proposal( |
| 31 | &git_repo, | 34 | &git_repo, |
| 32 | FEATURE_BRANCH_NAME_3, | 35 | FEATURE_BRANCH_NAME_3, |
| 33 | "c", | 36 | "c", |
| 34 | Some((PROPOSAL_TITLE_3, "proposal c description")), | 37 | Some((PROPOSAL_TITLE_3, "proposal c description")), |
| 38 | None, | ||
| 35 | )?; | 39 | )?; |
| 36 | Ok(git_repo) | 40 | Ok(git_repo) |
| 37 | } | 41 | } |
| @@ -65,10 +69,27 @@ fn cli_tester_create_proposal( | |||
| 65 | branch_name: &str, | 69 | branch_name: &str, |
| 66 | prefix: &str, | 70 | prefix: &str, |
| 67 | cover_letter_title_and_description: Option<(&str, &str)>, | 71 | cover_letter_title_and_description: Option<(&str, &str)>, |
| 72 | in_reply_to: Option<String>, | ||
| 68 | ) -> Result<()> { | 73 | ) -> Result<()> { |
| 69 | create_and_populate_branch(test_repo, branch_name, prefix, false)?; | 74 | create_and_populate_branch(test_repo, branch_name, prefix, false)?; |
| 70 | 75 | ||
| 71 | if let Some((title, description)) = cover_letter_title_and_description { | 76 | if let Some(in_reply_to) = in_reply_to { |
| 77 | let mut p = CliTester::new_from_dir( | ||
| 78 | &test_repo.dir, | ||
| 79 | [ | ||
| 80 | "--nsec", | ||
| 81 | TEST_KEY_1_NSEC, | ||
| 82 | "--password", | ||
| 83 | TEST_PASSWORD, | ||
| 84 | "--disable-cli-spinners", | ||
| 85 | "send", | ||
| 86 | "--no-cover-letter", | ||
| 87 | "--in-reply-to", | ||
| 88 | in_reply_to.as_str(), | ||
| 89 | ], | ||
| 90 | ); | ||
| 91 | p.expect_end_eventually()?; | ||
| 92 | } else if let Some((title, description)) = cover_letter_title_and_description { | ||
| 72 | let mut p = CliTester::new_from_dir( | 93 | let mut p = CliTester::new_from_dir( |
| 73 | &test_repo.dir, | 94 | &test_repo.dir, |
| 74 | [ | 95 | [ |
| @@ -636,6 +657,7 @@ mod when_main_branch_is_uptodate { | |||
| 636 | FEATURE_BRANCH_NAME_4, | 657 | FEATURE_BRANCH_NAME_4, |
| 637 | "d", | 658 | "d", |
| 638 | None, | 659 | None, |
| 660 | None, | ||
| 639 | )?; | 661 | )?; |
| 640 | let test_repo = GitTestRepo::default(); | 662 | let test_repo = GitTestRepo::default(); |
| 641 | test_repo.populate()?; | 663 | test_repo.populate()?; |
| @@ -715,6 +737,7 @@ mod when_main_branch_is_uptodate { | |||
| 715 | FEATURE_BRANCH_NAME_4, | 737 | FEATURE_BRANCH_NAME_4, |
| 716 | "d", | 738 | "d", |
| 717 | None, | 739 | None, |
| 740 | None, | ||
| 718 | )?; | 741 | )?; |
| 719 | let test_repo = GitTestRepo::default(); | 742 | let test_repo = GitTestRepo::default(); |
| 720 | test_repo.populate()?; | 743 | test_repo.populate()?; |
| @@ -1193,8 +1216,290 @@ mod when_main_branch_is_uptodate { | |||
| 1193 | } | 1216 | } |
| 1194 | 1217 | ||
| 1195 | mod when_latest_event_rebases_branch { | 1218 | mod when_latest_event_rebases_branch { |
| 1196 | // use super::*; | 1219 | use std::time::Duration; |
| 1197 | // TODO | 1220 | |
| 1221 | use super::*; | ||
| 1222 | |||
| 1223 | async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> { | ||
| 1224 | // fallback (51,52) user write (53, 55) repo (55, 56) | ||
| 1225 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 1226 | Relay::new(8051, None, None), | ||
| 1227 | Relay::new(8052, None, None), | ||
| 1228 | Relay::new(8053, None, None), | ||
| 1229 | Relay::new(8055, None, None), | ||
| 1230 | Relay::new(8056, None, None), | ||
| 1231 | ); | ||
| 1232 | |||
| 1233 | r51.events.push(generate_test_key_1_relay_list_event()); | ||
| 1234 | r51.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 1235 | r51.events.push(generate_repo_ref_event()); | ||
| 1236 | |||
| 1237 | r55.events.push(generate_repo_ref_event()); | ||
| 1238 | r55.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 1239 | r55.events.push(generate_test_key_1_relay_list_event()); | ||
| 1240 | |||
| 1241 | let cli_tester_handle = std::thread::spawn( | ||
| 1242 | move || -> Result<(GitTestRepo, GitTestRepo)> { | ||
| 1243 | // create 3 proposals | ||
| 1244 | let _ = cli_tester_create_proposals()?; | ||
| 1245 | // get proposal id of first | ||
| 1246 | let client = Client::new(&nostr::Keys::generate()); | ||
| 1247 | client.add_relay("ws://localhost:8055")?; | ||
| 1248 | client.connect_relay("ws://localhost:8055")?; | ||
| 1249 | let proposals = client.get_events_of( | ||
| 1250 | vec![ | ||
| 1251 | nostr::Filter::default() | ||
| 1252 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 1253 | .custom_tag(nostr::Alphabet::T, vec!["root"]), | ||
| 1254 | ], | ||
| 1255 | Some(Duration::from_millis(500)), | ||
| 1256 | )?; | ||
| 1257 | client.disconnect()?; | ||
| 1258 | |||
| 1259 | let proposal_1_id = proposals | ||
| 1260 | .iter() | ||
| 1261 | .find(|e| { | ||
| 1262 | e.tags | ||
| 1263 | .iter() | ||
| 1264 | .any(|t| t.as_vec()[1].eq(&FEATURE_BRANCH_NAME_1)) | ||
| 1265 | }) | ||
| 1266 | .unwrap() | ||
| 1267 | .id; | ||
| 1268 | // recreate proposal 1 on top of a another commit (like a rebase on top | ||
| 1269 | // of one extra commit) | ||
| 1270 | let second_originating_repo = GitTestRepo::default(); | ||
| 1271 | second_originating_repo.populate()?; | ||
| 1272 | std::fs::write( | ||
| 1273 | second_originating_repo.dir.join("amazing.md"), | ||
| 1274 | "some content", | ||
| 1275 | )?; | ||
| 1276 | second_originating_repo | ||
| 1277 | .stage_and_commit("commit for rebasing on top of")?; | ||
| 1278 | cli_tester_create_proposal( | ||
| 1279 | &second_originating_repo, | ||
| 1280 | FEATURE_BRANCH_NAME_1, | ||
| 1281 | "a", | ||
| 1282 | Some((PROPOSAL_TITLE_1, "proposal a description")), | ||
| 1283 | Some(proposal_1_id.to_string()), | ||
| 1284 | )?; | ||
| 1285 | |||
| 1286 | // pretend we have downloaded the origianl version of the first proposal | ||
| 1287 | let test_repo = GitTestRepo::default(); | ||
| 1288 | test_repo.populate()?; | ||
| 1289 | create_and_populate_branch( | ||
| 1290 | &test_repo, | ||
| 1291 | FEATURE_BRANCH_NAME_1, | ||
| 1292 | "a", | ||
| 1293 | false, | ||
| 1294 | )?; | ||
| 1295 | // pretend we have pulled the updated main branch | ||
| 1296 | test_repo.checkout("main")?; | ||
| 1297 | std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; | ||
| 1298 | test_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 1299 | |||
| 1300 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | ||
| 1301 | p.expect("finding proposals...\r\n")?; | ||
| 1302 | let mut c = p.expect_choice( | ||
| 1303 | "all proposals", | ||
| 1304 | vec![ | ||
| 1305 | format!("\"{PROPOSAL_TITLE_1}\""), | ||
| 1306 | format!("\"{PROPOSAL_TITLE_2}\""), | ||
| 1307 | format!("\"{PROPOSAL_TITLE_3}\""), | ||
| 1308 | ], | ||
| 1309 | )?; | ||
| 1310 | c.succeeds_with(0, true, None)?; | ||
| 1311 | p.expect("finding commits...\r\n")?; | ||
| 1312 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; | ||
| 1313 | // its got here but tmpgit-32.. indicates that creatubg the eature | ||
| 1314 | // branch in line 1291 didnt work so the choices will be different | ||
| 1315 | let mut c = p.expect_choice( | ||
| 1316 | "", | ||
| 1317 | vec![ | ||
| 1318 | format!("checkout and overwrite existing proposal branch"), | ||
| 1319 | format!("checkout existing outdated proposal branch"), | ||
| 1320 | format!("apply to current branch with `git am`"), | ||
| 1321 | format!("download to ./patches"), | ||
| 1322 | format!("back"), | ||
| 1323 | ], | ||
| 1324 | )?; | ||
| 1325 | c.succeeds_with(0, false, Some(0))?; | ||
| 1326 | p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; | ||
| 1327 | p.expect_end()?; | ||
| 1328 | |||
| 1329 | for p in [51, 52, 53, 55, 56] { | ||
| 1330 | relay::shutdown_relay(8000 + p)?; | ||
| 1331 | } | ||
| 1332 | Ok((second_originating_repo, test_repo)) | ||
| 1333 | }, | ||
| 1334 | ); | ||
| 1335 | |||
| 1336 | // launch relay | ||
| 1337 | let _ = join!( | ||
| 1338 | r51.listen_until_close(), | ||
| 1339 | r52.listen_until_close(), | ||
| 1340 | r53.listen_until_close(), | ||
| 1341 | r55.listen_until_close(), | ||
| 1342 | r56.listen_until_close(), | ||
| 1343 | ); | ||
| 1344 | let res = cli_tester_handle.join().unwrap()?; | ||
| 1345 | |||
| 1346 | Ok(res) | ||
| 1347 | } | ||
| 1348 | |||
| 1349 | mod cli_prompts { | ||
| 1350 | use super::*; | ||
| 1351 | async fn run_async_prompts_to_choose_from_proposal_titles() -> Result<()> { | ||
| 1352 | let (mut r51, mut r52, mut r53, mut r55, mut r56) = ( | ||
| 1353 | Relay::new(8051, None, None), | ||
| 1354 | Relay::new(8052, None, None), | ||
| 1355 | Relay::new(8053, None, None), | ||
| 1356 | Relay::new(8055, None, None), | ||
| 1357 | Relay::new(8056, None, None), | ||
| 1358 | ); | ||
| 1359 | |||
| 1360 | r51.events.push(generate_test_key_1_relay_list_event()); | ||
| 1361 | r51.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 1362 | r51.events.push(generate_repo_ref_event()); | ||
| 1363 | |||
| 1364 | r55.events.push(generate_repo_ref_event()); | ||
| 1365 | r55.events.push(generate_test_key_1_metadata_event("fred")); | ||
| 1366 | r55.events.push(generate_test_key_1_relay_list_event()); | ||
| 1367 | |||
| 1368 | let cli_tester_handle = std::thread::spawn(move || -> Result<()> { | ||
| 1369 | // create 3 proposals | ||
| 1370 | let _ = cli_tester_create_proposals()?; | ||
| 1371 | // get proposal id of first | ||
| 1372 | let client = Client::new(&nostr::Keys::generate()); | ||
| 1373 | client.add_relay("ws://localhost:8055")?; | ||
| 1374 | client.connect_relay("ws://localhost:8055")?; | ||
| 1375 | let proposals = client.get_events_of( | ||
| 1376 | vec![ | ||
| 1377 | nostr::Filter::default() | ||
| 1378 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 1379 | .custom_tag(nostr::Alphabet::T, vec!["root"]), | ||
| 1380 | ], | ||
| 1381 | Some(Duration::from_millis(500)), | ||
| 1382 | )?; | ||
| 1383 | client.disconnect()?; | ||
| 1384 | |||
| 1385 | let proposal_1_id = proposals | ||
| 1386 | .iter() | ||
| 1387 | .find(|e| { | ||
| 1388 | e.tags | ||
| 1389 | .iter() | ||
| 1390 | .any(|t| t.as_vec()[1].eq(&FEATURE_BRANCH_NAME_1)) | ||
| 1391 | }) | ||
| 1392 | .unwrap() | ||
| 1393 | .id; | ||
| 1394 | // recreate proposal 1 on top of a another commit (like a rebase on top | ||
| 1395 | // of one extra commit) | ||
| 1396 | let second_originating_repo = GitTestRepo::default(); | ||
| 1397 | second_originating_repo.populate()?; | ||
| 1398 | std::fs::write( | ||
| 1399 | second_originating_repo.dir.join("amazing.md"), | ||
| 1400 | "some content", | ||
| 1401 | )?; | ||
| 1402 | second_originating_repo | ||
| 1403 | .stage_and_commit("commit for rebasing on top of")?; | ||
| 1404 | cli_tester_create_proposal( | ||
| 1405 | &second_originating_repo, | ||
| 1406 | FEATURE_BRANCH_NAME_1, | ||
| 1407 | "a", | ||
| 1408 | Some((PROPOSAL_TITLE_1, "proposal a description")), | ||
| 1409 | Some(proposal_1_id.to_string()), | ||
| 1410 | )?; | ||
| 1411 | |||
| 1412 | // pretend we have downloaded the origianl version of the first proposal | ||
| 1413 | let test_repo = GitTestRepo::default(); | ||
| 1414 | test_repo.populate()?; | ||
| 1415 | create_and_populate_branch( | ||
| 1416 | &test_repo, | ||
| 1417 | FEATURE_BRANCH_NAME_1, | ||
| 1418 | "a", | ||
| 1419 | false, | ||
| 1420 | )?; | ||
| 1421 | // pretend we have pulled the updated main branch | ||
| 1422 | test_repo.checkout("main")?; | ||
| 1423 | std::fs::write(test_repo.dir.join("amazing.md"), "some content")?; | ||
| 1424 | test_repo.stage_and_commit("commit for rebasing on top of")?; | ||
| 1425 | |||
| 1426 | let mut p = CliTester::new_from_dir(&test_repo.dir, ["list"]); | ||
| 1427 | p.expect("finding proposals...\r\n")?; | ||
| 1428 | let mut c = p.expect_choice( | ||
| 1429 | "all proposals", | ||
| 1430 | vec![ | ||
| 1431 | format!("\"{PROPOSAL_TITLE_1}\""), | ||
| 1432 | format!("\"{PROPOSAL_TITLE_2}\""), | ||
| 1433 | format!("\"{PROPOSAL_TITLE_3}\""), | ||
| 1434 | ], | ||
| 1435 | )?; | ||
| 1436 | c.succeeds_with(0, true, None)?; | ||
| 1437 | p.expect("finding commits...\r\n")?; | ||
| 1438 | p.expect("updated proposal available (2 ahead 0 behind 'main'). existing version is 2 ahead 1 behind 'main'\r\n")?; | ||
| 1439 | // its got here but tmpgit-32.. indicates that creatubg the eature | ||
| 1440 | // branch in line 1291 didnt work so the choices will be different | ||
| 1441 | let mut c = p.expect_choice( | ||
| 1442 | "", | ||
| 1443 | vec![ | ||
| 1444 | format!("checkout and overwrite existing proposal branch"), | ||
| 1445 | format!("checkout existing outdated proposal branch"), | ||
| 1446 | format!("apply to current branch with `git am`"), | ||
| 1447 | format!("download to ./patches"), | ||
| 1448 | format!("back"), | ||
| 1449 | ], | ||
| 1450 | )?; | ||
| 1451 | c.succeeds_with(0, false, Some(0))?; | ||
| 1452 | p.expect("checked out new version of proposal (2 ahead 0 behind 'main'), replacing old version (2 ahead 1 behind 'main')\r\n")?; | ||
| 1453 | p.expect_end()?; | ||
| 1454 | |||
| 1455 | for p in [51, 52, 53, 55, 56] { | ||
| 1456 | relay::shutdown_relay(8000 + p)?; | ||
| 1457 | } | ||
| 1458 | Ok(()) | ||
| 1459 | }); | ||
| 1460 | |||
| 1461 | // launch relay | ||
| 1462 | let _ = join!( | ||
| 1463 | r51.listen_until_close(), | ||
| 1464 | r52.listen_until_close(), | ||
| 1465 | r53.listen_until_close(), | ||
| 1466 | r55.listen_until_close(), | ||
| 1467 | r56.listen_until_close(), | ||
| 1468 | ); | ||
| 1469 | cli_tester_handle.join().unwrap()?; | ||
| 1470 | println!("{:?}", r55.events); | ||
| 1471 | Ok(()) | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | #[tokio::test] | ||
| 1475 | #[serial] | ||
| 1476 | async fn prompts_to_choose_from_proposal_titles() -> Result<()> { | ||
| 1477 | let _ = run_async_prompts_to_choose_from_proposal_titles().await; | ||
| 1478 | Ok(()) | ||
| 1479 | } | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | #[tokio::test] | ||
| 1483 | #[serial] | ||
| 1484 | async fn proposal_branch_checked_out() -> Result<()> { | ||
| 1485 | let (_, test_repo) = prep_and_run().await?; | ||
| 1486 | assert_eq!( | ||
| 1487 | FEATURE_BRANCH_NAME_1, | ||
| 1488 | test_repo.get_checked_out_branch_name()?, | ||
| 1489 | ); | ||
| 1490 | Ok(()) | ||
| 1491 | } | ||
| 1492 | |||
| 1493 | #[tokio::test] | ||
| 1494 | #[serial] | ||
| 1495 | async fn proposal_branch_tip_is_most_recent_proposal_revision_tip() -> Result<()> { | ||
| 1496 | let (originating_repo, test_repo) = prep_and_run().await?; | ||
| 1497 | assert_eq!( | ||
| 1498 | originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, | ||
| 1499 | test_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_1)?, | ||
| 1500 | ); | ||
| 1501 | Ok(()) | ||
| 1502 | } | ||
| 1198 | } | 1503 | } |
| 1199 | } | 1504 | } |
| 1200 | } | 1505 | } |