upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/sub_commands
diff options
context:
space:
mode:
Diffstat (limited to 'src/sub_commands')
-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
4 files changed, 289 insertions, 223 deletions
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}