From 3eb2354edb8e76428625d5645e110c30aa1ccc2a Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 18 Jul 2025 11:56:15 +0100 Subject: 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. --- src/lib/client.rs | 37 ++++++++++++++++++++++++++++++------- src/lib/git_events.rs | 50 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 24 deletions(-) (limited to 'src/lib') 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( git_repo_path, vec![ nostr::Filter::default() - .kind(nostr::Kind::GitPatch) + .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) .custom_tags( nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), repo_coordinates @@ -1823,7 +1823,7 @@ pub async fn get_proposals_and_revisions_from_cache( ) .await? .iter() - .filter(|e| event_is_patch_set_root(e)) + .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) .cloned() .collect::>(); proposals.sort_by_key(|e| e.created_at); @@ -1831,7 +1831,7 @@ pub async fn get_proposals_and_revisions_from_cache( Ok(proposals) } -pub async fn get_all_proposal_patch_events_from_cache( +pub async fn get_all_proposal_patch_pr_pr_update_events_from_cache( git_repo_path: &Path, repo_ref: &RepoRef, proposal_id: &nostr::EventId, @@ -1840,10 +1840,21 @@ pub async fn get_all_proposal_patch_events_from_cache( git_repo_path, vec![ nostr::Filter::default() - .kind(nostr::Kind::GitPatch) + .kinds([ + nostr::Kind::GitPatch, + KIND_PULL_REQUEST, + KIND_PULL_REQUEST_UPDATE, + ]) .event(*proposal_id), nostr::Filter::default() - .kind(nostr::Kind::GitPatch) + .kinds([ + nostr::Kind::GitPatch, + KIND_PULL_REQUEST, + KIND_PULL_REQUEST_UPDATE, + ]) + .custom_tag(SingleLetterTag::uppercase(Alphabet::E), *proposal_id), + nostr::Filter::default() + .kinds([nostr::Kind::GitPatch, KIND_PULL_REQUEST]) .id(*proposal_id), ], ) @@ -1876,8 +1887,20 @@ pub async fn get_all_proposal_patch_events_from_cache( git_repo_path, vec![ nostr::Filter::default() - .kind(nostr::Kind::GitPatch) - .events(revision_roots) + .kinds([ + nostr::Kind::GitPatch, + KIND_PULL_REQUEST, + KIND_PULL_REQUEST_UPDATE, + ]) + .events(revision_roots.clone()) + .authors(permissioned_users.clone()), + nostr::Filter::default() + .kinds([ + nostr::Kind::GitPatch, + KIND_PULL_REQUEST, + KIND_PULL_REQUEST_UPDATE, + ]) + .custom_tags(SingleLetterTag::uppercase(Alphabet::E), revision_roots) .authors(permissioned_users.clone()), ], ) 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 { } pub fn event_is_revision_root(event: &Event) -> bool { - event.kind.eq(&Kind::GitPatch) + (event.kind.eq(&Kind::GitPatch) && event .tags .iter() - .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root")) + .any(|t| t.as_slice().len() > 1 && t.as_slice()[1].eq("revision-root"))) + || (event.kind.eq(&KIND_PULL_REQUEST) + && event + .tags + .iter() + .any(|t| t.as_slice().len() > 1 && t.as_slice()[0].eq("e"))) } pub fn patch_supports_commit_ids(event: &Event) -> bool { @@ -534,13 +539,22 @@ pub fn commit_msg_from_patch_oneliner(patch: &nostr::Event) -> Result { } pub fn event_to_cover_letter(event: &nostr::Event) -> Result { - if !event_is_patch_set_root(event) { + if !event.kind.eq(&KIND_PULL_REQUEST) && !event_is_patch_set_root(event) { bail!("event is not a patch set root event (root patch or cover letter)") } - let title = commit_msg_from_patch_oneliner(event)?; - let full = commit_msg_from_patch(event)?; - let description = full[title.len()..].trim().to_string(); + let title = if event.kind.eq(&KIND_PULL_REQUEST) { + tag_value(event, "subject").unwrap_or("untitled".to_owned()) + } else { + commit_msg_from_patch_oneliner(event)? + }; + let description = if event.kind.eq(&KIND_PULL_REQUEST) { + event.content.clone() + } else { + commit_msg_from_patch(event)?[title.len()..] + .trim() + .to_string() + }; Ok(CoverLetter { title: title.clone(), @@ -572,25 +586,25 @@ fn safe_branch_name_for_pr(s: &str) -> String { .collect() } -pub fn get_most_recent_patch_with_ancestors( - mut patches: Vec, +pub fn get_pr_tip_event_or_most_recent_patch_with_ancestors( + mut proposal_events: Vec, ) -> Result> { - patches.sort_by_key(|e| e.created_at); + proposal_events.sort_by_key(|e| e.created_at); - let youngest_patch = patches.last().context("no patches found")?; + let youngest = proposal_events.last().context("no proposal events found")?; - let patches_with_youngest_created_at: Vec<&nostr::Event> = patches + let events_with_youngest_created_at: Vec<&nostr::Event> = proposal_events .iter() - .filter(|p| p.created_at.eq(&youngest_patch.created_at)) + .filter(|p| p.created_at.eq(&youngest.created_at)) .collect(); let mut res = vec![]; - let mut event_id_to_search = patches_with_youngest_created_at + let mut event_id_to_search = events_with_youngest_created_at .clone() .iter() .find(|p| { - !patches_with_youngest_created_at.iter().any(|p2| { + !events_with_youngest_created_at.iter().any(|p2| { if let Ok(reply_to) = get_event_parent_id(p2) { reply_to.eq(&p.id.to_string()) } else { @@ -598,16 +612,18 @@ pub fn get_most_recent_patch_with_ancestors( } }) }) - .context("failed to find patches_with_youngest_created_at")? + .context("failed to find events_with_youngest_created_at")? .id .to_string(); - while let Some(event) = patches + while let Some(event) = proposal_events .iter() .find(|e| e.id.to_string().eq(&event_id_to_search)) { res.push(event.clone()); - if event_is_patch_set_root(event) { + if [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) + || event_is_patch_set_root(event) + { break; } event_id_to_search = get_event_parent_id(event).unwrap_or_default(); -- cgit v1.2.3