diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-18 11:56:15 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-18 11:56:15 +0100 |
| commit | 3eb2354edb8e76428625d5645e110c30aa1ccc2a (patch) | |
| tree | 59798e071817e2a8b155b15230a367005181ac56 /src | |
| parent | 757c2f888b2be2b37ea01e02a6c020c5f8c7aa9c (diff) | |
feat(pr): list PR and PR updates
remote will list the refs under `pr/*` namespace.
`ngit list` will display in the list of open / draft proposals.
it won't yet fetch the related oids to enable fetching or checking
out the branch.
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/git_remote_nostr/list.rs | 56 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/utils.rs | 25 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 157 | ||||
| -rw-r--r-- | src/lib/client.rs | 37 | ||||
| -rw-r--r-- | src/lib/git_events.rs | 50 |
5 files changed, 233 insertions, 92 deletions
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs index b9fb0c0..7bdf170 100644 --- a/src/bin/git_remote_nostr/list.rs +++ b/src/bin/git_remote_nostr/list.rs | |||
| @@ -11,7 +11,7 @@ use ngit::{ | |||
| 11 | self, | 11 | self, |
| 12 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, | 12 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, |
| 13 | }, | 13 | }, |
| 14 | git_events::event_to_cover_letter, | 14 | git_events::{KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_to_cover_letter, tag_value}, |
| 15 | login::get_curent_user, | 15 | login::get_curent_user, |
| 16 | repo_ref::{self, is_grasp_server}, | 16 | repo_ref::{self, is_grasp_server}, |
| 17 | }; | 17 | }; |
| @@ -122,6 +122,16 @@ async fn get_open_and_draft_proposals_state( | |||
| 122 | 122 | ||
| 123 | // without trusting commit_id we must apply each patch which requires the oid of | 123 | // without trusting commit_id we must apply each patch which requires the oid of |
| 124 | // the parent so we much do a fetch | 124 | // the parent so we much do a fetch |
| 125 | |||
| 126 | // As we are fetching from git servers we mighgt as well get oids from pull | ||
| 127 | // request too | ||
| 128 | // TODO get Pull Request and Pull Request Update Events add these to | ||
| 129 | // refs/nostr/<event-id> | ||
| 130 | // TODO prepare PRs and PRS oids to try and fetch from repo servers that are or | ||
| 131 | // clone urls in PR/update event we are using anyway. TODO after we tried | ||
| 132 | // and failed to get them from these server we should fallback to fetch them | ||
| 133 | // from listed clone urls in PR/update but not during list, only during fetch | ||
| 134 | |||
| 125 | for (git_server_url, (oids_from_git_servers, is_grasp_server)) in remote_states { | 135 | for (git_server_url, (oids_from_git_servers, is_grasp_server)) in remote_states { |
| 126 | if fetch_from_git_server( | 136 | if fetch_from_git_server( |
| 127 | git_repo, | 137 | git_repo, |
| @@ -144,7 +154,7 @@ async fn get_open_and_draft_proposals_state( | |||
| 144 | let mut state = HashMap::new(); | 154 | let mut state = HashMap::new(); |
| 145 | let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?; | 155 | let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?; |
| 146 | let current_user = get_curent_user(git_repo)?; | 156 | let current_user = get_curent_user(git_repo)?; |
| 147 | for (_, (proposal, patches)) in open_and_draft_proposals { | 157 | for (_, (proposal, events_to_apply)) in open_and_draft_proposals { |
| 148 | if let Ok(cl) = event_to_cover_letter(&proposal) { | 158 | if let Ok(cl) = event_to_cover_letter(&proposal) { |
| 149 | if let Ok(mut branch_name) = cl.get_branch_name_with_pr_prefix_and_shorthand_id() { | 159 | if let Ok(mut branch_name) = cl.get_branch_name_with_pr_prefix_and_shorthand_id() { |
| 150 | branch_name = if let Some(public_key) = current_user { | 160 | branch_name = if let Some(public_key) = current_user { |
| @@ -156,15 +166,43 @@ async fn get_open_and_draft_proposals_state( | |||
| 156 | } else { | 166 | } else { |
| 157 | branch_name | 167 | branch_name |
| 158 | }; | 168 | }; |
| 159 | match make_commits_for_proposal(git_repo, repo_ref, &patches) { | 169 | // if events_to_apply contains a PR or PR Update event it should be the only |
| 160 | Ok(tip) => { | 170 | // event in the Vec |
| 161 | state.insert(format!("refs/heads/{branch_name}"), tip); | 171 | if let Some(pr_or_pr_update) = events_to_apply |
| 172 | .iter() | ||
| 173 | .find(|e| e.kind.eq(&KIND_PULL_REQUEST) || e.kind.eq(&KIND_PULL_REQUEST_UPDATE)) | ||
| 174 | { | ||
| 175 | match tag_value(pr_or_pr_update, "c") { | ||
| 176 | Ok(tip) => { | ||
| 177 | state.insert(format!("refs/heads/{branch_name}"), tip); | ||
| 178 | } | ||
| 179 | Err(_) => { | ||
| 180 | let _ = term.write_line( | ||
| 181 | format!( | ||
| 182 | "WARNING: failed to fetch branch {branch_name} error: {} event poorly formatted", | ||
| 183 | if pr_or_pr_update.kind.eq(&KIND_PULL_REQUEST) { | ||
| 184 | "PR" | ||
| 185 | } else { | ||
| 186 | "PR update" | ||
| 187 | } | ||
| 188 | ) | ||
| 189 | .as_str(), | ||
| 190 | ); | ||
| 191 | } | ||
| 162 | } | 192 | } |
| 163 | Err(error) => { | 193 | } else { |
| 164 | let _ = term.write_line( | 194 | match make_commits_for_proposal(git_repo, repo_ref, &events_to_apply) { |
| 165 | format!("WARNING: failed to fetch branch {branch_name} error: {error}") | 195 | Ok(tip) => { |
| 196 | state.insert(format!("refs/heads/{branch_name}"), tip); | ||
| 197 | } | ||
| 198 | Err(error) => { | ||
| 199 | let _ = term.write_line( | ||
| 200 | format!( | ||
| 201 | "WARNING: failed to fetch branch {branch_name} error: {error}" | ||
| 202 | ) | ||
| 166 | .as_str(), | 203 | .as_str(), |
| 167 | ); | 204 | ); |
| 205 | } | ||
| 168 | } | 206 | } |
| 169 | } | 207 | } |
| 170 | } | 208 | } |
diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index dc75872..d0b4e7e 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs | |||
| @@ -10,7 +10,7 @@ use anyhow::{Context, Result, bail}; | |||
| 10 | use git2::Repository; | 10 | use git2::Repository; |
| 11 | use ngit::{ | 11 | use ngit::{ |
| 12 | client::{ | 12 | client::{ |
| 13 | get_all_proposal_patch_events_from_cache, get_events_from_local_cache, | 13 | get_all_proposal_patch_pr_pr_update_events_from_cache, get_events_from_local_cache, |
| 14 | get_proposals_and_revisions_from_cache, | 14 | get_proposals_and_revisions_from_cache, |
| 15 | }, | 15 | }, |
| 16 | git::{ | 16 | git::{ |
| @@ -18,7 +18,7 @@ use ngit::{ | |||
| 18 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, | 18 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, |
| 19 | }, | 19 | }, |
| 20 | git_events::{ | 20 | git_events::{ |
| 21 | event_is_revision_root, get_most_recent_patch_with_ancestors, | 21 | event_is_revision_root, get_pr_tip_event_or_most_recent_patch_with_ancestors, |
| 22 | is_event_proposal_root_for_branch, status_kinds, | 22 | is_event_proposal_root_for_branch, status_kinds, |
| 23 | }, | 23 | }, |
| 24 | repo_ref::RepoRef, | 24 | repo_ref::RepoRef, |
| @@ -140,12 +140,15 @@ pub async fn get_open_or_draft_proposals( | |||
| 140 | Kind::GitStatusOpen | 140 | Kind::GitStatusOpen |
| 141 | }; | 141 | }; |
| 142 | if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) { | 142 | if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) { |
| 143 | if let Ok(commits_events) = | 143 | if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( |
| 144 | get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id) | 144 | git_repo_path, |
| 145 | .await | 145 | repo_ref, |
| 146 | &proposal.id, | ||
| 147 | ) | ||
| 148 | .await | ||
| 146 | { | 149 | { |
| 147 | if let Ok(most_recent_proposal_patch_chain) = | 150 | if let Ok(most_recent_proposal_patch_chain) = |
| 148 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 151 | get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) |
| 149 | { | 152 | { |
| 150 | open_or_draft_proposals | 153 | open_or_draft_proposals |
| 151 | .insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); | 154 | .insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); |
| @@ -172,11 +175,15 @@ pub async fn get_all_proposals( | |||
| 172 | let mut all_proposals = HashMap::new(); | 175 | let mut all_proposals = HashMap::new(); |
| 173 | 176 | ||
| 174 | for proposal in proposals { | 177 | for proposal in proposals { |
| 175 | if let Ok(commits_events) = | 178 | if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( |
| 176 | get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id).await | 179 | git_repo_path, |
| 180 | repo_ref, | ||
| 181 | &proposal.id, | ||
| 182 | ) | ||
| 183 | .await | ||
| 177 | { | 184 | { |
| 178 | if let Ok(most_recent_proposal_patch_chain) = | 185 | if let Ok(most_recent_proposal_patch_chain) = |
| 179 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 186 | get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) |
| 180 | { | 187 | { |
| 181 | all_proposals.insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); | 188 | all_proposals.insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); |
| 182 | } | 189 | } |
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index 0330be1..a90b28e 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -3,10 +3,12 @@ use std::{io::Write, ops::Add}; | |||
| 3 | use anyhow::{Context, Result, bail}; | 3 | use anyhow::{Context, Result, bail}; |
| 4 | use ngit::{ | 4 | use ngit::{ |
| 5 | client::{ | 5 | client::{ |
| 6 | Params, get_all_proposal_patch_events_from_cache, get_proposals_and_revisions_from_cache, | 6 | Params, get_all_proposal_patch_pr_pr_update_events_from_cache, |
| 7 | get_proposals_and_revisions_from_cache, | ||
| 7 | }, | 8 | }, |
| 8 | git_events::{ | 9 | git_events::{ |
| 9 | get_commit_id_from_patch, get_most_recent_patch_with_ancestors, status_kinds, tag_value, | 10 | KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, get_commit_id_from_patch, |
| 11 | get_pr_tip_event_or_most_recent_patch_with_ancestors, status_kinds, tag_value, | ||
| 10 | }, | 12 | }, |
| 11 | }; | 13 | }; |
| 12 | use nostr_sdk::Kind; | 14 | use nostr_sdk::Kind; |
| @@ -184,21 +186,22 @@ pub async fn launch() -> Result<()> { | |||
| 184 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) | 186 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) |
| 185 | .context("failed to extract proposal details from proposal root event")?; | 187 | .context("failed to extract proposal details from proposal root event")?; |
| 186 | 188 | ||
| 187 | let commits_events: Vec<nostr::Event> = get_all_proposal_patch_events_from_cache( | 189 | let commits_events: Vec<nostr::Event> = |
| 188 | git_repo_path, | 190 | get_all_proposal_patch_pr_pr_update_events_from_cache( |
| 189 | &repo_ref, | 191 | git_repo_path, |
| 190 | &proposals_for_status[selected_index].id, | 192 | &repo_ref, |
| 191 | ) | 193 | &proposals_for_status[selected_index].id, |
| 192 | .await?; | 194 | ) |
| 195 | .await?; | ||
| 193 | 196 | ||
| 194 | let Ok(most_recent_proposal_patch_chain) = | 197 | let Ok(most_recent_proposal_patch_chain_or_pr_or_pr_update) = |
| 195 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 198 | get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) |
| 196 | else { | 199 | else { |
| 197 | if Interactor::default().confirm( | 200 | if Interactor::default().confirm( |
| 198 | PromptConfirmParms::default() | 201 | PromptConfirmParms::default() |
| 199 | .with_default(true) | 202 | .with_default(true) |
| 200 | .with_prompt( | 203 | .with_prompt( |
| 201 | "failed to find any patches on this proposal. choose another proposal?", | 204 | "failed to find any PR or patch events on this proposal. choose another proposal?", |
| 202 | ), | 205 | ), |
| 203 | )? { | 206 | )? { |
| 204 | continue; | 207 | continue; |
| @@ -208,15 +211,37 @@ pub async fn launch() -> Result<()> { | |||
| 208 | // for commit in &most_recent_proposal_patch_chain { | 211 | // for commit in &most_recent_proposal_patch_chain { |
| 209 | // println!("recent_event: {:?}", commit.as_json()); | 212 | // println!("recent_event: {:?}", commit.as_json()); |
| 210 | // } | 213 | // } |
| 214 | if most_recent_proposal_patch_chain_or_pr_or_pr_update | ||
| 215 | .iter() | ||
| 216 | .any(|e| [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&e.kind)) | ||
| 217 | { | ||
| 218 | match Interactor::default().choice( | ||
| 219 | PromptChoiceParms::default() | ||
| 220 | .with_prompt("this is new PR event kind which ngit doesnt yet support") | ||
| 221 | .with_default(0) | ||
| 222 | .with_choices(vec!["back to proposals".to_string()]), | ||
| 223 | )? { | ||
| 224 | 0 => continue, | ||
| 225 | _ => { | ||
| 226 | bail!("unexpected choice") | ||
| 227 | } | ||
| 228 | }; | ||
| 229 | } | ||
| 211 | 230 | ||
| 212 | let binding_patch_text_ref = format!("{} commits", most_recent_proposal_patch_chain.len()); | 231 | let binding_patch_text_ref = format!( |
| 213 | let patch_text_ref = if most_recent_proposal_patch_chain.len().gt(&1) { | 232 | "{} commits", |
| 233 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len() | ||
| 234 | ); | ||
| 235 | let patch_text_ref = if most_recent_proposal_patch_chain_or_pr_or_pr_update | ||
| 236 | .len() | ||
| 237 | .gt(&1) | ||
| 238 | { | ||
| 214 | binding_patch_text_ref.as_str() | 239 | binding_patch_text_ref.as_str() |
| 215 | } else { | 240 | } else { |
| 216 | "1 commit" | 241 | "1 commit" |
| 217 | }; | 242 | }; |
| 218 | 243 | ||
| 219 | let no_support_for_patches_as_branch = most_recent_proposal_patch_chain | 244 | let no_support_for_patches_as_branch = most_recent_proposal_patch_chain_or_pr_or_pr_update |
| 220 | .iter() | 245 | .iter() |
| 221 | .any(|event| !patch_supports_commit_ids(event)); | 246 | .any(|event| !patch_supports_commit_ids(event)); |
| 222 | 247 | ||
| @@ -253,8 +278,13 @@ pub async fn launch() -> Result<()> { | |||
| 253 | )?; | 278 | )?; |
| 254 | continue; | 279 | continue; |
| 255 | } | 280 | } |
| 256 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 281 | 1 => { |
| 257 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 282 | launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update) |
| 283 | } | ||
| 284 | 2 => save_patches_to_dir( | ||
| 285 | most_recent_proposal_patch_chain_or_pr_or_pr_update, | ||
| 286 | &git_repo, | ||
| 287 | ), | ||
| 258 | 3 => continue, | 288 | 3 => continue, |
| 259 | _ => { | 289 | _ => { |
| 260 | bail!("unexpected choice") | 290 | bail!("unexpected choice") |
| @@ -277,9 +307,11 @@ pub async fn launch() -> Result<()> { | |||
| 277 | .eq(&cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?); | 307 | .eq(&cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?); |
| 278 | 308 | ||
| 279 | let proposal_base_commit = str_to_sha1(&tag_value( | 309 | let proposal_base_commit = str_to_sha1(&tag_value( |
| 280 | most_recent_proposal_patch_chain.last().context( | 310 | most_recent_proposal_patch_chain_or_pr_or_pr_update |
| 281 | "there should be at least one patch as we have already checked for this", | 311 | .last() |
| 282 | )?, | 312 | .context( |
| 313 | "there should be at least one patch as we have already checked for this", | ||
| 314 | )?, | ||
| 283 | "parent-commit", | 315 | "parent-commit", |
| 284 | )?) | 316 | )?) |
| 285 | .context("failed to get valid parent commit id from patch")?; | 317 | .context("failed to get valid parent commit id from patch")?; |
| @@ -300,8 +332,8 @@ pub async fn launch() -> Result<()> { | |||
| 300 | ], | 332 | ], |
| 301 | ))? { | 333 | ))? { |
| 302 | 0 | 3 => continue, | 334 | 0 | 3 => continue, |
| 303 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 335 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update), |
| 304 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 336 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain_or_pr_or_pr_update, &git_repo), |
| 305 | _ => { | 337 | _ => { |
| 306 | bail!("unexpected choice") | 338 | bail!("unexpected choice") |
| 307 | } | 339 | } |
| @@ -309,9 +341,13 @@ pub async fn launch() -> Result<()> { | |||
| 309 | } | 341 | } |
| 310 | 342 | ||
| 311 | let proposal_tip = str_to_sha1( | 343 | let proposal_tip = str_to_sha1( |
| 312 | &get_commit_id_from_patch(most_recent_proposal_patch_chain.first().context( | 344 | &get_commit_id_from_patch( |
| 313 | "there should be at least one patch as we have already checked for this", | 345 | most_recent_proposal_patch_chain_or_pr_or_pr_update |
| 314 | )?) | 346 | .first() |
| 347 | .context( | ||
| 348 | "there should be at least one patch as we have already checked for this", | ||
| 349 | )?, | ||
| 350 | ) | ||
| 315 | .context("failed to get valid commit_id from patch")?, | 351 | .context("failed to get valid commit_id from patch")?, |
| 316 | ) | 352 | ) |
| 317 | .context("failed to get valid commit_id from patch")?; | 353 | .context("failed to get valid commit_id from patch")?; |
| @@ -325,7 +361,7 @@ pub async fn launch() -> Result<()> { | |||
| 325 | .choice(PromptChoiceParms::default().with_default(0).with_choices(vec![ | 361 | .choice(PromptChoiceParms::default().with_default(0).with_choices(vec![ |
| 326 | format!( | 362 | format!( |
| 327 | "create and checkout proposal branch ({} ahead {} behind '{main_branch_name}')", | 363 | "create and checkout proposal branch ({} ahead {} behind '{main_branch_name}')", |
| 328 | most_recent_proposal_patch_chain.len(), | 364 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len(), |
| 329 | proposal_behind_main.len(), | 365 | proposal_behind_main.len(), |
| 330 | ), | 366 | ), |
| 331 | format!("apply to current branch with `git am`"), | 367 | format!("apply to current branch with `git am`"), |
| @@ -337,7 +373,7 @@ pub async fn launch() -> Result<()> { | |||
| 337 | let _ = git_repo | 373 | let _ = git_repo |
| 338 | .apply_patch_chain( | 374 | .apply_patch_chain( |
| 339 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 375 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 340 | most_recent_proposal_patch_chain, | 376 | most_recent_proposal_patch_chain_or_pr_or_pr_update, |
| 341 | ) | 377 | ) |
| 342 | .context("failed to apply patch chain")?; | 378 | .context("failed to apply patch chain")?; |
| 343 | 379 | ||
| @@ -347,8 +383,8 @@ pub async fn launch() -> Result<()> { | |||
| 347 | ); | 383 | ); |
| 348 | Ok(()) | 384 | Ok(()) |
| 349 | } | 385 | } |
| 350 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 386 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update), |
| 351 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 387 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain_or_pr_or_pr_update, &git_repo), |
| 352 | 3 => continue, | 388 | 3 => continue, |
| 353 | _ => { | 389 | _ => { |
| 354 | bail!("unexpected choice") | 390 | bail!("unexpected choice") |
| @@ -382,7 +418,7 @@ pub async fn launch() -> Result<()> { | |||
| 382 | .with_choices(vec![ | 418 | .with_choices(vec![ |
| 383 | format!( | 419 | format!( |
| 384 | "checkout proposal branch ({} ahead {} behind '{main_branch_name}')", | 420 | "checkout proposal branch ({} ahead {} behind '{main_branch_name}')", |
| 385 | most_recent_proposal_patch_chain.len(), | 421 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len(), |
| 386 | proposal_behind_main.len(), | 422 | proposal_behind_main.len(), |
| 387 | ), | 423 | ), |
| 388 | format!("apply to current branch with `git am`"), | 424 | format!("apply to current branch with `git am`"), |
| @@ -401,8 +437,13 @@ pub async fn launch() -> Result<()> { | |||
| 401 | ); | 437 | ); |
| 402 | Ok(()) | 438 | Ok(()) |
| 403 | } | 439 | } |
| 404 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 440 | 1 => { |
| 405 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 441 | launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update) |
| 442 | } | ||
| 443 | 2 => save_patches_to_dir( | ||
| 444 | most_recent_proposal_patch_chain_or_pr_or_pr_update, | ||
| 445 | &git_repo, | ||
| 446 | ), | ||
| 406 | 3 => continue, | 447 | 3 => continue, |
| 407 | _ => { | 448 | _ => { |
| 408 | bail!("unexpected choice") | 449 | bail!("unexpected choice") |
| @@ -414,11 +455,14 @@ pub async fn launch() -> Result<()> { | |||
| 414 | git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?; | 455 | git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?; |
| 415 | 456 | ||
| 416 | // new appendments to proposal | 457 | // new appendments to proposal |
| 417 | if let Some(index) = most_recent_proposal_patch_chain.iter().position(|patch| { | 458 | if let Some(index) = most_recent_proposal_patch_chain_or_pr_or_pr_update |
| 418 | get_commit_id_from_patch(patch) | 459 | .iter() |
| 419 | .unwrap_or_default() | 460 | .position(|patch| { |
| 420 | .eq(&local_branch_tip.to_string()) | 461 | get_commit_id_from_patch(patch) |
| 421 | }) { | 462 | .unwrap_or_default() |
| 463 | .eq(&local_branch_tip.to_string()) | ||
| 464 | }) | ||
| 465 | { | ||
| 422 | return match Interactor::default().choice( | 466 | return match Interactor::default().choice( |
| 423 | PromptChoiceParms::default() | 467 | PromptChoiceParms::default() |
| 424 | .with_default(0) | 468 | .with_default(0) |
| @@ -437,7 +481,7 @@ pub async fn launch() -> Result<()> { | |||
| 437 | let _ = git_repo | 481 | let _ = git_repo |
| 438 | .apply_patch_chain( | 482 | .apply_patch_chain( |
| 439 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 483 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 440 | most_recent_proposal_patch_chain, | 484 | most_recent_proposal_patch_chain_or_pr_or_pr_update, |
| 441 | ) | 485 | ) |
| 442 | .context("failed to apply patch chain")?; | 486 | .context("failed to apply patch chain")?; |
| 443 | println!( | 487 | println!( |
| @@ -448,8 +492,13 @@ pub async fn launch() -> Result<()> { | |||
| 448 | ); | 492 | ); |
| 449 | Ok(()) | 493 | Ok(()) |
| 450 | } | 494 | } |
| 451 | 1 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 495 | 1 => { |
| 452 | 2 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 496 | launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update) |
| 497 | } | ||
| 498 | 2 => save_patches_to_dir( | ||
| 499 | most_recent_proposal_patch_chain_or_pr_or_pr_update, | ||
| 500 | &git_repo, | ||
| 501 | ), | ||
| 453 | 3 => continue, | 502 | 3 => continue, |
| 454 | _ => { | 503 | _ => { |
| 455 | bail!("unexpected choice") | 504 | bail!("unexpected choice") |
| @@ -467,7 +516,7 @@ pub async fn launch() -> Result<()> { | |||
| 467 | }) { | 516 | }) { |
| 468 | println!( | 517 | println!( |
| 469 | "updated proposal available ({} ahead {} behind '{main_branch_name}'). existing version is {} ahead {} behind '{main_branch_name}'", | 518 | "updated proposal available ({} ahead {} behind '{main_branch_name}'). existing version is {} ahead {} behind '{main_branch_name}'", |
| 470 | most_recent_proposal_patch_chain.len(), | 519 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len(), |
| 471 | proposal_behind_main.len(), | 520 | proposal_behind_main.len(), |
| 472 | local_ahead_of_main.len(), | 521 | local_ahead_of_main.len(), |
| 473 | local_beind_main.len(), | 522 | local_beind_main.len(), |
| @@ -492,11 +541,11 @@ pub async fn launch() -> Result<()> { | |||
| 492 | git_repo.checkout( | 541 | git_repo.checkout( |
| 493 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 542 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 494 | )?; | 543 | )?; |
| 495 | let chain_length = most_recent_proposal_patch_chain.len(); | 544 | let chain_length = most_recent_proposal_patch_chain_or_pr_or_pr_update.len(); |
| 496 | let _ = git_repo | 545 | let _ = git_repo |
| 497 | .apply_patch_chain( | 546 | .apply_patch_chain( |
| 498 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 547 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 499 | most_recent_proposal_patch_chain, | 548 | most_recent_proposal_patch_chain_or_pr_or_pr_update, |
| 500 | ) | 549 | ) |
| 501 | .context("failed to apply patch chain")?; | 550 | .context("failed to apply patch chain")?; |
| 502 | println!( | 551 | println!( |
| @@ -520,8 +569,13 @@ pub async fn launch() -> Result<()> { | |||
| 520 | ); | 569 | ); |
| 521 | Ok(()) | 570 | Ok(()) |
| 522 | } | 571 | } |
| 523 | 2 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 572 | 2 => { |
| 524 | 3 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 573 | launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update) |
| 574 | } | ||
| 575 | 3 => save_patches_to_dir( | ||
| 576 | most_recent_proposal_patch_chain_or_pr_or_pr_update, | ||
| 577 | &git_repo, | ||
| 578 | ), | ||
| 525 | 4 => continue, | 579 | 4 => continue, |
| 526 | _ => { | 580 | _ => { |
| 527 | bail!("unexpected choice") | 581 | bail!("unexpected choice") |
| @@ -581,7 +635,7 @@ pub async fn launch() -> Result<()> { | |||
| 581 | if git_repo.does_commit_exist(&proposal_tip.to_string())? { | 635 | if git_repo.does_commit_exist(&proposal_tip.to_string())? { |
| 582 | println!( | 636 | println!( |
| 583 | "you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has amended or rebased it ({} ahead {} behind '{main_branch_name}')", | 637 | "you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has amended or rebased it ({} ahead {} behind '{main_branch_name}')", |
| 584 | most_recent_proposal_patch_chain.len(), | 638 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len(), |
| 585 | proposal_behind_main.len(), | 639 | proposal_behind_main.len(), |
| 586 | local_ahead_of_main.len(), | 640 | local_ahead_of_main.len(), |
| 587 | local_beind_main.len(), | 641 | local_beind_main.len(), |
| @@ -594,7 +648,7 @@ pub async fn launch() -> Result<()> { | |||
| 594 | "your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')", | 648 | "your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')", |
| 595 | local_ahead_of_main.len(), | 649 | local_ahead_of_main.len(), |
| 596 | local_beind_main.len(), | 650 | local_beind_main.len(), |
| 597 | most_recent_proposal_patch_chain.len(), | 651 | most_recent_proposal_patch_chain_or_pr_or_pr_update.len(), |
| 598 | proposal_behind_main.len(), | 652 | proposal_behind_main.len(), |
| 599 | ); | 653 | ); |
| 600 | 654 | ||
| @@ -639,11 +693,11 @@ pub async fn launch() -> Result<()> { | |||
| 639 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 693 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 640 | &proposal_base_commit.to_string(), | 694 | &proposal_base_commit.to_string(), |
| 641 | )?; | 695 | )?; |
| 642 | let chain_length = most_recent_proposal_patch_chain.len(); | 696 | let chain_length = most_recent_proposal_patch_chain_or_pr_or_pr_update.len(); |
| 643 | let _ = git_repo | 697 | let _ = git_repo |
| 644 | .apply_patch_chain( | 698 | .apply_patch_chain( |
| 645 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 699 | &cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 646 | most_recent_proposal_patch_chain, | 700 | most_recent_proposal_patch_chain_or_pr_or_pr_update, |
| 647 | ) | 701 | ) |
| 648 | .context("failed to apply patch chain")?; | 702 | .context("failed to apply patch chain")?; |
| 649 | 703 | ||
| @@ -658,8 +712,11 @@ pub async fn launch() -> Result<()> { | |||
| 658 | ); | 712 | ); |
| 659 | Ok(()) | 713 | Ok(()) |
| 660 | } | 714 | } |
| 661 | 2 => launch_git_am_with_patches(most_recent_proposal_patch_chain), | 715 | 2 => launch_git_am_with_patches(most_recent_proposal_patch_chain_or_pr_or_pr_update), |
| 662 | 3 => save_patches_to_dir(most_recent_proposal_patch_chain, &git_repo), | 716 | 3 => save_patches_to_dir( |
| 717 | most_recent_proposal_patch_chain_or_pr_or_pr_update, | ||
| 718 | &git_repo, | ||
| 719 | ), | ||
| 663 | 4 => continue, | 720 | 4 => continue, |
| 664 | _ => { | 721 | _ => { |
| 665 | bail!("unexpected choice") | 722 | bail!("unexpected choice") |
diff --git a/src/lib/client.rs b/src/lib/client.rs index ae3b414..1f3b08c 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -1811,7 +1811,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1811 | git_repo_path, | 1811 | git_repo_path, |
| 1812 | vec![ | 1812 | vec![ |
| 1813 | nostr::Filter::default() | 1813 | nostr::Filter::default() |
| 1814 | .kind(nostr::Kind::GitPatch) | 1814 | .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) |
| 1815 | .custom_tags( | 1815 | .custom_tags( |
| 1816 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), | 1816 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), |
| 1817 | repo_coordinates | 1817 | repo_coordinates |
| @@ -1823,7 +1823,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1823 | ) | 1823 | ) |
| 1824 | .await? | 1824 | .await? |
| 1825 | .iter() | 1825 | .iter() |
| 1826 | .filter(|e| event_is_patch_set_root(e)) | 1826 | .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) |
| 1827 | .cloned() | 1827 | .cloned() |
| 1828 | .collect::<Vec<nostr::Event>>(); | 1828 | .collect::<Vec<nostr::Event>>(); |
| 1829 | proposals.sort_by_key(|e| e.created_at); | 1829 | proposals.sort_by_key(|e| e.created_at); |
| @@ -1831,7 +1831,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1831 | Ok(proposals) | 1831 | Ok(proposals) |
| 1832 | } | 1832 | } |
| 1833 | 1833 | ||
| 1834 | pub async fn get_all_proposal_patch_events_from_cache( | 1834 | pub async fn get_all_proposal_patch_pr_pr_update_events_from_cache( |
| 1835 | git_repo_path: &Path, | 1835 | git_repo_path: &Path, |
| 1836 | repo_ref: &RepoRef, | 1836 | repo_ref: &RepoRef, |
| 1837 | proposal_id: &nostr::EventId, | 1837 | proposal_id: &nostr::EventId, |
| @@ -1840,10 +1840,21 @@ pub async fn get_all_proposal_patch_events_from_cache( | |||
| 1840 | git_repo_path, | 1840 | git_repo_path, |
| 1841 | vec![ | 1841 | vec![ |
| 1842 | nostr::Filter::default() | 1842 | nostr::Filter::default() |
| 1843 | .kind(nostr::Kind::GitPatch) | 1843 | .kinds([ |
| 1844 | nostr::Kind::GitPatch, | ||
| 1845 | KIND_PULL_REQUEST, | ||
| 1846 | KIND_PULL_REQUEST_UPDATE, | ||
| 1847 | ]) | ||
| 1844 | .event(*proposal_id), | 1848 | .event(*proposal_id), |
| 1845 | nostr::Filter::default() | 1849 | nostr::Filter::default() |
| 1846 | .kind(nostr::Kind::GitPatch) | 1850 | .kinds([ |
| 1851 | nostr::Kind::GitPatch, | ||
| 1852 | KIND_PULL_REQUEST, | ||
| 1853 | KIND_PULL_REQUEST_UPDATE, | ||
| 1854 | ]) | ||
| 1855 | .custom_tag(SingleLetterTag::uppercase(Alphabet::E), *proposal_id), | ||
| 1856 | nostr::Filter::default() | ||
| 1857 | .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) | ||
| 1847 | .id(*proposal_id), | 1858 | .id(*proposal_id), |
| 1848 | ], | 1859 | ], |
| 1849 | ) | 1860 | ) |
| @@ -1876,8 +1887,20 @@ pub async fn get_all_proposal_patch_events_from_cache( | |||
| 1876 | git_repo_path, | 1887 | git_repo_path, |
| 1877 | vec![ | 1888 | vec![ |
| 1878 | nostr::Filter::default() | 1889 | nostr::Filter::default() |
| 1879 | .kind(nostr::Kind::GitPatch) | 1890 | .kinds([ |
| 1880 | .events(revision_roots) | 1891 | nostr::Kind::GitPatch, |
| 1892 | KIND_PULL_REQUEST, | ||
| 1893 | KIND_PULL_REQUEST_UPDATE, | ||
| 1894 | ]) | ||
| 1895 | .events(revision_roots.clone()) | ||
| 1896 | .authors(permissioned_users.clone()), | ||
| 1897 | nostr::Filter::default() | ||
| 1898 | .kinds([ | ||
| 1899 | nostr::Kind::GitPatch, | ||
| 1900 | KIND_PULL_REQUEST, | ||
| 1901 | KIND_PULL_REQUEST_UPDATE, | ||
| 1902 | ]) | ||
| 1903 | .custom_tags(SingleLetterTag::uppercase(Alphabet::E), revision_roots) | ||
| 1881 | .authors(permissioned_users.clone()), | 1904 | .authors(permissioned_users.clone()), |
| 1882 | ], | 1905 | ], |
| 1883 | ) | 1906 | ) |
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 80793bd..7b25daf 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs | |||
| @@ -70,11 +70,16 @@ pub fn event_is_patch_set_root(event: &Event) -> bool { | |||
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | pub fn event_is_revision_root(event: &Event) -> bool { | 72 | pub fn event_is_revision_root(event: &Event) -> bool { |
| 73 | event.kind.eq(&Kind::GitPatch) | 73 | (event.kind.eq(&Kind::GitPatch) |
| 74 | && event | 74 | && event |
| 75 | .tags | 75 | .tags |
| 76 | .iter() | 76 | .iter() |
| 77 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root")) | 77 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root"))) |
| 78 | || (event.kind.eq(&KIND_PULL_REQUEST) | ||
| 79 | && event | ||
| 80 | .tags | ||
| 81 | .iter() | ||
| 82 | .any(|t| t.as_slice().len() > 1 && t.as_slice()[0].eq("e"))) | ||
| 78 | } | 83 | } |
| 79 | 84 | ||
| 80 | pub fn patch_supports_commit_ids(event: &Event) -> bool { | 85 | pub fn patch_supports_commit_ids(event: &Event) -> bool { |
| @@ -534,13 +539,22 @@ pub fn commit_msg_from_patch_oneliner(patch: &nostr::Event) -> Result<String> { | |||
| 534 | } | 539 | } |
| 535 | 540 | ||
| 536 | pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> { | 541 | pub fn event_to_cover_letter(event: &nostr::Event) -> Result<CoverLetter> { |
| 537 | if !event_is_patch_set_root(event) { | 542 | if !event.kind.eq(&KIND_PULL_REQUEST) && !event_is_patch_set_root(event) { |
| 538 | bail!("event is not a patch set root event (root patch or cover letter)") | 543 | bail!("event is not a patch set root event (root patch or cover letter)") |
| 539 | } | 544 | } |
| 540 | 545 | ||
| 541 | let title = commit_msg_from_patch_oneliner(event)?; | 546 | let title = if event.kind.eq(&KIND_PULL_REQUEST) { |
| 542 | let full = commit_msg_from_patch(event)?; | 547 | tag_value(event, "subject").unwrap_or("untitled".to_owned()) |
| 543 | let description = full[title.len()..].trim().to_string(); | 548 | } else { |
| 549 | commit_msg_from_patch_oneliner(event)? | ||
| 550 | }; | ||
| 551 | let description = if event.kind.eq(&KIND_PULL_REQUEST) { | ||
| 552 | event.content.clone() | ||
| 553 | } else { | ||
| 554 | commit_msg_from_patch(event)?[title.len()..] | ||
| 555 | .trim() | ||
| 556 | .to_string() | ||
| 557 | }; | ||
| 544 | 558 | ||
| 545 | Ok(CoverLetter { | 559 | Ok(CoverLetter { |
| 546 | title: title.clone(), | 560 | title: title.clone(), |
| @@ -572,25 +586,25 @@ fn safe_branch_name_for_pr(s: &str) -> String { | |||
| 572 | .collect() | 586 | .collect() |
| 573 | } | 587 | } |
| 574 | 588 | ||
| 575 | pub fn get_most_recent_patch_with_ancestors( | 589 | pub fn get_pr_tip_event_or_most_recent_patch_with_ancestors( |
| 576 | mut patches: Vec<nostr::Event>, | 590 | mut proposal_events: Vec<nostr::Event>, |
| 577 | ) -> Result<Vec<nostr::Event>> { | 591 | ) -> Result<Vec<nostr::Event>> { |
| 578 | patches.sort_by_key(|e| e.created_at); | 592 | proposal_events.sort_by_key(|e| e.created_at); |
| 579 | 593 | ||
| 580 | let youngest_patch = patches.last().context("no patches found")?; | 594 | let youngest = proposal_events.last().context("no proposal events found")?; |
| 581 | 595 | ||
| 582 | let patches_with_youngest_created_at: Vec<&nostr::Event> = patches | 596 | let events_with_youngest_created_at: Vec<&nostr::Event> = proposal_events |
| 583 | .iter() | 597 | .iter() |
| 584 | .filter(|p| p.created_at.eq(&youngest_patch.created_at)) | 598 | .filter(|p| p.created_at.eq(&youngest.created_at)) |
| 585 | .collect(); | 599 | .collect(); |
| 586 | 600 | ||
| 587 | let mut res = vec![]; | 601 | let mut res = vec![]; |
| 588 | 602 | ||
| 589 | let mut event_id_to_search = patches_with_youngest_created_at | 603 | let mut event_id_to_search = events_with_youngest_created_at |
| 590 | .clone() | 604 | .clone() |
| 591 | .iter() | 605 | .iter() |
| 592 | .find(|p| { | 606 | .find(|p| { |
| 593 | !patches_with_youngest_created_at.iter().any(|p2| { | 607 | !events_with_youngest_created_at.iter().any(|p2| { |
| 594 | if let Ok(reply_to) = get_event_parent_id(p2) { | 608 | if let Ok(reply_to) = get_event_parent_id(p2) { |
| 595 | reply_to.eq(&p.id.to_string()) | 609 | reply_to.eq(&p.id.to_string()) |
| 596 | } else { | 610 | } else { |
| @@ -598,16 +612,18 @@ pub fn get_most_recent_patch_with_ancestors( | |||
| 598 | } | 612 | } |
| 599 | }) | 613 | }) |
| 600 | }) | 614 | }) |
| 601 | .context("failed to find patches_with_youngest_created_at")? | 615 | .context("failed to find events_with_youngest_created_at")? |
| 602 | .id | 616 | .id |
| 603 | .to_string(); | 617 | .to_string(); |
| 604 | 618 | ||
| 605 | while let Some(event) = patches | 619 | while let Some(event) = proposal_events |
| 606 | .iter() | 620 | .iter() |
| 607 | .find(|e| e.id.to_string().eq(&event_id_to_search)) | 621 | .find(|e| e.id.to_string().eq(&event_id_to_search)) |
| 608 | { | 622 | { |
| 609 | res.push(event.clone()); | 623 | res.push(event.clone()); |
| 610 | if event_is_patch_set_root(event) { | 624 | if [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) |
| 625 | || event_is_patch_set_root(event) | ||
| 626 | { | ||
| 611 | break; | 627 | break; |
| 612 | } | 628 | } |
| 613 | event_id_to_search = get_event_parent_id(event).unwrap_or_default(); | 629 | event_id_to_search = get_event_parent_id(event).unwrap_or_default(); |