upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/sub_commands/prs/list.rs
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 /src/sub_commands/prs/list.rs
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.
Diffstat (limited to 'src/sub_commands/prs/list.rs')
-rw-r--r--src/sub_commands/prs/list.rs215
1 files changed, 139 insertions, 76 deletions
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}