diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-13 14:52:24 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-02-13 15:55:54 +0000 |
| commit | cf319efc6dcdc6c54564cb84e13218edbf3643fa (patch) | |
| tree | ccccf807fac6c2ab242b2d6bb322c679ae5b94f7 /src/sub_commands/prs/list.rs | |
| parent | 3112576195aef212622d27ad9164336796c1953e (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.rs | 215 |
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 @@ | |||
| 1 | use anyhow::{bail, Context, Result}; | 1 | use anyhow::{bail, Context, Result}; |
| 2 | 2 | ||
| 3 | use super::create::event_is_patch_set_root; | ||
| 3 | #[cfg(not(test))] | 4 | #[cfg(not(test))] |
| 4 | use crate::client::Client; | 5 | use 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 | |||
| 187 | pub 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 | |||
| 241 | pub 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 | } | ||