upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-02-22 10:11:39 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-02-22 10:11:39 +0000
commitfdc15cb017b022a3b932ac5a337c649cb63df93c (patch)
treea0883ab2e027042df43c15892f837fffe159cf49
parentea5aa6993d4c906c1703563ddc304c324c4ae079 (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.lock1
-rw-r--r--src/sub_commands/list.rs171
-rw-r--r--src/sub_commands/push.rs35
-rw-r--r--src/sub_commands/send.rs5
-rw-r--r--test_utils/Cargo.toml1
-rw-r--r--tests/list.rs311
6 files changed, 445 insertions, 79 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 41d8882..a4df86f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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
790pub async fn find_commits_for_proposal_root_event( 816pub 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
616pub 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
616pub fn patch_supports_commit_ids(event: &nostr::Event) -> bool { 621pub 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"
10directories = "5.0.1" 10directories = "5.0.1"
11git2 = "0.18.1" 11git2 = "0.18.1"
12nostr = "0.27.0" 12nostr = "0.27.0"
13nostr-sdk = { version = "0.27.0", features = ["blocking"] }
13once_cell = "1.18.0" 14once_cell = "1.18.0"
14rand = "0.8" 15rand = "0.8"
15rexpect = { git = "https://github.com/rust-cli/rexpect.git", rev = "9eb61dd" } 16rexpect = { 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 @@
1use anyhow::Result; 1use anyhow::Result;
2use futures::join; 2use futures::join;
3use nostr_sdk::client::blocking::Client;
3use serial_test::serial; 4use serial_test::serial;
4use test_utils::{git::GitTestRepo, relay::Relay, *}; 5use 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 }