diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-19 21:59:50 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-07-19 21:59:50 +0100 |
| commit | dde029b4f5988cfa830ebc36bee0f35c93bd2544 (patch) | |
| tree | cd2362d202afcc5c4f388ac17390b42715a5eddc /src | |
| parent | 55fd2693e314a87d32872cd74d3d563cd94201a2 (diff) | |
feat: integrate `fetch` into `list`
as part of a project to use fetch and the stored cache everywhere
Diffstat (limited to 'src')
| -rw-r--r-- | src/sub_commands/list.rs | 262 |
1 files changed, 116 insertions, 146 deletions
diff --git a/src/sub_commands/list.rs b/src/sub_commands/list.rs index d3f583f..2ae4cfb 100644 --- a/src/sub_commands/list.rs +++ b/src/sub_commands/list.rs | |||
| @@ -1,17 +1,19 @@ | |||
| 1 | use std::{io::Write, ops::Add}; | 1 | use std::{collections::HashSet, io::Write, ops::Add, path::Path}; |
| 2 | 2 | ||
| 3 | use anyhow::{bail, Context, Result}; | 3 | use anyhow::{bail, Context, Result}; |
| 4 | use nostr::nips::nip01::Coordinate; | ||
| 5 | use nostr_sdk::PublicKey; | ||
| 4 | 6 | ||
| 5 | use super::send::event_is_patch_set_root; | 7 | use super::send::event_is_patch_set_root; |
| 6 | #[cfg(not(test))] | ||
| 7 | use crate::client::Client; | ||
| 8 | #[cfg(test)] | 8 | #[cfg(test)] |
| 9 | use crate::client::MockConnect; | 9 | use crate::client::MockConnect; |
| 10 | #[cfg(not(test))] | ||
| 11 | use crate::client::{Client, Connect}; | ||
| 10 | use crate::{ | 12 | use crate::{ |
| 11 | cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, | 13 | cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, |
| 12 | client::Connect, | 14 | client::{fetching_with_report, get_events_from_cache, get_repo_ref_from_cache}, |
| 13 | git::{str_to_sha1, Repo, RepoActions}, | 15 | git::{str_to_sha1, Repo, RepoActions}, |
| 14 | repo_ref::{self, RepoRef, REPO_REF_KIND}, | 16 | repo_ref::{get_repo_coordinates, RepoRef}, |
| 15 | sub_commands::send::{ | 17 | sub_commands::send::{ |
| 16 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, | 18 | commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, |
| 17 | event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, | 19 | event_to_cover_letter, patch_supports_commit_ids, PATCH_KIND, |
| @@ -21,10 +23,7 @@ use crate::{ | |||
| 21 | #[allow(clippy::too_many_lines)] | 23 | #[allow(clippy::too_many_lines)] |
| 22 | pub async fn launch() -> Result<()> { | 24 | pub async fn launch() -> Result<()> { |
| 23 | let git_repo = Repo::discover().context("cannot find a git repository")?; | 25 | let git_repo = Repo::discover().context("cannot find a git repository")?; |
| 24 | 26 | let git_repo_path = git_repo.get_path()?; | |
| 25 | let root_commit = git_repo | ||
| 26 | .get_root_commit() | ||
| 27 | .context("failed to get root commit of the repository")?; | ||
| 28 | 27 | ||
| 29 | // TODO: check for empty repo | 28 | // TODO: check for empty repo |
| 30 | // TODO: check for existing maintaiers file | 29 | // TODO: check for existing maintaiers file |
| @@ -35,36 +34,47 @@ pub async fn launch() -> Result<()> { | |||
| 35 | #[cfg(test)] | 34 | #[cfg(test)] |
| 36 | let client = <MockConnect as std::default::Default>::default(); | 35 | let client = <MockConnect as std::default::Default>::default(); |
| 37 | 36 | ||
| 38 | let repo_ref = repo_ref::fetch( | 37 | let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?; |
| 39 | &git_repo, | ||
| 40 | root_commit.to_string(), | ||
| 41 | &client, | ||
| 42 | client.get_fallback_relays().clone(), | ||
| 43 | true, | ||
| 44 | ) | ||
| 45 | .await?; | ||
| 46 | 38 | ||
| 47 | println!("finding proposals..."); | 39 | fetching_with_report(git_repo_path, &client, &repo_coordinates).await?; |
| 48 | 40 | ||
| 49 | let proposal_events_and_revisions_and_statuses: Vec<nostr::Event> = | 41 | let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?; |
| 50 | find_proposal_and_status_events(&client, &repo_ref, &root_commit.to_string()).await?; | ||
| 51 | 42 | ||
| 52 | if proposal_events_and_revisions_and_statuses.is_empty() { | 43 | let proposals_and_revisions: Vec<nostr::Event> = |
| 44 | get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates()).await?; | ||
| 45 | if proposals_and_revisions.is_empty() { | ||
| 53 | println!("no proposals found... create one? try `ngit send`"); | 46 | println!("no proposals found... create one? try `ngit send`"); |
| 54 | return Ok(()); | 47 | return Ok(()); |
| 55 | } | 48 | } |
| 56 | let proposal_events: Vec<&nostr::Event> = proposal_events_and_revisions_and_statuses | 49 | |
| 57 | .iter() | 50 | let statuses: Vec<nostr::Event> = { |
| 58 | .filter(|e| !event_is_revision_root(e) && e.kind().as_u16().eq(&PATCH_KIND)) | 51 | let mut statuses = get_events_from_cache( |
| 59 | .collect::<Vec<&nostr::Event>>(); | 52 | git_repo_path, |
| 53 | vec![ | ||
| 54 | nostr::Filter::default() | ||
| 55 | .kinds(status_kinds().clone()) | ||
| 56 | .events(proposals_and_revisions.iter().map(nostr::Event::id)), | ||
| 57 | ], | ||
| 58 | ) | ||
| 59 | .await?; | ||
| 60 | statuses.sort_by_key(|e| e.created_at); | ||
| 61 | statuses.reverse(); | ||
| 62 | statuses | ||
| 63 | }; | ||
| 60 | 64 | ||
| 61 | let mut open_proposals: Vec<&nostr::Event> = vec![]; | 65 | let mut open_proposals: Vec<&nostr::Event> = vec![]; |
| 62 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; | 66 | let mut draft_proposals: Vec<&nostr::Event> = vec![]; |
| 63 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; | 67 | let mut closed_proposals: Vec<&nostr::Event> = vec![]; |
| 64 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; | 68 | let mut applied_proposals: Vec<&nostr::Event> = vec![]; |
| 65 | 69 | ||
| 66 | for proposal in &proposal_events { | 70 | let proposals: Vec<nostr::Event> = proposals_and_revisions |
| 67 | let status = if let Some(e) = proposal_events_and_revisions_and_statuses | 71 | .iter() |
| 72 | .filter(|e| !event_is_revision_root(e)) | ||
| 73 | .cloned() | ||
| 74 | .collect(); | ||
| 75 | |||
| 76 | for proposal in &proposals { | ||
| 77 | let status = if let Some(e) = statuses | ||
| 68 | .iter() | 78 | .iter() |
| 69 | .filter(|e| { | 79 | .filter(|e| { |
| 70 | status_kinds().contains(&e.kind()) | 80 | status_kinds().contains(&e.kind()) |
| @@ -89,11 +99,6 @@ pub async fn launch() -> Result<()> { | |||
| 89 | } | 99 | } |
| 90 | } | 100 | } |
| 91 | 101 | ||
| 92 | if proposal_events_and_revisions_and_statuses.is_empty() { | ||
| 93 | println!("no open proposals found... create one? try `ngit send`"); | ||
| 94 | return Ok(()); | ||
| 95 | } | ||
| 96 | |||
| 97 | let mut selected_status = STATUS_KIND_OPEN; | 102 | let mut selected_status = STATUS_KIND_OPEN; |
| 98 | 103 | ||
| 99 | loop { | 104 | loop { |
| @@ -106,10 +111,10 @@ pub async fn launch() -> Result<()> { | |||
| 106 | } else if selected_status == STATUS_KIND_APPLIED { | 111 | } else if selected_status == STATUS_KIND_APPLIED { |
| 107 | &applied_proposals | 112 | &applied_proposals |
| 108 | } else { | 113 | } else { |
| 109 | &proposal_events | 114 | &open_proposals |
| 110 | }; | 115 | }; |
| 111 | 116 | ||
| 112 | let prompt = if proposal_events.len().eq(&open_proposals.len()) { | 117 | let prompt = if proposals.len().eq(&open_proposals.len()) { |
| 113 | "all proposals" | 118 | "all proposals" |
| 114 | } else if selected_status == STATUS_KIND_OPEN { | 119 | } else if selected_status == STATUS_KIND_OPEN { |
| 115 | if open_proposals.is_empty() { | 120 | if open_proposals.is_empty() { |
| @@ -176,31 +181,12 @@ pub async fn launch() -> Result<()> { | |||
| 176 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) | 181 | let cover_letter = event_to_cover_letter(proposals_for_status[selected_index]) |
| 177 | .context("cannot extract proposal details from proposal root event")?; | 182 | .context("cannot extract proposal details from proposal root event")?; |
| 178 | 183 | ||
| 179 | println!("finding commits..."); | 184 | let commits_events: Vec<nostr::Event> = get_all_proposal_patch_events_from_cache( |
| 180 | 185 | git_repo_path, | |
| 181 | let commits_events: Vec<nostr::Event> = find_commits_for_proposal_root_events( | ||
| 182 | &client, | ||
| 183 | &[ | ||
| 184 | vec![proposals_for_status[selected_index]], | ||
| 185 | proposal_events_and_revisions_and_statuses | ||
| 186 | .iter() | ||
| 187 | .filter(|e| { | ||
| 188 | e.kind.as_u16().eq(&PATCH_KIND) | ||
| 189 | && e.tags.iter().any(|t| { | ||
| 190 | t.as_vec().len().gt(&1) | ||
| 191 | && t.as_vec()[1] | ||
| 192 | .eq(&proposals_for_status[selected_index].id.to_string()) | ||
| 193 | }) | ||
| 194 | }) | ||
| 195 | .collect::<Vec<&nostr::Event>>(), | ||
| 196 | ] | ||
| 197 | .concat(), | ||
| 198 | &repo_ref, | 186 | &repo_ref, |
| 187 | &proposals_for_status[selected_index].id(), | ||
| 199 | ) | 188 | ) |
| 200 | .await?; | 189 | .await?; |
| 201 | // for commit in &commits_events { | ||
| 202 | // println!("cevent: {:?}", commit.as_json()); | ||
| 203 | // } | ||
| 204 | 190 | ||
| 205 | let Ok(most_recent_proposal_patch_chain) = | 191 | let Ok(most_recent_proposal_patch_chain) = |
| 206 | get_most_recent_patch_with_ancestors(commits_events.clone()) | 192 | get_most_recent_patch_with_ancestors(commits_events.clone()) |
| @@ -829,109 +815,93 @@ pub fn status_kinds() -> Vec<nostr::Kind> { | |||
| 829 | ] | 815 | ] |
| 830 | } | 816 | } |
| 831 | 817 | ||
| 832 | pub async fn find_proposal_and_status_events( | 818 | pub async fn get_proposals_and_revisions_from_cache( |
| 833 | #[cfg(test)] client: &crate::client::MockConnect, | 819 | git_repo_path: &Path, |
| 834 | #[cfg(not(test))] client: &Client, | 820 | repo_coordinates: HashSet<Coordinate>, |
| 835 | repo_ref: &RepoRef, | ||
| 836 | root_commit: &str, | ||
| 837 | ) -> Result<Vec<nostr::Event>> { | 821 | ) -> Result<Vec<nostr::Event>> { |
| 838 | let repo_tags_filter = nostr::Filter::default().custom_tag( | 822 | let mut proposals = get_events_from_cache( |
| 839 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::A), | 823 | git_repo_path, |
| 840 | repo_ref | 824 | vec![ |
| 841 | .maintainers | 825 | nostr::Filter::default() |
| 842 | .iter() | 826 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 843 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)), | 827 | .custom_tag( |
| 844 | ); | 828 | nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A), |
| 845 | let mut proposals = client | 829 | repo_coordinates |
| 846 | .get_events( | 830 | .iter() |
| 847 | repo_ref.relays.clone(), | 831 | .map(std::string::ToString::to_string) |
| 848 | vec![ | 832 | .collect::<Vec<String>>(), |
| 849 | repo_tags_filter | 833 | ), |
| 850 | .clone() | 834 | ], |
| 851 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 835 | ) |
| 852 | .custom_tag( | 836 | .await? |
| 853 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | 837 | .iter() |
| 854 | vec!["root"], | 838 | .filter(|e| event_is_patch_set_root(e)) |
| 855 | ), | 839 | .cloned() |
| 856 | // also pick up proposals from the same repo but no target at our maintainers repo | 840 | .collect::<Vec<nostr::Event>>(); |
| 857 | // events | ||
| 858 | nostr::Filter::default() | ||
| 859 | .reference(root_commit) | ||
| 860 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 861 | .custom_tag( | ||
| 862 | nostr::SingleLetterTag::lowercase(nostr::Alphabet::T), | ||
| 863 | vec!["root"], | ||
| 864 | ), | ||
| 865 | repo_tags_filter.clone().kinds(status_kinds().clone()), | ||
| 866 | nostr::Filter::default() | ||
| 867 | .reference(root_commit) | ||
| 868 | .kinds(status_kinds().clone()), | ||
| 869 | ], | ||
| 870 | ) | ||
| 871 | .await | ||
| 872 | .context("cannot get proposal events")? | ||
| 873 | .iter() | ||
| 874 | .filter(|e| { | ||
| 875 | (event_is_patch_set_root(e) || status_kinds().contains(&e.kind())) | ||
| 876 | && (e | ||
| 877 | .tags | ||
| 878 | .iter() | ||
| 879 | .any(|t| t.as_vec().len() > 1 && t.as_vec()[1].eq(root_commit)) | ||
| 880 | || e.tags.iter().any(|t| { | ||
| 881 | t.as_vec().len() > 1 | ||
| 882 | && repo_ref | ||
| 883 | .maintainers | ||
| 884 | .iter() | ||
| 885 | .map(|m| format!("{REPO_REF_KIND}:{m}:{}", repo_ref.identifier)) | ||
| 886 | .any(|d| t.as_vec()[1].eq(&d)) | ||
| 887 | })) | ||
| 888 | }) | ||
| 889 | .map(std::borrow::ToOwned::to_owned) | ||
| 890 | .collect::<Vec<nostr::Event>>(); | ||
| 891 | proposals.sort_by_key(|e| e.created_at); | 841 | proposals.sort_by_key(|e| e.created_at); |
| 892 | proposals.reverse(); | 842 | proposals.reverse(); |
| 893 | Ok(proposals) | 843 | Ok(proposals) |
| 894 | } | 844 | } |
| 895 | 845 | ||
| 896 | pub async fn find_commits_for_proposal_root_events( | 846 | pub async fn get_all_proposal_patch_events_from_cache( |
| 897 | #[cfg(test)] client: &crate::client::MockConnect, | 847 | git_repo_path: &Path, |
| 898 | #[cfg(not(test))] client: &Client, | ||
| 899 | proposal_root_events: &Vec<&nostr::Event>, | ||
| 900 | repo_ref: &RepoRef, | 848 | repo_ref: &RepoRef, |
| 849 | proposal_id: &nostr::EventId, | ||
| 901 | ) -> Result<Vec<nostr::Event>> { | 850 | ) -> Result<Vec<nostr::Event>> { |
| 902 | let mut patch_events: Vec<nostr::Event> = client | 851 | let mut commit_events = get_events_from_cache( |
| 903 | .get_events( | 852 | git_repo_path, |
| 904 | repo_ref.relays.clone(), | 853 | vec![ |
| 854 | nostr::Filter::default() | ||
| 855 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 856 | .event(*proposal_id), | ||
| 857 | nostr::Filter::default() | ||
| 858 | .kind(nostr::Kind::Custom(PATCH_KIND)) | ||
| 859 | .id(*proposal_id), | ||
| 860 | ], | ||
| 861 | ) | ||
| 862 | .await?; | ||
| 863 | |||
| 864 | let permissioned_users: HashSet<PublicKey> = [ | ||
| 865 | repo_ref.maintainers.clone(), | ||
| 866 | vec![ | ||
| 867 | commit_events | ||
| 868 | .iter() | ||
| 869 | .find(|e| e.id().eq(proposal_id)) | ||
| 870 | .context("proposal not in cache")? | ||
| 871 | .author(), | ||
| 872 | ], | ||
| 873 | ] | ||
| 874 | .concat() | ||
| 875 | .iter() | ||
| 876 | .copied() | ||
| 877 | .collect(); | ||
| 878 | commit_events.retain(|e| permissioned_users.contains(e.author_ref())); | ||
| 879 | |||
| 880 | let revision_roots: HashSet<nostr::EventId> = commit_events | ||
| 881 | .iter() | ||
| 882 | .filter(|e| event_is_revision_root(e)) | ||
| 883 | .map(nostr::Event::id) | ||
| 884 | .collect(); | ||
| 885 | |||
| 886 | if !revision_roots.is_empty() { | ||
| 887 | for event in get_events_from_cache( | ||
| 888 | git_repo_path, | ||
| 905 | vec![ | 889 | vec![ |
| 906 | nostr::Filter::default() | 890 | nostr::Filter::default() |
| 907 | .kind(nostr::Kind::Custom(PATCH_KIND)) | 891 | .kind(nostr::Kind::Custom(PATCH_KIND)) |
| 908 | .events( | 892 | .events(revision_roots) |
| 909 | proposal_root_events | 893 | .authors(permissioned_users.clone()), |
| 910 | .iter() | ||
| 911 | .map(|e| e.id) | ||
| 912 | .collect::<Vec<nostr::EventId>>(), | ||
| 913 | ), | ||
| 914 | ], | 894 | ], |
| 915 | ) | 895 | ) |
| 916 | .await | 896 | .await? |
| 917 | .context("cannot fetch patch events")? | 897 | { |
| 918 | .iter() | 898 | commit_events.push(event); |
| 919 | .filter(|e| { | ||
| 920 | e.kind.as_u16() == PATCH_KIND | ||
| 921 | && e.tags.iter().any(|t| { | ||
| 922 | t.as_vec().len() > 2 | ||
| 923 | && proposal_root_events | ||
| 924 | .iter() | ||
| 925 | .any(|e| t.as_vec()[1].eq(&e.id.to_string())) | ||
| 926 | }) | ||
| 927 | }) | ||
| 928 | .map(std::borrow::ToOwned::to_owned) | ||
| 929 | .collect(); | ||
| 930 | |||
| 931 | for e in proposal_root_events { | ||
| 932 | if !event_is_cover_letter(e) && !patch_events.iter().any(|e2| e2.id.eq(&e.id)) { | ||
| 933 | patch_events.push(e.to_owned().clone()); | ||
| 934 | } | 899 | } |
| 935 | } | 900 | } |
| 936 | Ok(patch_events) | 901 | |
| 902 | Ok(commit_events | ||
| 903 | .iter() | ||
| 904 | .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(e.author_ref())) | ||
| 905 | .cloned() | ||
| 906 | .collect()) | ||
| 937 | } | 907 | } |