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-13 14:52:24 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-02-13 15:55:54 +0000
commitcf319efc6dcdc6c54564cb84e13218edbf3643fa (patch)
treeccccf807fac6c2ab242b2d6bb322c679ae5b94f7
parent3112576195aef212622d27ad9164336796c1953e (diff)
feat!: nip34 make pr event optional
use first patch as thread root if pr event isn't present. begin renaming pr event to cover letter. fix patch ordering upon creation. patches were in youngest first order which caused: - `PATCH n/t`to be in reverse order - the youngest patch was the marked root - oldest patch replied to the youngest fix finding most recent patch event. when a patch in a set is the most recent it will share a created_at with other patches. previously the first patch recieved from relay in the set would be used. now it finds the first patch with that created_at which isn't also a parent of another patch with the same created_at.
-rw-r--r--src/git.rs5
-rw-r--r--src/sub_commands/prs/create.rs171
-rw-r--r--src/sub_commands/prs/list.rs215
-rw-r--r--src/sub_commands/pull.rs62
-rw-r--r--src/sub_commands/push.rs64
-rw-r--r--tests/prs_create.rs483
-rw-r--r--tests/prs_list.rs241
7 files changed, 853 insertions, 388 deletions
diff --git a/src/git.rs b/src/git.rs
index 24afe76..cd42724 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -1265,6 +1265,7 @@ mod tests {
1265 &RepoRef::try_from(generate_repo_ref_event()).unwrap(), 1265 &RepoRef::try_from(generate_repo_ref_event()).unwrap(),
1266 None, 1266 None,
1267 None, 1267 None,
1268 None,
1268 ) 1269 )
1269 } 1270 }
1270 fn test_patch_applies_to_repository(patch_event: nostr::Event) -> Result<()> { 1271 fn test_patch_applies_to_repository(patch_event: nostr::Event) -> Result<()> {
@@ -1418,9 +1419,7 @@ mod tests {
1418 let git_repo = Repo::from_path(&original_repo.dir)?; 1419 let git_repo = Repo::from_path(&original_repo.dir)?;
1419 1420
1420 let mut events = generate_pr_and_patch_events( 1421 let mut events = generate_pr_and_patch_events(
1421 // Some(("test".to_string(), "test".to_string())), 1422 Some(("test".to_string(), "test".to_string())),
1422 "title",
1423 "description",
1424 &git_repo, 1423 &git_repo,
1425 &vec![oid_to_sha1(&oid1), oid_to_sha1(&oid2), oid_to_sha1(&oid3)], 1424 &vec![oid_to_sha1(&oid1), oid_to_sha1(&oid2), oid_to_sha1(&oid3)],
1426 &TEST_KEY_1_KEYS, 1425 &TEST_KEY_1_KEYS,
diff --git a/src/sub_commands/prs/create.rs b/src/sub_commands/prs/create.rs
index 83a3942..e5a7c1e 100644
--- a/src/sub_commands/prs/create.rs
+++ b/src/sub_commands/prs/create.rs
@@ -5,6 +5,7 @@ use futures::future::join_all;
5use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 5use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
6use nostr::{prelude::sha1::Hash as Sha1Hash, EventBuilder, Marker, Tag, TagKind}; 6use nostr::{prelude::sha1::Hash as Sha1Hash, EventBuilder, Marker, Tag, TagKind};
7 7
8use super::list::tag_value;
8#[cfg(not(test))] 9#[cfg(not(test))]
9use crate::client::Client; 10use crate::client::Client;
10#[cfg(test)] 11#[cfg(test)]
@@ -32,6 +33,9 @@ pub struct SubCommandArgs {
32 #[clap(long)] 33 #[clap(long)]
33 /// destination branch (defaults to main or master) 34 /// destination branch (defaults to main or master)
34 to_branch: Option<String>, 35 to_branch: Option<String>,
36 /// don't ask about a cover letter
37 #[arg(long, action)]
38 no_cover_letter: bool,
35} 39}
36 40
37#[allow(clippy::too_many_lines)] 41#[allow(clippy::too_many_lines)]
@@ -42,7 +46,7 @@ pub async fn launch(
42) -> Result<()> { 46) -> Result<()> {
43 let git_repo = Repo::discover().context("cannot find a git repository")?; 47 let git_repo = Repo::discover().context("cannot find a git repository")?;
44 48
45 let (from_branch, to_branch, ahead, behind) = 49 let (from_branch, to_branch, mut ahead, behind) =
46 identify_ahead_behind(&git_repo, &args.from_branch, &args.to_branch)?; 50 identify_ahead_behind(&git_repo, &args.from_branch, &args.to_branch)?;
47 51
48 if ahead.is_empty() { 52 if ahead.is_empty() {
@@ -79,34 +83,44 @@ pub async fn launch(
79 ); 83 );
80 } 84 }
81 85
82 let title = match &args.title { 86 let title = if args.no_cover_letter {
83 Some(t) => t.clone(), 87 None
84 None => Interactor::default() 88 } else {
85 .input(PromptInputParms::default().with_prompt("title"))? 89 match &args.title {
86 .clone(), 90 Some(t) => Some(t.clone()),
91 None => {
92 if Interactor::default().confirm(
93 PromptConfirmParms::default()
94 .with_default(false)
95 .with_prompt("include cover letter?"),
96 )? {
97 Some(
98 Interactor::default()
99 .input(PromptInputParms::default().with_prompt("title"))?
100 .clone(),
101 )
102 } else {
103 None
104 }
105 }
106 }
87 }; 107 };
88 108
89 let description = match &args.description { 109 let cover_letter_title_description = if let Some(title) = title {
90 Some(t) => t.clone(), 110 Some((
91 None => Interactor::default() 111 title,
92 .input(PromptInputParms::default().with_prompt("description (Optional)"))?, 112 if let Some(t) = &args.description {
113 t.clone()
114 } else {
115 Interactor::default()
116 .input(PromptInputParms::default().with_prompt("cover letter description"))?
117 .clone()
118 },
119 ))
120 } else {
121 None
93 }; 122 };
94 123
95 // let cover_letter_title_description = if let Some(title) = title {
96 // Some((
97 // title,
98 // if let Some(t) = &args.description {
99 // t.clone()
100 // } else {
101 // Interactor::default()
102 // .input(PromptInputParms::default().with_prompt("cover letter
103 // description"))? .clone()
104 // },
105 // ))
106 // } else {
107 // None
108 // };
109
110 #[cfg(not(test))] 124 #[cfg(not(test))]
111 let mut client = Client::default(); 125 let mut client = Client::default();
112 #[cfg(test)] 126 #[cfg(test)]
@@ -127,10 +141,11 @@ pub async fn launch(
127 ) 141 )
128 .await?; 142 .await?;
129 143
144 // oldest first
145 ahead.reverse();
146
130 let events = generate_pr_and_patch_events( 147 let events = generate_pr_and_patch_events(
131 // cover_letter_title_description, 148 cover_letter_title_description.clone(),
132 &title,
133 &description,
134 &git_repo, 149 &git_repo,
135 &ahead, 150 &ahead,
136 &keys, 151 &keys,
@@ -138,8 +153,17 @@ pub async fn launch(
138 )?; 153 )?;
139 154
140 println!( 155 println!(
141 "posting 1 pull request with {} commits...", 156 "posting {} patches {} a covering letter...",
142 events.len() - 1 157 if cover_letter_title_description.is_none() {
158 events.len()
159 } else {
160 events.len() - 1
161 },
162 if cover_letter_title_description.is_none() {
163 "without"
164 } else {
165 "with"
166 }
143 ); 167 );
144 168
145 send_events( 169 send_events(
@@ -329,9 +353,7 @@ pub static PR_KIND: u64 = 318;
329pub static PATCH_KIND: u64 = 1617; 353pub static PATCH_KIND: u64 = 1617;
330 354
331pub fn generate_pr_and_patch_events( 355pub fn generate_pr_and_patch_events(
332 title: &str, 356 cover_letter_title_description: Option<(String, String)>,
333 description: &str,
334 // cover_letter_title_description: Option<(String, String)>,
335 git_repo: &Repo, 357 git_repo: &Repo,
336 commits: &Vec<Sha1Hash>, 358 commits: &Vec<Sha1Hash>,
337 keys: &nostr::Keys, 359 keys: &nostr::Keys,
@@ -343,8 +365,7 @@ pub fn generate_pr_and_patch_events(
343 365
344 let mut events = vec![]; 366 let mut events = vec![];
345 367
346 // if let Some((title, description)) = cover_letter_title_description { 368 if let Some((title, description)) = cover_letter_title_description {
347 if !title.is_empty() {
348 events.push(EventBuilder::new( 369 events.push(EventBuilder::new(
349 nostr::event::Kind::Custom(PR_KIND), 370 nostr::event::Kind::Custom(PR_KIND),
350 format!( 371 format!(
@@ -400,6 +421,15 @@ pub fn generate_pr_and_patch_events(
400 } else { 421 } else {
401 Some(((i + 1).try_into()?, commits.len().try_into()?)) 422 Some(((i + 1).try_into()?, commits.len().try_into()?))
402 }, 423 },
424 if events.is_empty() {
425 if let Ok(branch_name) = git_repo.get_checked_out_branch_name() {
426 Some(branch_name)
427 } else {
428 None
429 }
430 } else {
431 None
432 },
403 ) 433 )
404 .context("failed to generate patch event")?, 434 .context("failed to generate patch event")?,
405 ); 435 );
@@ -410,36 +440,72 @@ pub fn generate_pr_and_patch_events(
410pub struct CoverLetter { 440pub struct CoverLetter {
411 pub title: String, 441 pub title: String,
412 pub description: String, 442 pub description: String,
413 pub branch_name: Option<String>, 443 pub branch_name: String,
414} 444}
415 445
416fn event_is_cover_letter(event: &nostr::Event) -> bool { 446pub fn event_is_cover_letter(event: &nostr::Event) -> bool {
417 event.kind.as_u64().eq(&PR_KIND) && event.iter_tags().any(|t| t.as_vec()[1].eq("cover-letter")) 447 event.kind.as_u64().eq(&PR_KIND) && event.iter_tags().any(|t| t.as_vec()[1].eq("cover-letter"))
418} 448}
419pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> { 449pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> {
420 if !event_is_cover_letter(event) { 450 if !event_is_patch_set_root(event) {
421 bail!("event is not a cover letter") 451 bail!("event is not a patch set root event (root patch or cover letter)")
422 } 452 }
423 let title_index = event 453 let title_index = event
424 .content 454 .content
425 .find("] ") 455 .find("] ")
426 .context("event is not formatted as a cover letter patch")? 456 .context("event is not formatted as a patch or cover letter")?
427 + 2; 457 + 2;
428 let description_index = event.content[title_index..] 458 let description_index = event.content[title_index..]
429 .find('\n') 459 .find('\n')
430 .unwrap_or(event.content.len() - 1 - title_index) 460 .unwrap_or(event.content.len() - 1 - title_index)
431 + title_index; 461 + title_index;
432 462
463 let title = if let Ok(msg) = tag_value(event, "description") {
464 msg.split('\n').collect::<Vec<&str>>()[0].to_string()
465 } else {
466 event.content[title_index..description_index].to_string()
467 };
468
469 // note: if the description field is removed from patch events like in gitstr,
470 // then this will show entire patch. I'm not sure it is ever displayed though
471 let description = if let Ok(msg) = tag_value(event, "description") {
472 if let Some((_before, after)) = msg.split_once('\n') {
473 after.trim().to_string()
474 } else {
475 String::new()
476 }
477 } else {
478 event.content[description_index..].trim().to_string()
479 };
480
433 Ok(CoverLetter { 481 Ok(CoverLetter {
434 title: event.content[title_index..description_index].to_string(), 482 title: title.clone(),
435 description: event.content[description_index..].trim().to_string(), 483 description,
436 branch_name: event 484 // TODO should this be prefixed by format!("{}-"e.id.to_string()[..5]?)
437 .iter_tags() 485 branch_name: if let Ok(name) = tag_value(event, "branch-name") {
438 .find(|t| t.as_vec()[0].eq("branch-name")) 486 name
439 .map(|tag| tag.as_vec()[1].clone()), 487 } else {
488 let s = title
489 .replace(' ', "-")
490 .chars()
491 .map(|c| {
492 if c.is_ascii_alphanumeric() || c.eq(&'/') {
493 c
494 } else {
495 '-'
496 }
497 })
498 .collect();
499 s
500 },
440 }) 501 })
441} 502}
442 503
504pub fn event_is_patch_set_root(event: &nostr::Event) -> bool {
505 (event.kind.as_u64().eq(&PR_KIND) || event.kind.as_u64().eq(&PATCH_KIND))
506 && event.iter_tags().any(|t| t.as_vec()[1].eq("root"))
507}
508
443#[allow(clippy::too_many_arguments)] 509#[allow(clippy::too_many_arguments)]
444pub fn generate_patch_event( 510pub fn generate_patch_event(
445 git_repo: &Repo, 511 git_repo: &Repo,
@@ -450,6 +516,7 @@ pub fn generate_patch_event(
450 repo_ref: &RepoRef, 516 repo_ref: &RepoRef,
451 parent_patch_event_id: Option<nostr::EventId>, 517 parent_patch_event_id: Option<nostr::EventId>,
452 series_count: Option<(u64, u64)>, 518 series_count: Option<(u64, u64)>,
519 branch_name: Option<String>,
453) -> Result<nostr::Event> { 520) -> Result<nostr::Event> {
454 let commit_parent = git_repo 521 let commit_parent = git_repo
455 .get_commit_parent(commit) 522 .get_commit_parent(commit)
@@ -496,6 +563,18 @@ pub fn generate_patch_event(
496 } else { 563 } else {
497 vec![] 564 vec![]
498 }, 565 },
566 if let Some(branch_name) = branch_name {
567 if thread_event_id.is_none() {
568 vec![
569 Tag::Generic(
570 TagKind::Custom("branch-name".to_string()),
571 vec![branch_name.to_string()],
572 )
573 ]
574 }
575 else { vec![]}
576 }
577 else { vec![]},
499 // whilst it is in nip34 draft to tag the maintainers 578 // whilst it is in nip34 draft to tag the maintainers
500 // I'm not sure it is a good idea because if they are 579 // I'm not sure it is a good idea because if they are
501 // interested in all patches then their specialised 580 // interested in all patches then their specialised
diff --git a/src/sub_commands/prs/list.rs b/src/sub_commands/prs/list.rs
index bc85eed..36cbd02 100644
--- a/src/sub_commands/prs/list.rs
+++ b/src/sub_commands/prs/list.rs
@@ -1,5 +1,6 @@
1use anyhow::{bail, Context, Result}; 1use anyhow::{bail, Context, Result};
2 2
3use super::create::event_is_patch_set_root;
3#[cfg(not(test))] 4#[cfg(not(test))]
4use crate::client::Client; 5use crate::client::Client;
5#[cfg(test)] 6#[cfg(test)]
@@ -8,8 +9,10 @@ use crate::{
8 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, 9 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms},
9 client::Connect, 10 client::Connect,
10 git::{Repo, RepoActions}, 11 git::{Repo, RepoActions},
11 repo_ref::{self}, 12 repo_ref::{self, RepoRef, REPO_REF_KIND},
12 sub_commands::prs::create::{event_to_cover_letter, PATCH_KIND, PR_KIND}, 13 sub_commands::prs::create::{
14 event_is_cover_letter, event_to_cover_letter, PATCH_KIND, PR_KIND,
15 },
13 Cli, 16 Cli,
14}; 17};
15 18
@@ -51,40 +54,8 @@ pub async fn launch(
51 54
52 println!("finding PRs..."); 55 println!("finding PRs...");
53 56
54 let pr_events: Vec<nostr::Event> = client 57 let pr_events: Vec<nostr::Event> =
55 .get_events( 58 find_pr_events(&client, &repo_ref, &root_commit.to_string()).await?;
56 repo_ref.relays.clone(),
57 vec![
58 nostr::Filter::default()
59 .kind(nostr::Kind::Custom(PR_KIND))
60 .reference(format!("{root_commit}")),
61 ],
62 )
63 .await?
64 .iter()
65 .filter(|e| {
66 e.kind.as_u64() == PR_KIND
67 && e.tags
68 .iter()
69 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("{root_commit}")))
70 })
71 .map(std::borrow::ToOwned::to_owned)
72 .collect();
73
74 // let pr_branch_names: Vec<String> = pr_events
75 // .iter()
76 // .map(|e| {
77 // format!(
78 // "{}-{}",
79 // &e.id.to_string()[..5],
80 // if let Some(t) = e.tags.iter().find(|t| t.as_vec()[0] ==
81 // "branch-name") { t.as_vec()[1].to_string()
82 // } else {
83 // "".to_string()
84 // } // git_repo.get_checked_out_branch_name(),
85 // )
86 // })
87 // .collect();
88 59
89 let selected_index = Interactor::default().choice( 60 let selected_index = Interactor::default().choice(
90 PromptChoiceParms::default() 61 PromptChoiceParms::default()
@@ -95,6 +66,8 @@ pub async fn launch(
95 .map(|e| { 66 .map(|e| {
96 if let Ok(cl) = event_to_cover_letter(e) { 67 if let Ok(cl) = event_to_cover_letter(e) {
97 cl.title 68 cl.title
69 } else if let Ok(msg) = tag_value(e, "description") {
70 msg.split('\n').collect::<Vec<&str>>()[0].to_string()
98 } else { 71 } else {
99 e.id.to_string() 72 e.id.to_string()
100 } 73 }
@@ -102,49 +75,20 @@ pub async fn launch(
102 .collect(), 75 .collect(),
103 ), 76 ),
104 )?; 77 )?;
105 // println!("prs:{:?}", &pr_events);
106 78
107 println!("finding commits..."); 79 println!("finding commits...");
108 80
109 let commits_events: Vec<nostr::Event> = client 81 let commits_events: Vec<nostr::Event> =
110 .get_events( 82 find_commits_for_pr_event(&client, &pr_events[selected_index], &repo_ref).await?;
111 repo_ref.relays.clone(),
112 vec![
113 nostr::Filter::default()
114 .kind(nostr::Kind::Custom(PATCH_KIND))
115 .event(pr_events[selected_index].id),
116 ],
117 )
118 .await?
119 .iter()
120 .filter(|e| {
121 e.kind.as_u64() == PATCH_KIND
122 && e.tags.iter().any(|t| {
123 t.as_vec().len() > 2
124 && t.as_vec()[1].eq(&pr_events[selected_index].id.to_string())
125 })
126 })
127 .map(std::borrow::ToOwned::to_owned)
128 .collect();
129 83
130 confirm_checkout(&git_repo)?; 84 confirm_checkout(&git_repo)?;
131 85
132 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commits_events) 86 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commits_events)
133 .context("cannot get most recent patch for PR")?; 87 .context("cannot get most recent patch for PR")?;
134 88
135 let branch_name: String = if let Ok(cl) = event_to_cover_letter(&pr_events[selected_index]) { 89 let branch_name: String = event_to_cover_letter(&pr_events[selected_index])
136 if let Some(name) = cl.branch_name { 90 .context("cannot assign a branch name as event is not a patch set root")?
137 name 91 .branch_name;
138 } else {
139 cl.title
140 .replace(' ', "-")
141 .chars()
142 .filter(|c| c.is_ascii_alphanumeric() || c.eq(&'/'))
143 .collect()
144 }
145 } else {
146 bail!("Placeholder not a cover letter")
147 };
148 92
149 let applied = git_repo 93 let applied = git_repo
150 .apply_patch_chain(&branch_name, most_recent_pr_patch_chain) 94 .apply_patch_chain(&branch_name, most_recent_pr_patch_chain)
@@ -193,20 +137,139 @@ pub fn get_most_recent_patch_with_ancestors(
193) -> Result<Vec<nostr::Event>> { 137) -> Result<Vec<nostr::Event>> {
194 patches.sort_by_key(|e| e.created_at); 138 patches.sort_by_key(|e| e.created_at);
195 139
196 let mut res = vec![]; 140 let first_patch = patches.first().context("no patches found")?;
197 141
198 let latest_commit_id = tag_value(patches.first().context("no patches found")?, "commit")?; 142 let patches_with_youngest_created_at: Vec<&nostr::Event> = patches
143 .iter()
144 .filter(|p| p.created_at.eq(&first_patch.created_at))
145 .collect();
146
147 let latest_commit_id = tag_value(
148 // get the first patch which isn't a parent of a patch event created at the same
149 // time
150 patches_with_youngest_created_at
151 .clone()
152 .iter()
153 .find(|p| {
154 if let Ok(commit) = tag_value(p, "commit") {
155 !patches_with_youngest_created_at.iter().any(|p2| {
156 if let Ok(parent) = tag_value(p2, "parent-commit") {
157 commit.eq(&parent)
158 } else {
159 false // skip
160 }
161 })
162 } else {
163 false // skip
164 }
165 })
166 .context("cannot find patches_with_youngest_created_at")?,
167 "commit",
168 )?;
169
170 let mut res = vec![];
199 171
200 let mut commit_id_to_search = latest_commit_id; 172 let mut commit_id_to_search = latest_commit_id;
201 173
202 while let Some(event) = patches.iter().find(|e| { 174 while let Some(event) = patches.iter().find(|e| {
203 tag_value(e, "commit") 175 if let Ok(commit) = tag_value(e, "commit") {
204 .context("patch event doesnt contain commit tag") 176 commit.eq(&commit_id_to_search)
205 .unwrap() 177 } else {
206 .eq(&commit_id_to_search) 178 false // skip
179 }
207 }) { 180 }) {
208 res.push(event.clone()); 181 res.push(event.clone());
209 commit_id_to_search = tag_value(event, "parent-commit")?; 182 commit_id_to_search = tag_value(event, "parent-commit")?;
210 } 183 }
211 Ok(res) 184 Ok(res)
212} 185}
186
187pub async fn find_pr_events(
188 #[cfg(test)] client: &crate::client::MockConnect,
189 #[cfg(not(test))] client: &Client,
190 repo_ref: &RepoRef,
191 root_commit: &str,
192) -> Result<Vec<nostr::Event>> {
193 Ok(client
194 .get_events(
195 repo_ref.relays.clone(),
196 vec![
197 nostr::Filter::default()
198 .kinds(vec![
199 nostr::Kind::Custom(PR_KIND),
200 nostr::Kind::Custom(PATCH_KIND),
201 ])
202 .custom_tag(nostr::Alphabet::T, vec!["root"])
203 .identifiers(
204 repo_ref
205 .maintainers
206 .iter()
207 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)),
208 ),
209 // also pick up prs from the same repo but no target at our maintainers repo events
210 nostr::Filter::default()
211 .kinds(vec![
212 nostr::Kind::Custom(PR_KIND),
213 nostr::Kind::Custom(PATCH_KIND),
214 ])
215 .custom_tag(nostr::Alphabet::T, vec!["root"])
216 .reference(root_commit),
217 ],
218 )
219 .await
220 .context("cannot get pr events")?
221 .iter()
222 .filter(|e| {
223 event_is_patch_set_root(e)
224 && (e
225 .tags
226 .iter()
227 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(root_commit))
228 || e.tags.iter().any(|t| {
229 t.as_vec().len() > 1
230 && repo_ref
231 .maintainers
232 .iter()
233 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier))
234 .any(|d| t.as_vec()[1].eq(&d))
235 }))
236 })
237 .map(std::borrow::ToOwned::to_owned)
238 .collect::<Vec<nostr::Event>>())
239}
240
241pub async fn find_commits_for_pr_event(
242 #[cfg(test)] client: &crate::client::MockConnect,
243 #[cfg(not(test))] client: &Client,
244 pr_event: &nostr::Event,
245 repo_ref: &RepoRef,
246) -> Result<Vec<nostr::Event>> {
247 let mut patch_events: Vec<nostr::Event> = client
248 .get_events(
249 repo_ref.relays.clone(),
250 vec![
251 nostr::Filter::default()
252 .kind(nostr::Kind::Custom(PATCH_KIND))
253 // this requires every patch to reference the root event
254 // this will not pick up v2,v3 patch sets
255 // TODO: fetch commits for v2.. patch sets
256 .event(pr_event.id),
257 ],
258 )
259 .await
260 .context("cannot fetch patch events")?
261 .iter()
262 .filter(|e| {
263 e.kind.as_u64() == PATCH_KIND
264 && e.tags
265 .iter()
266 .any(|t| t.as_vec().len() > 2 && t.as_vec()[1].eq(&pr_event.id.to_string()))
267 })
268 .map(std::borrow::ToOwned::to_owned)
269 .collect();
270
271 if !event_is_cover_letter(pr_event) {
272 patch_events.push(pr_event.clone());
273 }
274 Ok(patch_events)
275}
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs
index f3ae81f..2b20d3d 100644
--- a/src/sub_commands/pull.rs
+++ b/src/sub_commands/pull.rs
@@ -8,10 +8,8 @@ use crate::{
8 client::Connect, 8 client::Connect,
9 git::{Repo, RepoActions}, 9 git::{Repo, RepoActions},
10 repo_ref, 10 repo_ref,
11 repo_ref::REPO_REF_KIND, 11 sub_commands::{
12 sub_commands::prs::{ 12 prs::list::get_most_recent_patch_with_ancestors, push::fetch_pr_and_most_recent_patch_chain,
13 create::{PATCH_KIND, PR_KIND},
14 list::{get_most_recent_patch_with_ancestors, tag_value},
15 }, 13 },
16}; 14};
17 15
@@ -46,63 +44,15 @@ pub async fn launch() -> Result<()> {
46 ) 44 )
47 .await?; 45 .await?;
48 46
49 println!("finding PR event..."); 47 let (_pr_event, commit_events) =
50 48 fetch_pr_and_most_recent_patch_chain(&client, &repo_ref, &root_commit, &branch_name)
51 let pr_event: nostr::Event = client 49 .await?;
52 .get_events(
53 repo_ref.relays.clone(),
54 vec![
55 nostr::Filter::default()
56 .kind(nostr::Kind::Custom(PR_KIND))
57 .identifiers(
58 repo_ref
59 .maintainers
60 .iter()
61 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)),
62 ),
63 ],
64 )
65 .await?
66 .iter()
67 .find(|e| {
68 e.kind.as_u64() == PR_KIND
69 && e.tags
70 .iter()
71 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("{root_commit}")))
72 && tag_value(e, "branch-name")
73 .unwrap_or_default()
74 .eq(&branch_name)
75 })
76 .context("cannot find a PR event associated with the checked out branch name")?
77 .to_owned();
78
79 println!("found PR event. finding commits...");
80
81 let commits_events: Vec<nostr::Event> = client
82 .get_events(
83 repo_ref.relays.clone(),
84 vec![
85 nostr::Filter::default()
86 .kind(nostr::Kind::Custom(PATCH_KIND))
87 .event(pr_event.id),
88 ],
89 )
90 .await?
91 .iter()
92 .filter(|e| {
93 e.kind.as_u64() == PATCH_KIND
94 && e.tags
95 .iter()
96 .any(|t| t.as_vec().len() > 2 && t.as_vec()[1].eq(&pr_event.id.to_string()))
97 })
98 .map(std::borrow::ToOwned::to_owned)
99 .collect();
100 50
101 if git_repo.has_outstanding_changes()? { 51 if git_repo.has_outstanding_changes()? {
102 bail!("cannot pull changes when repository is not clean. discard changes and try again."); 52 bail!("cannot pull changes when repository is not clean. discard changes and try again.");
103 } 53 }
104 54
105 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commits_events) 55 let most_recent_pr_patch_chain = get_most_recent_patch_with_ancestors(commit_events)
106 .context("cannot get most recent patch for PR")?; 56 .context("cannot get most recent patch for PR")?;
107 57
108 let applied = git_repo 58 let applied = git_repo
diff --git a/src/sub_commands/push.rs b/src/sub_commands/push.rs
index 61d5d46..eb42699 100644
--- a/src/sub_commands/push.rs
+++ b/src/sub_commands/push.rs
@@ -9,10 +9,13 @@ use crate::{
9 client::Connect, 9 client::Connect,
10 git::{str_to_sha1, Repo, RepoActions}, 10 git::{str_to_sha1, Repo, RepoActions},
11 login, 11 login,
12 repo_ref::{self, RepoRef, REPO_REF_KIND}, 12 repo_ref::{self, RepoRef},
13 sub_commands::prs::{ 13 sub_commands::prs::{
14 create::{generate_patch_event, send_events, PATCH_KIND, PR_KIND}, 14 create::{event_to_cover_letter, generate_patch_event, send_events},
15 list::{get_most_recent_patch_with_ancestors, tag_value}, 15 list::{
16 find_commits_for_pr_event, find_pr_events, get_most_recent_patch_with_ancestors,
17 tag_value,
18 },
16 }, 19 },
17 Cli, 20 Cli,
18}; 21};
@@ -111,6 +114,7 @@ pub async fn launch(cli_args: &Cli) -> Result<()> {
111 &repo_ref, 114 &repo_ref,
112 patch_events.last().map(nostr::Event::id), 115 patch_events.last().map(nostr::Event::id),
113 None, 116 None,
117 None,
114 ) 118 )
115 .context("cannot make patch event from commit")?, 119 .context("cannot make patch event from commit")?,
116 ); 120 );
@@ -131,7 +135,7 @@ pub async fn launch(cli_args: &Cli) -> Result<()> {
131 Ok(()) 135 Ok(())
132} 136}
133 137
134async fn fetch_pr_and_most_recent_patch_chain( 138pub async fn fetch_pr_and_most_recent_patch_chain(
135 #[cfg(test)] client: &crate::client::MockConnect, 139 #[cfg(test)] client: &crate::client::MockConnect,
136 #[cfg(not(test))] client: &Client, 140 #[cfg(not(test))] client: &Client,
137 repo_ref: &RepoRef, 141 repo_ref: &RepoRef,
@@ -140,54 +144,24 @@ async fn fetch_pr_and_most_recent_patch_chain(
140) -> Result<(nostr::Event, Vec<nostr::Event>)> { 144) -> Result<(nostr::Event, Vec<nostr::Event>)> {
141 println!("finding PR event..."); 145 println!("finding PR event...");
142 146
143 let pr_event: nostr::Event = client 147 let pr_events: Vec<nostr::Event> = find_pr_events(client, repo_ref, &root_commit.to_string())
144 .get_events( 148 .await
145 repo_ref.relays.clone(), 149 .context("cannot get pr events for repo")?;
146 vec![ 150
147 nostr::Filter::default() 151 let pr_event: nostr::Event = pr_events
148 .kind(nostr::Kind::Custom(PR_KIND))
149 .identifiers(
150 repo_ref
151 .maintainers
152 .iter()
153 .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)),
154 ),
155 ],
156 )
157 .await?
158 .iter() 152 .iter()
159 .find(|e| { 153 .find(|e| {
160 e.kind.as_u64() == PR_KIND 154 event_to_cover_letter(e).is_ok_and(|cl| cl.branch_name.eq(branch_name))
161 && e.tags 155 // TODO remove the dependancy on same branch name and replace with
162 .iter() 156 // references stored in .git/ngit
163 .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(&format!("{root_commit}")))
164 && tag_value(e, "branch-name")
165 .unwrap_or_default()
166 .eq(branch_name)
167 }) 157 })
168 .context("cannot find a PR event associated with the checked out branch name")? 158 .context("cannot find a PR event associated with the checked out branch name")?
169 .to_owned(); 159 .to_owned();
170 160
171 println!("found PR event. finding commits..."); 161 println!("found PR event. finding commits...");
172 162
173 let commits_events: Vec<nostr::Event> = client 163 let commits_events: Vec<nostr::Event> =
174 .get_events( 164 find_commits_for_pr_event(client, &pr_event, repo_ref).await?;
175 repo_ref.relays.clone(), 165
176 vec![
177 nostr::Filter::default()
178 .kind(nostr::Kind::Custom(PATCH_KIND))
179 .event(pr_event.id),
180 ],
181 )
182 .await?
183 .iter()
184 .filter(|e| {
185 e.kind.as_u64() == PATCH_KIND
186 && e.tags
187 .iter()
188 .any(|t| t.as_vec().len() > 2 && t.as_vec()[1].eq(&pr_event.id.to_string()))
189 })
190 .map(std::borrow::ToOwned::to_owned)
191 .collect();
192 Ok((pr_event, commits_events)) 166 Ok((pr_event, commits_events))
193} 167}
diff --git a/tests/prs_create.rs b/tests/prs_create.rs
index 6272ccd..316c9fe 100644
--- a/tests/prs_create.rs
+++ b/tests/prs_create.rs
@@ -1,6 +1,7 @@
1use anyhow::Result; 1use anyhow::Result;
2use futures::join;
2use serial_test::serial; 3use serial_test::serial;
3use test_utils::{git::GitTestRepo, *}; 4use test_utils::{git::GitTestRepo, relay::Relay, *};
4 5
5#[test] 6#[test]
6fn when_to_branch_doesnt_exist_return_error() -> Result<()> { 7fn when_to_branch_doesnt_exist_return_error() -> Result<()> {
@@ -150,121 +151,133 @@ fn is_patch(event: &nostr::Event) -> bool {
150 && !event.iter_tags().any(|t| t.as_vec()[1].eq("cover-letter")) 151 && !event.iter_tags().any(|t| t.as_vec()[1].eq("cover-letter"))
151} 152}
152 153
153mod sends_pr_and_2_patches_to_3_relays { 154fn prep_git_repo() -> Result<GitTestRepo> {
154 use futures::join; 155 let test_repo = GitTestRepo::default();
155 use test_utils::relay::Relay; 156 test_repo.populate()?;
156 157 // create feature branch with 2 commit ahead
157 use super::*; 158 test_repo.create_branch("feature")?;
159 test_repo.checkout("feature")?;
160 std::fs::write(test_repo.dir.join("t3.md"), "some content")?;
161 test_repo.stage_and_commit("add t3.md")?;
162 std::fs::write(test_repo.dir.join("t4.md"), "some content")?;
163 test_repo.stage_and_commit("add t4.md")?;
164 Ok(test_repo)
165}
158 166
159 fn prep_git_repo() -> Result<GitTestRepo> { 167fn cli_tester_create_pr(git_repo: &GitTestRepo, include_cover_letter: bool) -> CliTester {
160 let test_repo = GitTestRepo::default(); 168 let mut args = vec![
161 test_repo.populate()?; 169 "--nsec",
162 // create feature branch with 2 commit ahead 170 TEST_KEY_1_NSEC,
163 test_repo.create_branch("feature")?; 171 "--password",
164 test_repo.checkout("feature")?; 172 TEST_PASSWORD,
165 std::fs::write(test_repo.dir.join("t3.md"), "some content")?; 173 "--disable-cli-spinners",
166 test_repo.stage_and_commit("add t3.md")?; 174 "prs",
167 std::fs::write(test_repo.dir.join("t4.md"), "some content")?; 175 "create",
168 test_repo.stage_and_commit("add t4.md")?; 176 ];
169 Ok(test_repo) 177 if include_cover_letter {
178 for arg in [
179 "--title",
180 "exampletitle",
181 "--description",
182 "exampledescription",
183 ] {
184 args.push(arg);
185 }
186 } else {
187 args.push("--no-cover-letter");
170 } 188 }
189 CliTester::new_from_dir(&git_repo.dir, args)
190}
171 191
172 fn cli_tester_create_pr(git_repo: &GitTestRepo) -> CliTester { 192fn expect_msgs_first(p: &mut CliTester, include_cover_letter: bool) -> Result<()> {
173 CliTester::new_from_dir( 193 p.expect("creating patch for 2 commits from 'head' that can be merged into 'main'\r\n")?;
174 &git_repo.dir, 194 p.expect("searching for your details...\r\n")?;
175 [ 195 p.expect("\r")?;
176 "--nsec", 196 p.expect("logged in as fred\r\n")?;
177 TEST_KEY_1_NSEC, 197 p.expect(format!(
178 "--password", 198 "posting 2 patches {} a covering letter...\r\n",
179 TEST_PASSWORD, 199 if include_cover_letter {
180 "--disable-cli-spinners", 200 "with"
181 "prs", 201 } else {
182 "create", 202 "without"
183 "--title", 203 }
184 "exampletitle", 204 ))?;
185 "--description", 205 Ok(())
186 "exampledescription", 206}
187 ],
188 )
189 }
190 207
191 fn expect_msgs_first(p: &mut CliTester) -> Result<()> { 208async fn prep_run_create_pr(
192 p.expect("creating patch for 2 commits from 'head' that can be merged into 'main'\r\n")?; 209 include_cover_letter: bool,
193 p.expect("searching for your details...\r\n")?; 210) -> Result<(
194 p.expect("\r")?; 211 Relay<'static>,
195 p.expect("logged in as fred\r\n")?; 212 Relay<'static>,
196 p.expect("posting 1 pull request with 2 commits...\r\n")?; 213 Relay<'static>,
214 Relay<'static>,
215 Relay<'static>,
216)> {
217 let git_repo = prep_git_repo()?;
218 // fallback (51,52) user write (53, 55) repo (55, 56)
219 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
220 Relay::new(
221 8051,
222 None,
223 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
224 relay.respond_events(
225 client_id,
226 &subscription_id,
227 &vec![
228 generate_test_key_1_metadata_event("fred"),
229 generate_test_key_1_relay_list_event(),
230 ],
231 )?;
232 Ok(())
233 }),
234 ),
235 Relay::new(8052, None, None),
236 Relay::new(8053, None, None),
237 Relay::new(
238 8055,
239 None,
240 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
241 relay.respond_events(
242 client_id,
243 &subscription_id,
244 &vec![generate_repo_ref_event()],
245 )?;
246 Ok(())
247 }),
248 ),
249 Relay::new(8056, None, None),
250 );
251
252 // // check relay had the right number of events
253 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
254 let mut p = cli_tester_create_pr(&git_repo, include_cover_letter);
255 p.expect_end_eventually()?;
256 for p in [51, 52, 53, 55, 56] {
257 relay::shutdown_relay(8000 + p)?;
258 }
197 Ok(()) 259 Ok(())
198 } 260 });
261
262 // launch relay
263 let _ = join!(
264 r51.listen_until_close(),
265 r52.listen_until_close(),
266 r53.listen_until_close(),
267 r55.listen_until_close(),
268 r56.listen_until_close(),
269 );
270 cli_tester_handle.join().unwrap()?;
271 Ok((r51, r52, r53, r55, r56))
272}
199 273
200 async fn prep_run_create_pr() -> Result<( 274mod sends_cover_letter_and_2_patches_to_3_relays {
201 Relay<'static>,
202 Relay<'static>,
203 Relay<'static>,
204 Relay<'static>,
205 Relay<'static>,
206 )> {
207 let git_repo = prep_git_repo()?;
208 // fallback (51,52) user write (53, 55) repo (55, 56)
209 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
210 Relay::new(
211 8051,
212 None,
213 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
214 relay.respond_events(
215 client_id,
216 &subscription_id,
217 &vec![
218 generate_test_key_1_metadata_event("fred"),
219 generate_test_key_1_relay_list_event(),
220 ],
221 )?;
222 Ok(())
223 }),
224 ),
225 Relay::new(8052, None, None),
226 Relay::new(8053, None, None),
227 Relay::new(
228 8055,
229 None,
230 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
231 relay.respond_events(
232 client_id,
233 &subscription_id,
234 &vec![generate_repo_ref_event()],
235 )?;
236 Ok(())
237 }),
238 ),
239 Relay::new(8056, None, None),
240 );
241
242 // // check relay had the right number of events
243 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
244 let mut p = cli_tester_create_pr(&git_repo);
245 p.expect_end_eventually()?;
246 for p in [51, 52, 53, 55, 56] {
247 relay::shutdown_relay(8000 + p)?;
248 }
249 Ok(())
250 });
251
252 // launch relay
253 let _ = join!(
254 r51.listen_until_close(),
255 r52.listen_until_close(),
256 r53.listen_until_close(),
257 r55.listen_until_close(),
258 r56.listen_until_close(),
259 );
260 cli_tester_handle.join().unwrap()?;
261 Ok((r51, r52, r53, r55, r56))
262 }
263 275
276 use super::*;
264 #[tokio::test] 277 #[tokio::test]
265 #[serial] 278 #[serial]
266 async fn only_1_pr_kind_event_sent_to_each_relay() -> Result<()> { 279 async fn only_1_pr_kind_event_sent_to_each_relay() -> Result<()> {
267 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 280 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
268 for relay in [&r53, &r55, &r56] { 281 for relay in [&r53, &r55, &r56] {
269 assert_eq!( 282 assert_eq!(
270 relay.events.iter().filter(|e| is_cover_letter(e)).count(), 283 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
@@ -277,7 +290,7 @@ mod sends_pr_and_2_patches_to_3_relays {
277 #[tokio::test] 290 #[tokio::test]
278 #[serial] 291 #[serial]
279 async fn only_1_pr_kind_event_sent_to_user_relays() -> Result<()> { 292 async fn only_1_pr_kind_event_sent_to_user_relays() -> Result<()> {
280 let (_, _, r53, r55, _) = prep_run_create_pr().await?; 293 let (_, _, r53, r55, _) = prep_run_create_pr(true).await?;
281 for relay in [&r53, &r55] { 294 for relay in [&r53, &r55] {
282 assert_eq!( 295 assert_eq!(
283 relay.events.iter().filter(|e| is_cover_letter(e)).count(), 296 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
@@ -290,7 +303,7 @@ mod sends_pr_and_2_patches_to_3_relays {
290 #[tokio::test] 303 #[tokio::test]
291 #[serial] 304 #[serial]
292 async fn only_1_pr_kind_event_sent_to_repo_relays() -> Result<()> { 305 async fn only_1_pr_kind_event_sent_to_repo_relays() -> Result<()> {
293 let (_, _, _, r55, r56) = prep_run_create_pr().await?; 306 let (_, _, _, r55, r56) = prep_run_create_pr(true).await?;
294 for relay in [&r55, &r56] { 307 for relay in [&r55, &r56] {
295 assert_eq!( 308 assert_eq!(
296 relay.events.iter().filter(|e| is_cover_letter(e)).count(), 309 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
@@ -303,7 +316,7 @@ mod sends_pr_and_2_patches_to_3_relays {
303 #[tokio::test] 316 #[tokio::test]
304 #[serial] 317 #[serial]
305 async fn pr_not_sent_to_fallback_relay() -> Result<()> { 318 async fn pr_not_sent_to_fallback_relay() -> Result<()> {
306 let (r51, r52, _, _, _) = prep_run_create_pr().await?; 319 let (r51, r52, _, _, _) = prep_run_create_pr(true).await?;
307 for relay in [&r51, &r52] { 320 for relay in [&r51, &r52] {
308 assert_eq!( 321 assert_eq!(
309 relay.events.iter().filter(|e| is_cover_letter(e)).count(), 322 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
@@ -316,7 +329,7 @@ mod sends_pr_and_2_patches_to_3_relays {
316 #[tokio::test] 329 #[tokio::test]
317 #[serial] 330 #[serial]
318 async fn only_2_patch_kind_events_sent_to_each_relay() -> Result<()> { 331 async fn only_2_patch_kind_events_sent_to_each_relay() -> Result<()> {
319 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 332 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
320 for relay in [&r53, &r55, &r56] { 333 for relay in [&r53, &r55, &r56] {
321 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2,); 334 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2,);
322 } 335 }
@@ -327,18 +340,18 @@ mod sends_pr_and_2_patches_to_3_relays {
327 #[serial] 340 #[serial]
328 async fn patch_content_contains_patch_in_email_format_with_patch_series_numbers() -> Result<()> 341 async fn patch_content_contains_patch_in_email_format_with_patch_series_numbers() -> Result<()>
329 { 342 {
330 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 343 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
331 for relay in [&r53, &r55, &r56] { 344 for relay in [&r53, &r55, &r56] {
332 let patch_events: Vec<&nostr::Event> = 345 let patch_events: Vec<&nostr::Event> =
333 relay.events.iter().filter(|e| is_patch(e)).collect(); 346 relay.events.iter().filter(|e| is_patch(e)).collect();
334 347
335 assert_eq!( 348 assert_eq!(
336 patch_events[0].content, 349 patch_events[1].content,
337 "\ 350 "\
338 From fe973a840fba2a8ab37dd505c154854a69a6505c Mon Sep 17 00:00:00 2001\n\ 351 From fe973a840fba2a8ab37dd505c154854a69a6505c Mon Sep 17 00:00:00 2001\n\
339 From: Joe Bloggs <joe.bloggs@pm.me>\n\ 352 From: Joe Bloggs <joe.bloggs@pm.me>\n\
340 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ 353 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\
341 Subject: [PATCH 1/2] add t4.md\n\ 354 Subject: [PATCH 2/2] add t4.md\n\
342 \n\ 355 \n\
343 ---\n \ 356 ---\n \
344 t4.md | 1 +\n \ 357 t4.md | 1 +\n \
@@ -359,12 +372,12 @@ mod sends_pr_and_2_patches_to_3_relays {
359 ", 372 ",
360 ); 373 );
361 assert_eq!( 374 assert_eq!(
362 patch_events[1].content, 375 patch_events[0].content,
363 "\ 376 "\
364 From 232efb37ebc67692c9e9ff58b83c0d3d63971a0a Mon Sep 17 00:00:00 2001\n\ 377 From 232efb37ebc67692c9e9ff58b83c0d3d63971a0a Mon Sep 17 00:00:00 2001\n\
365 From: Joe Bloggs <joe.bloggs@pm.me>\n\ 378 From: Joe Bloggs <joe.bloggs@pm.me>\n\
366 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\ 379 Date: Thu, 1 Jan 1970 00:00:00 +0000\n\
367 Subject: [PATCH 2/2] add t3.md\n\ 380 Subject: [PATCH 1/2] add t3.md\n\
368 \n\ 381 \n\
369 ---\n \ 382 ---\n \
370 t3.md | 1 +\n \ 383 t3.md | 1 +\n \
@@ -394,7 +407,7 @@ mod sends_pr_and_2_patches_to_3_relays {
394 #[tokio::test] 407 #[tokio::test]
395 #[serial] 408 #[serial]
396 async fn root_commit_as_r() -> Result<()> { 409 async fn root_commit_as_r() -> Result<()> {
397 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 410 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
398 for relay in [&r53, &r55, &r56] { 411 for relay in [&r53, &r55, &r56] {
399 let pr_event: &nostr::Event = 412 let pr_event: &nostr::Event =
400 relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); 413 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
@@ -414,7 +427,7 @@ mod sends_pr_and_2_patches_to_3_relays {
414 #[tokio::test] 427 #[tokio::test]
415 #[serial] 428 #[serial]
416 async fn a_tag_for_repo_event() -> Result<()> { 429 async fn a_tag_for_repo_event() -> Result<()> {
417 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 430 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
418 for relay in [&r53, &r55, &r56] { 431 for relay in [&r53, &r55, &r56] {
419 let pr_event: &nostr::Event = 432 let pr_event: &nostr::Event =
420 relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); 433 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
@@ -436,7 +449,7 @@ mod sends_pr_and_2_patches_to_3_relays {
436 .unwrap() 449 .unwrap()
437 .as_vec() 450 .as_vec()
438 .clone()[1..]; 451 .clone()[1..];
439 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 452 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
440 for relay in [&r53, &r55, &r56] { 453 for relay in [&r53, &r55, &r56] {
441 for m in maintainers { 454 for m in maintainers {
442 let pr_event: &nostr::Event = 455 let pr_event: &nostr::Event =
@@ -454,7 +467,7 @@ mod sends_pr_and_2_patches_to_3_relays {
454 #[tokio::test] 467 #[tokio::test]
455 #[serial] 468 #[serial]
456 async fn t_tag_cover_letter() -> Result<()> { 469 async fn t_tag_cover_letter() -> Result<()> {
457 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 470 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
458 for relay in [&r53, &r55, &r56] { 471 for relay in [&r53, &r55, &r56] {
459 let pr_event: &nostr::Event = 472 let pr_event: &nostr::Event =
460 relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); 473 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
@@ -470,7 +483,7 @@ mod sends_pr_and_2_patches_to_3_relays {
470 #[tokio::test] 483 #[tokio::test]
471 #[serial] 484 #[serial]
472 async fn t_tag_root() -> Result<()> { 485 async fn t_tag_root() -> Result<()> {
473 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 486 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
474 for relay in [&r53, &r55, &r56] { 487 for relay in [&r53, &r55, &r56] {
475 let pr_event: &nostr::Event = 488 let pr_event: &nostr::Event =
476 relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); 489 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
@@ -486,7 +499,7 @@ mod sends_pr_and_2_patches_to_3_relays {
486 #[tokio::test] 499 #[tokio::test]
487 #[serial] 500 #[serial]
488 async fn pr_tags_branch_name() -> Result<()> { 501 async fn pr_tags_branch_name() -> Result<()> {
489 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 502 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
490 for relay in [&r53, &r55, &r56] { 503 for relay in [&r53, &r55, &r56] {
491 let pr_event: &nostr::Event = 504 let pr_event: &nostr::Event =
492 relay.events.iter().find(|e| is_cover_letter(e)).unwrap(); 505 relay.events.iter().find(|e| is_cover_letter(e)).unwrap();
@@ -509,14 +522,14 @@ mod sends_pr_and_2_patches_to_3_relays {
509 use super::*; 522 use super::*;
510 523
511 async fn prep() -> Result<nostr::Event> { 524 async fn prep() -> Result<nostr::Event> {
512 let (_, _, r53, _, _) = prep_run_create_pr().await?; 525 let (_, _, r53, _, _) = prep_run_create_pr(true).await?;
513 Ok(r53.events.iter().find(|e| is_patch(e)).unwrap().clone()) 526 Ok(r53.events.iter().find(|e| is_patch(e)).unwrap().clone())
514 } 527 }
515 528
516 #[tokio::test] 529 #[tokio::test]
517 #[serial] 530 #[serial]
518 async fn commit_and_commit_r() -> Result<()> { 531 async fn commit_and_commit_r() -> Result<()> {
519 static COMMIT_ID: &str = "fe973a840fba2a8ab37dd505c154854a69a6505c"; 532 static COMMIT_ID: &str = "232efb37ebc67692c9e9ff58b83c0d3d63971a0a";
520 let most_recent_patch = prep().await?; 533 let most_recent_patch = prep().await?;
521 assert!( 534 assert!(
522 most_recent_patch 535 most_recent_patch
@@ -537,12 +550,16 @@ mod sends_pr_and_2_patches_to_3_relays {
537 #[serial] 550 #[serial]
538 async fn parent_commit() -> Result<()> { 551 async fn parent_commit() -> Result<()> {
539 // commit parent 'r' and 'parent-commit' tag 552 // commit parent 'r' and 'parent-commit' tag
540 static COMMIT_PARENT_ID: &str = "232efb37ebc67692c9e9ff58b83c0d3d63971a0a"; 553 static COMMIT_PARENT_ID: &str = "431b84edc0d2fa118d63faa3c2db9c73d630a5ae";
541 let most_recent_patch = prep().await?; 554 let most_recent_patch = prep().await?;
542 assert!( 555 assert_eq!(
543 most_recent_patch.tags.iter().any( 556 most_recent_patch
544 |t| t.as_vec()[0].eq("parent-commit") && t.as_vec()[1].eq(COMMIT_PARENT_ID) 557 .tags
545 ) 558 .iter()
559 .find(|t| t.as_vec()[0].eq("parent-commit"))
560 .unwrap()
561 .as_vec()[1],
562 COMMIT_PARENT_ID,
546 ); 563 );
547 Ok(()) 564 Ok(())
548 } 565 }
@@ -599,7 +616,7 @@ mod sends_pr_and_2_patches_to_3_relays {
599 .find(|t| t.as_vec()[0].eq("description")) 616 .find(|t| t.as_vec()[0].eq("description"))
600 .unwrap() 617 .unwrap()
601 .as_vec()[1], 618 .as_vec()[1],
602 "add t4.md" 619 "add t3.md"
603 ); 620 );
604 Ok(()) 621 Ok(())
605 } 622 }
@@ -639,7 +656,7 @@ mod sends_pr_and_2_patches_to_3_relays {
639 #[tokio::test] 656 #[tokio::test]
640 #[serial] 657 #[serial]
641 async fn patch_tags_pr_event_as_root() -> Result<()> { 658 async fn patch_tags_pr_event_as_root() -> Result<()> {
642 let (_, _, r53, r55, r56) = prep_run_create_pr().await?; 659 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
643 for relay in [&r53, &r55, &r56] { 660 for relay in [&r53, &r55, &r56] {
644 let patch_events: Vec<&nostr::Event> = 661 let patch_events: Vec<&nostr::Event> =
645 relay.events.iter().filter(|e| is_patch(e)).collect(); 662 relay.events.iter().filter(|e| is_patch(e)).collect();
@@ -659,6 +676,43 @@ mod sends_pr_and_2_patches_to_3_relays {
659 } 676 }
660 Ok(()) 677 Ok(())
661 } 678 }
679
680 #[tokio::test]
681 #[serial]
682 async fn second_patch_tags_first_with_reply() -> Result<()> {
683 let (_, _, r53, r55, r56) = prep_run_create_pr(true).await?;
684 for relay in [&r53, &r55, &r56] {
685 let patch_events = relay
686 .events
687 .iter()
688 .filter(|e| is_patch(e))
689 .collect::<Vec<&nostr::Event>>();
690 assert_eq!(
691 patch_events[1]
692 .iter_tags()
693 .find(|t| t.as_vec()[0].eq("e")
694 && t.as_vec().len().eq(&4)
695 && t.as_vec()[3].eq("reply"))
696 .unwrap()
697 .as_vec()[1],
698 patch_events[0].id.to_string(),
699 );
700 }
701 Ok(())
702 }
703
704 #[tokio::test]
705 #[serial]
706 async fn no_t_root_tag() -> Result<()> {
707 assert!(
708 !prep()
709 .await?
710 .tags
711 .iter()
712 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
713 );
714 Ok(())
715 }
662 } 716 }
663 mod cli_ouput { 717 mod cli_ouput {
664 use super::*; 718 use super::*;
@@ -701,8 +755,8 @@ mod sends_pr_and_2_patches_to_3_relays {
701 755
702 // // check relay had the right number of events 756 // // check relay had the right number of events
703 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 757 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
704 let mut p = cli_tester_create_pr(&git_repo); 758 let mut p = cli_tester_create_pr(&git_repo, true);
705 expect_msgs_first(&mut p)?; 759 expect_msgs_first(&mut p, true)?;
706 relay::expect_send_with_progress( 760 relay::expect_send_with_progress(
707 &mut p, 761 &mut p,
708 vec![ 762 vec![
@@ -790,7 +844,7 @@ mod sends_pr_and_2_patches_to_3_relays {
790 844
791 // // check relay had the right number of events 845 // // check relay had the right number of events
792 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 846 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
793 let mut p = cli_tester_create_pr(&git_repo); 847 let mut p = cli_tester_create_pr(&git_repo, true);
794 p.expect_end_eventually()?; 848 p.expect_end_eventually()?;
795 for p in [51, 52, 53, 55, 56] { 849 for p in [51, 52, 53, 55, 56] {
796 relay::shutdown_relay(8000 + p)?; 850 relay::shutdown_relay(8000 + p)?;
@@ -869,8 +923,8 @@ mod sends_pr_and_2_patches_to_3_relays {
869 923
870 // // check relay had the right number of events 924 // // check relay had the right number of events
871 let cli_tester_handle = std::thread::spawn(move || -> Result<()> { 925 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
872 let mut p = cli_tester_create_pr(&git_repo); 926 let mut p = cli_tester_create_pr(&git_repo, true);
873 expect_msgs_first(&mut p)?; 927 expect_msgs_first(&mut p, true)?;
874 // p.expect_end_with("bla")?; 928 // p.expect_end_with("bla")?;
875 relay::expect_send_with_progress( 929 relay::expect_send_with_progress(
876 &mut p, 930 &mut p,
@@ -915,7 +969,162 @@ mod sends_pr_and_2_patches_to_3_relays {
915 } 969 }
916} 970}
917 971
918mod without_cover_letter { 972mod sends_2_patches_without_cover_letter {
919 use super::*; 973 use super::*;
920 // TODO 974
975 mod cli_ouput {
976 use super::*;
977
978 async fn run_test_async() -> Result<()> {
979 let git_repo = prep_git_repo()?;
980
981 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
982 Relay::new(
983 8051,
984 None,
985 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
986 relay.respond_events(
987 client_id,
988 &subscription_id,
989 &vec![
990 generate_test_key_1_metadata_event("fred"),
991 generate_test_key_1_relay_list_event(),
992 ],
993 )?;
994 Ok(())
995 }),
996 ),
997 Relay::new(8052, None, None),
998 Relay::new(8053, None, None),
999 Relay::new(
1000 8055,
1001 None,
1002 Some(&|relay, client_id, subscription_id, _| -> Result<()> {
1003 relay.respond_events(
1004 client_id,
1005 &subscription_id,
1006 &vec![generate_repo_ref_event()],
1007 )?;
1008 Ok(())
1009 }),
1010 ),
1011 Relay::new(8056, None, None),
1012 );
1013
1014 // // check relay had the right number of events
1015 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
1016 let mut p = cli_tester_create_pr(&git_repo, false);
1017
1018 expect_msgs_first(&mut p, false)?;
1019 relay::expect_send_with_progress(
1020 &mut p,
1021 vec![
1022 (" [my-relay] [repo-relay] ws://localhost:8055", true, ""),
1023 (" [my-relay] ws://localhost:8053", true, ""),
1024 (" [repo-relay] ws://localhost:8056", true, ""),
1025 ],
1026 2,
1027 )?;
1028 p.expect_end_with_whitespace()?;
1029 for p in [51, 52, 53, 55, 56] {
1030 relay::shutdown_relay(8000 + p)?;
1031 }
1032 Ok(())
1033 });
1034
1035 // launch relay
1036 let _ = join!(
1037 r51.listen_until_close(),
1038 r52.listen_until_close(),
1039 r53.listen_until_close(),
1040 r55.listen_until_close(),
1041 r56.listen_until_close(),
1042 );
1043 cli_tester_handle.join().unwrap()?;
1044 Ok(())
1045 }
1046
1047 #[tokio::test]
1048 #[serial]
1049 async fn check_cli_output() -> Result<()> {
1050 run_test_async().await?;
1051 Ok(())
1052 }
1053 }
1054
1055 #[tokio::test]
1056 #[serial]
1057 async fn no_cover_letter_event() -> Result<()> {
1058 let (_, _, r53, r55, r56) = prep_run_create_pr(false).await?;
1059 for relay in [&r53, &r55, &r56] {
1060 assert_eq!(
1061 relay.events.iter().filter(|e| is_cover_letter(e)).count(),
1062 0,
1063 );
1064 }
1065 Ok(())
1066 }
1067
1068 #[tokio::test]
1069 #[serial]
1070 async fn two_patch_events() -> Result<()> {
1071 let (_, _, r53, r55, r56) = prep_run_create_pr(false).await?;
1072 for relay in [&r53, &r55, &r56] {
1073 assert_eq!(relay.events.iter().filter(|e| is_patch(e)).count(), 2);
1074 }
1075 Ok(())
1076 }
1077
1078 #[tokio::test]
1079 #[serial]
1080 // TODO check this is the ancestor
1081 async fn first_patch_with_root_t_tag() -> Result<()> {
1082 let (_, _, r53, r55, r56) = prep_run_create_pr(false).await?;
1083 for relay in [&r53, &r55, &r56] {
1084 let patch_events = relay
1085 .events
1086 .iter()
1087 .filter(|e| is_patch(e))
1088 .collect::<Vec<&nostr::Event>>();
1089
1090 // first patch tagged as root
1091 assert!(
1092 patch_events[0]
1093 .iter_tags()
1094 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
1095 );
1096 // second patch not tagged as root
1097 assert!(
1098 !patch_events[1]
1099 .iter_tags()
1100 .any(|t| t.as_vec()[0].eq("t") && t.as_vec()[1].eq("root"))
1101 );
1102 }
1103 Ok(())
1104 }
1105
1106 #[tokio::test]
1107 #[serial]
1108 async fn second_patch_lists_first_as_root() -> Result<()> {
1109 let (_, _, r53, r55, r56) = prep_run_create_pr(false).await?;
1110 for relay in [&r53, &r55, &r56] {
1111 let patch_events = relay
1112 .events
1113 .iter()
1114 .filter(|e| is_patch(e))
1115 .collect::<Vec<&nostr::Event>>();
1116
1117 assert_eq!(
1118 patch_events[1]
1119 .iter_tags()
1120 .find(|t| t.as_vec()[0].eq("e")
1121 && t.as_vec().len().eq(&4)
1122 && t.as_vec()[3].eq("root"))
1123 .unwrap()
1124 .as_vec()[1],
1125 patch_events[0].id.to_string(),
1126 );
1127 }
1128 Ok(())
1129 }
921} 1130}
diff --git a/tests/prs_list.rs b/tests/prs_list.rs
index 75704f6..7c0d8ec 100644
--- a/tests/prs_list.rs
+++ b/tests/prs_list.rs
@@ -6,6 +6,7 @@ use test_utils::{git::GitTestRepo, relay::Relay, *};
6static FEATURE_BRANCH_NAME_1: &str = "feature-example-t"; 6static FEATURE_BRANCH_NAME_1: &str = "feature-example-t";
7static FEATURE_BRANCH_NAME_2: &str = "feature-example-f"; 7static FEATURE_BRANCH_NAME_2: &str = "feature-example-f";
8static FEATURE_BRANCH_NAME_3: &str = "feature-example-c"; 8static FEATURE_BRANCH_NAME_3: &str = "feature-example-c";
9static FEATURE_BRANCH_NAME_4: &str = "feature-example-d";
9 10
10static PR_TITLE_1: &str = "pr a"; 11static PR_TITLE_1: &str = "pr a";
11static PR_TITLE_2: &str = "pr b"; 12static PR_TITLE_2: &str = "pr b";
@@ -18,22 +19,19 @@ fn cli_tester_create_prs() -> Result<GitTestRepo> {
18 &git_repo, 19 &git_repo,
19 FEATURE_BRANCH_NAME_1, 20 FEATURE_BRANCH_NAME_1,
20 "a", 21 "a",
21 PR_TITLE_1, 22 Some((PR_TITLE_1, "pr a description")),
22 "pr a description",
23 )?; 23 )?;
24 cli_tester_create_pr( 24 cli_tester_create_pr(
25 &git_repo, 25 &git_repo,
26 FEATURE_BRANCH_NAME_2, 26 FEATURE_BRANCH_NAME_2,
27 "b", 27 "b",
28 PR_TITLE_2, 28 Some((PR_TITLE_2, "pr b description")),
29 "pr b description",
30 )?; 29 )?;
31 cli_tester_create_pr( 30 cli_tester_create_pr(
32 &git_repo, 31 &git_repo,
33 FEATURE_BRANCH_NAME_3, 32 FEATURE_BRANCH_NAME_3,
34 "c", 33 "c",
35 PR_TITLE_3, 34 Some((PR_TITLE_3, "pr c description")),
36 "pr c description",
37 )?; 35 )?;
38 Ok(git_repo) 36 Ok(git_repo)
39} 37}
@@ -66,28 +64,44 @@ fn cli_tester_create_pr(
66 test_repo: &GitTestRepo, 64 test_repo: &GitTestRepo,
67 branch_name: &str, 65 branch_name: &str,
68 prefix: &str, 66 prefix: &str,
69 title: &str, 67 cover_letter_title_and_description: Option<(&str, &str)>,
70 description: &str,
71) -> Result<()> { 68) -> Result<()> {
72 create_and_populate_branch(test_repo, branch_name, prefix, false)?; 69 create_and_populate_branch(test_repo, branch_name, prefix, false)?;
73 70
74 let mut p = CliTester::new_from_dir( 71 if let Some((title, description)) = cover_letter_title_and_description {
75 &test_repo.dir, 72 let mut p = CliTester::new_from_dir(
76 [ 73 &test_repo.dir,
77 "--nsec", 74 [
78 TEST_KEY_1_NSEC, 75 "--nsec",
79 "--password", 76 TEST_KEY_1_NSEC,
80 TEST_PASSWORD, 77 "--password",
81 "--disable-cli-spinners", 78 TEST_PASSWORD,
82 "prs", 79 "--disable-cli-spinners",
83 "create", 80 "prs",
84 "--title", 81 "create",
85 format!("\"{title}\"").as_str(), 82 "--title",
86 "--description", 83 format!("\"{title}\"").as_str(),
87 format!("\"{description}\"").as_str(), 84 "--description",
88 ], 85 format!("\"{description}\"").as_str(),
89 ); 86 ],
90 p.expect_end_eventually()?; 87 );
88 p.expect_end_eventually()?;
89 } else {
90 let mut p = CliTester::new_from_dir(
91 &test_repo.dir,
92 [
93 "--nsec",
94 TEST_KEY_1_NSEC,
95 "--password",
96 TEST_PASSWORD,
97 "--disable-cli-spinners",
98 "prs",
99 "create",
100 "--no-cover-letter",
101 ],
102 );
103 p.expect_end_eventually()?;
104 }
91 Ok(()) 105 Ok(())
92} 106}
93 107
@@ -432,6 +446,183 @@ mod when_main_branch_is_uptodate {
432 Ok(()) 446 Ok(())
433 } 447 }
434 } 448 }
449 mod when_forth_pr_has_no_cover_letter {
450 use super::*;
451
452 async fn prep_and_run() -> Result<(GitTestRepo, GitTestRepo)> {
453 // fallback (51,52) user write (53, 55) repo (55, 56)
454 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
455 Relay::new(8051, None, None),
456 Relay::new(8052, None, None),
457 Relay::new(8053, None, None),
458 Relay::new(8055, None, None),
459 Relay::new(8056, None, None),
460 );
461
462 r51.events.push(generate_test_key_1_relay_list_event());
463 r51.events.push(generate_test_key_1_metadata_event("fred"));
464 r51.events.push(generate_repo_ref_event());
465
466 r55.events.push(generate_repo_ref_event());
467 r55.events.push(generate_test_key_1_metadata_event("fred"));
468 r55.events.push(generate_test_key_1_relay_list_event());
469
470 let cli_tester_handle =
471 std::thread::spawn(move || -> Result<(GitTestRepo, GitTestRepo)> {
472 let originating_repo = cli_tester_create_prs()?;
473 cli_tester_create_pr(
474 &originating_repo,
475 FEATURE_BRANCH_NAME_4,
476 "d",
477 None,
478 )?;
479 let test_repo = GitTestRepo::default();
480 test_repo.populate()?;
481 let mut p = CliTester::new_from_dir(&test_repo.dir, ["prs", "list"]);
482
483 p.expect("finding PRs...\r\n")?;
484 let mut c = p.expect_choice(
485 "All PRs",
486 vec![
487 format!("\"{PR_TITLE_1}\""),
488 format!("\"{PR_TITLE_2}\""),
489 format!("\"{PR_TITLE_3}\""),
490 format!("add d3.md"), // commit msg title
491 ],
492 )?;
493 c.succeeds_with(3, true)?;
494 let mut confirm =
495 p.expect_confirm_eventually("check out branch?", Some(true))?;
496 confirm.succeeds_with(None)?;
497 p.expect_end_eventually_and_print()?;
498
499 for p in [51, 52, 53, 55, 56] {
500 relay::shutdown_relay(8000 + p)?;
501 }
502 Ok((originating_repo, test_repo))
503 });
504
505 // launch relay
506 let _ = join!(
507 r51.listen_until_close(),
508 r52.listen_until_close(),
509 r53.listen_until_close(),
510 r55.listen_until_close(),
511 r56.listen_until_close(),
512 );
513 let res = cli_tester_handle.join().unwrap()?;
514
515 Ok(res)
516 }
517
518 mod cli_prompts {
519 use super::*;
520 async fn run_async_prompts_to_choose_from_pr_titles() -> Result<()> {
521 let (mut r51, mut r52, mut r53, mut r55, mut r56) = (
522 Relay::new(8051, None, None),
523 Relay::new(8052, None, None),
524 Relay::new(8053, None, None),
525 Relay::new(8055, None, None),
526 Relay::new(8056, None, None),
527 );
528
529 r51.events.push(generate_test_key_1_relay_list_event());
530 r51.events.push(generate_test_key_1_metadata_event("fred"));
531 r51.events.push(generate_repo_ref_event());
532
533 r55.events.push(generate_repo_ref_event());
534 r55.events.push(generate_test_key_1_metadata_event("fred"));
535 r55.events.push(generate_test_key_1_relay_list_event());
536
537 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
538 let originating_repo = cli_tester_create_prs()?;
539 cli_tester_create_pr(
540 &originating_repo,
541 FEATURE_BRANCH_NAME_4,
542 "d",
543 None,
544 )?;
545 let test_repo = GitTestRepo::default();
546 test_repo.populate()?;
547 let mut p = CliTester::new_from_dir(&test_repo.dir, ["prs", "list"]);
548
549 p.expect("finding PRs...\r\n")?;
550 let mut c = p.expect_choice(
551 "All PRs",
552 vec![
553 format!("\"{PR_TITLE_1}\""),
554 format!("\"{PR_TITLE_2}\""),
555 format!("\"{PR_TITLE_3}\""),
556 format!("add d3.md"), // commit msg title
557 ],
558 )?;
559 c.succeeds_with(3, true)?;
560 p.expect("finding commits...\r\n")?;
561 let mut confirm = p.expect_confirm("check out branch?", Some(true))?;
562 confirm.succeeds_with(None)?;
563 p.expect("checked out PR branch. pulled 2 new commits\r\n")?;
564 p.expect_end()?;
565
566 for p in [51, 52, 53, 55, 56] {
567 relay::shutdown_relay(8000 + p)?;
568 }
569 Ok(())
570 });
571
572 // launch relay
573 let _ = join!(
574 r51.listen_until_close(),
575 r52.listen_until_close(),
576 r53.listen_until_close(),
577 r55.listen_until_close(),
578 r56.listen_until_close(),
579 );
580 cli_tester_handle.join().unwrap()?;
581 println!("{:?}", r55.events);
582 Ok(())
583 }
584
585 #[tokio::test]
586 #[serial]
587 async fn prompts_to_choose_from_pr_titles() -> Result<()> {
588 let _ = run_async_prompts_to_choose_from_pr_titles().await;
589 Ok(())
590 }
591 }
592
593 #[tokio::test]
594 #[serial]
595 async fn pr_branch_created_with_correct_name() -> Result<()> {
596 let (_, test_repo) = prep_and_run().await?;
597 assert_eq!(
598 vec![FEATURE_BRANCH_NAME_4, "main"],
599 test_repo.get_local_branch_names()?
600 );
601 Ok(())
602 }
603
604 #[tokio::test]
605 #[serial]
606 async fn pr_branch_checked_out() -> Result<()> {
607 let (_, test_repo) = prep_and_run().await?;
608 assert_eq!(
609 FEATURE_BRANCH_NAME_4,
610 test_repo.get_checked_out_branch_name()?,
611 );
612 Ok(())
613 }
614
615 #[tokio::test]
616 #[serial]
617 async fn pr_branch_tip_is_most_recent_patch() -> Result<()> {
618 let (originating_repo, test_repo) = prep_and_run().await?;
619 assert_eq!(
620 originating_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?,
621 test_repo.get_tip_of_local_branch(FEATURE_BRANCH_NAME_4)?,
622 );
623 Ok(())
624 }
625 }
435 } 626 }
436 } 627 }
437 628