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/lib | |
| 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/lib')
| -rw-r--r-- | src/lib/client.rs | 37 | ||||
| -rw-r--r-- | src/lib/git_events.rs | 50 |
2 files changed, 63 insertions, 24 deletions
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(); |