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/bin/git_remote_nostr/utils.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'src/bin/git_remote_nostr/utils.rs') 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}; use git2::Repository; use ngit::{ client::{ - get_all_proposal_patch_events_from_cache, get_events_from_local_cache, + get_all_proposal_patch_pr_pr_update_events_from_cache, get_events_from_local_cache, get_proposals_and_revisions_from_cache, }, git::{ @@ -18,7 +18,7 @@ use ngit::{ nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, }, git_events::{ - event_is_revision_root, get_most_recent_patch_with_ancestors, + event_is_revision_root, get_pr_tip_event_or_most_recent_patch_with_ancestors, is_event_proposal_root_for_branch, status_kinds, }, repo_ref::RepoRef, @@ -140,12 +140,15 @@ pub async fn get_open_or_draft_proposals( Kind::GitStatusOpen }; if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) { - if let Ok(commits_events) = - get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id) - .await + if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( + git_repo_path, + repo_ref, + &proposal.id, + ) + .await { if let Ok(most_recent_proposal_patch_chain) = - get_most_recent_patch_with_ancestors(commits_events.clone()) + get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) { open_or_draft_proposals .insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); @@ -172,11 +175,15 @@ pub async fn get_all_proposals( let mut all_proposals = HashMap::new(); for proposal in proposals { - if let Ok(commits_events) = - get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id).await + if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( + git_repo_path, + repo_ref, + &proposal.id, + ) + .await { if let Ok(most_recent_proposal_patch_chain) = - get_most_recent_patch_with_ancestors(commits_events.clone()) + get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) { all_proposals.insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); } -- cgit v1.2.3 From b4253bb0c543ceef896e0a95482b5403a29a7878 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 23 Jul 2025 08:55:03 +0100 Subject: fix(status): only use events from author and maintainers instead of status events from any pubkey --- src/bin/git_remote_nostr/utils.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/bin/git_remote_nostr/utils.rs') diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index d0b4e7e..563c0d8 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs @@ -131,6 +131,7 @@ pub async fn get_open_or_draft_proposals( && e.tags.iter().any(|t| { t.as_slice().len() > 1 && t.as_slice()[1].eq(&proposal.id.to_string()) }) + && (proposal.pubkey.eq(&e.pubkey) || repo_ref.maintainers.contains(&e.pubkey)) }) .collect::>() .first() -- cgit v1.2.3 From f7299cc5fd2276db8d9bb7778c34ddbe5b3a8e48 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 23 Jul 2025 10:33:20 +0100 Subject: feat(pr): patch upgraded to pr inherit pr status when a patch is upgraded to a pr, eg because new commits would be too large to be additional patches, the patch receives a closed staus and the new pr 'e' tags the original root patch. we therefore need to inherit the new pr's status instead of using the closed status. the closed status was used so that clients don't have to support pr revisions of patches, and still have a good UX. --- src/bin/git_remote_nostr/utils.rs | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'src/bin/git_remote_nostr/utils.rs') diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index 563c0d8..8433967 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs @@ -18,8 +18,9 @@ use ngit::{ nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, }, git_events::{ - event_is_revision_root, get_pr_tip_event_or_most_recent_patch_with_ancestors, - is_event_proposal_root_for_branch, status_kinds, + KIND_PULL_REQUEST, event_is_revision_root, + get_pr_tip_event_or_most_recent_patch_with_ancestors, is_event_proposal_root_for_branch, + status_kinds, }, repo_ref::RepoRef, }; @@ -123,8 +124,8 @@ pub async fn get_open_or_draft_proposals( }; let mut open_or_draft_proposals = HashMap::new(); - for proposal in proposals { - let status = if let Some(e) = statuses + let get_status = |proposal: &Event| { + if let Some(e) = statuses .iter() .filter(|e| { status_kinds().contains(&e.kind) @@ -139,8 +140,32 @@ pub async fn get_open_or_draft_proposals( e.kind } else { Kind::GitStatusOpen - }; - if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) { + } + }; + + let is_proposal_pr_revision_of_patch = |proposal: &Event, patch: &Event| { + proposal.kind.eq(&KIND_PULL_REQUEST) + && proposal.tags.clone().into_iter().any(|t| { + t.as_slice().len() > 1 + && t.as_slice()[0].eq("e") + && t.as_slice()[1].eq(&patch.id.to_string()) + && [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&get_status(proposal)) + }) + }; + + for proposal in &proposals { + let status = get_status(proposal); + if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) + || // or patch has been revised as a PR which is open or draft + ( + status.eq(&Kind::GitStatusClosed) && + proposal.kind.eq(&Kind::GitPatch) && + proposals.iter() + .any(|p| { + is_proposal_pr_revision_of_patch(p, proposal) + }) + ) + { if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( git_repo_path, repo_ref, @@ -151,8 +176,10 @@ pub async fn get_open_or_draft_proposals( if let Ok(most_recent_proposal_patch_chain) = get_pr_tip_event_or_most_recent_patch_with_ancestors(commits_events.clone()) { - open_or_draft_proposals - .insert(proposal.id, (proposal, most_recent_proposal_patch_chain)); + open_or_draft_proposals.insert( + proposal.id, + (proposal.clone(), most_recent_proposal_patch_chain), + ); } } } -- cgit v1.2.3 From 9357b62b9824299be6fc85b09f57d93d3902f79a Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 25 Jul 2025 11:48:22 +0100 Subject: refactor: abstract `get_status` for use by `ngit list` --- src/bin/git_remote_nostr/utils.rs | 58 ++++++++------------------------------- src/bin/ngit/sub_commands/list.rs | 5 +++- src/lib/git_events.rs | 56 ++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 48 deletions(-) (limited to 'src/bin/git_remote_nostr/utils.rs') diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index 8433967..2cb85bf 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs @@ -18,9 +18,8 @@ use ngit::{ nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, }, git_events::{ - KIND_PULL_REQUEST, event_is_revision_root, - get_pr_tip_event_or_most_recent_patch_with_ancestors, is_event_proposal_root_for_branch, - status_kinds, + event_is_revision_root, get_pr_tip_event_or_most_recent_patch_with_ancestors, get_status, + is_event_proposal_root_for_branch, status_kinds, }, repo_ref::RepoRef, }; @@ -104,7 +103,10 @@ pub async fn get_open_or_draft_proposals( get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()) .await? .iter() - .filter(|e| !event_is_revision_root(e)) + .filter(|e| + // If we wanted to treat to list Pull Requests that revise a Patch we would do this: + // e.kind.eq(&KIND_PULL_REQUEST) || + !event_is_revision_root(e)) .cloned() .collect(); @@ -124,48 +126,9 @@ pub async fn get_open_or_draft_proposals( }; let mut open_or_draft_proposals = HashMap::new(); - let get_status = |proposal: &Event| { - if let Some(e) = statuses - .iter() - .filter(|e| { - status_kinds().contains(&e.kind) - && e.tags.iter().any(|t| { - t.as_slice().len() > 1 && t.as_slice()[1].eq(&proposal.id.to_string()) - }) - && (proposal.pubkey.eq(&e.pubkey) || repo_ref.maintainers.contains(&e.pubkey)) - }) - .collect::>() - .first() - { - e.kind - } else { - Kind::GitStatusOpen - } - }; - - let is_proposal_pr_revision_of_patch = |proposal: &Event, patch: &Event| { - proposal.kind.eq(&KIND_PULL_REQUEST) - && proposal.tags.clone().into_iter().any(|t| { - t.as_slice().len() > 1 - && t.as_slice()[0].eq("e") - && t.as_slice()[1].eq(&patch.id.to_string()) - && [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&get_status(proposal)) - }) - }; - for proposal in &proposals { - let status = get_status(proposal); - if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) - || // or patch has been revised as a PR which is open or draft - ( - status.eq(&Kind::GitStatusClosed) && - proposal.kind.eq(&Kind::GitPatch) && - proposals.iter() - .any(|p| { - is_proposal_pr_revision_of_patch(p, proposal) - }) - ) - { + let status = get_status(proposal, repo_ref, &statuses, &proposals); + if [Kind::GitStatusOpen, Kind::GitStatusDraft].contains(&status) { if let Ok(commits_events) = get_all_proposal_patch_pr_pr_update_events_from_cache( git_repo_path, repo_ref, @@ -196,7 +159,10 @@ pub async fn get_all_proposals( get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()) .await? .iter() - .filter(|e| !event_is_revision_root(e)) + .filter(|e| + // If we wanted to treat to list Pull Requests that revise a Patch we would do this: + // e.kind.eq(&KIND_PULL_REQUEST) || + !event_is_revision_root(e)) .cloned() .collect(); diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index 9e35b33..95e17f3 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs @@ -72,7 +72,10 @@ pub async fn launch() -> Result<()> { let proposals: Vec = proposals_and_revisions .iter() - .filter(|e| !event_is_revision_root(e)) + .filter(|e| + // If we wanted to treat to list Pull Requests that revise a Patch we would do this: + // e.kind.eq(&KIND_PULL_REQUEST) || + !event_is_revision_root(e)) .cloned() .collect(); diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 7cd64ab..2e1f215 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs @@ -835,7 +835,61 @@ pub fn is_event_proposal_root_for_branch( || cl .get_branch_name_with_pr_prefix_and_shorthand_id() .is_ok_and(|s| s.eq(&branch_name)) - }) && !event_is_revision_root(e)) + }) && ( + // If we wanted to treat to list Pull Requests that revise a Patch we would do this: + // Note: whilst this the the case elsewhere event_is_revision_root is used, there is more to + // think about here? + // e.kind.eq(&KIND_PULL_REQUEST) || + !event_is_revision_root(e) + )) +} + +pub fn get_status( + proposal: &Event, + repo_ref: &RepoRef, + all_status_in_repo: &[Event], + all_pr_roots_in_repo: &[Event], +) -> Kind { + let get_direct_status = |proposal: &Event| { + if let Some(e) = all_status_in_repo + .iter() + .filter(|e| { + status_kinds().contains(&e.kind) + && e.tags.iter().any(|t| { + t.as_slice().len() > 1 && t.as_slice()[1].eq(&proposal.id.to_string()) + }) + && (proposal.pubkey.eq(&e.pubkey) || repo_ref.maintainers.contains(&e.pubkey)) + }) + .collect::>() + .first() + { + e.kind + } else { + Kind::GitStatusOpen + } + }; + let is_proposal_pr_revision_of_patch = |proposal: &Event, patch: &Event| { + proposal.kind.eq(&KIND_PULL_REQUEST) + && proposal.tags.clone().into_iter().any(|t| { + t.as_slice().len() > 1 + && t.as_slice()[0].eq("e") + && t.as_slice()[1].eq(&patch.id.to_string()) + }) + }; + + let direct_status = get_direct_status(proposal); + if direct_status.eq(&Kind::GitStatusClosed) && proposal.kind.eq(&Kind::GitPatch) { + if let Some(pr_revision) = all_pr_roots_in_repo + .iter() + .find(|p| is_proposal_pr_revision_of_patch(p, proposal)) + { + get_direct_status(pr_revision) + } else { + direct_status + } + } else { + direct_status + } } #[cfg(test)] -- cgit v1.2.3