upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/list.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 11:32:05 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-09-04 14:23:54 +0100
commit771f944af447c202eba045936a36dee71ab797ac (patch)
treee691de4ebc8dde7ac4855e139881ff923bc254ce /src/bin/ngit/sub_commands/list.rs
parent949c6459aa7683453a7160423b689ceadb08954b (diff)
refactor: fix imports, etc based on restructure
move some functions out of ngit and into lib/mod and lib/git_events remove MockConnect from binaries so it is only used in the library. this was done: * mainly because automocks were not being imported from lib into each binary * but also because the these functions were being tested with MockConnect
Diffstat (limited to 'src/bin/ngit/sub_commands/list.rs')
-rw-r--r--src/bin/ngit/sub_commands/list.rs216
1 files changed, 16 insertions, 200 deletions
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs
index ac1f4ab..0755e3b 100644
--- a/src/bin/ngit/sub_commands/list.rs
+++ b/src/bin/ngit/sub_commands/list.rs
@@ -1,23 +1,25 @@
1use std::{collections::HashSet, io::Write, ops::Add, path::Path}; 1use std::{io::Write, ops::Add};
2 2
3use anyhow::{bail, Context, Result}; 3use anyhow::{bail, Context, Result};
4use nostr::nips::nip01::Coordinate; 4use ngit::{
5use nostr_sdk::{Kind, PublicKey}; 5 client::{get_all_proposal_patch_events_from_cache, get_proposals_and_revisions_from_cache},
6 6 git_events::{
7use super::send::event_is_patch_set_root; 7 get_commit_id_from_patch, get_most_recent_patch_with_ancestors, status_kinds, tag_value,
8#[cfg(test)] 8 },
9use crate::client::MockConnect; 9};
10#[cfg(not(test))] 10use nostr_sdk::Kind;
11use crate::client::{Client, Connect}; 11
12use crate::{ 12use crate::{
13 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms}, 13 cli_interactor::{Interactor, InteractorPrompt, PromptChoiceParms, PromptConfirmParms},
14 client::{fetching_with_report, get_events_from_cache, get_repo_ref_from_cache}, 14 client::{
15 fetching_with_report, get_events_from_cache, get_repo_ref_from_cache, Client, Connect,
16 },
15 git::{str_to_sha1, Repo, RepoActions}, 17 git::{str_to_sha1, Repo, RepoActions},
16 repo_ref::{get_repo_coordinates, RepoRef}, 18 git_events::{
17 sub_commands::send::{ 19 commit_msg_from_patch_oneliner, event_is_revision_root, event_to_cover_letter,
18 commit_msg_from_patch_oneliner, event_is_cover_letter, event_is_revision_root, 20 patch_supports_commit_ids,
19 event_to_cover_letter, patch_supports_commit_ids,
20 }, 21 },
22 repo_ref::get_repo_coordinates,
21}; 23};
22 24
23#[allow(clippy::too_many_lines)] 25#[allow(clippy::too_many_lines)]
@@ -29,10 +31,7 @@ pub async fn launch() -> Result<()> {
29 // TODO: check for existing maintaiers file 31 // TODO: check for existing maintaiers file
30 // TODO: check for other claims 32 // TODO: check for other claims
31 33
32 #[cfg(not(test))]
33 let client = Client::default(); 34 let client = Client::default();
34 #[cfg(test)]
35 let client = <MockConnect as std::default::Default>::default();
36 35
37 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?; 36 let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?;
38 37
@@ -721,186 +720,3 @@ fn check_clean(git_repo: &Repo) -> Result<()> {
721 } 720 }
722 Ok(()) 721 Ok(())
723} 722}
724
725pub fn tag_value(event: &nostr::Event, tag_name: &str) -> Result<String> {
726 Ok(event
727 .tags
728 .iter()
729 .find(|t| t.as_vec()[0].eq(tag_name))
730 .context(format!("tag '{tag_name}'not present"))?
731 .as_vec()[1]
732 .clone())
733}
734
735pub fn get_commit_id_from_patch(event: &nostr::Event) -> Result<String> {
736 let value = tag_value(event, "commit");
737
738 if value.is_ok() {
739 value
740 } else if event.content.starts_with("From ") && event.content.len().gt(&45) {
741 Ok(event.content[5..45].to_string())
742 } else {
743 bail!("event is not a patch")
744 }
745}
746
747fn get_event_parent_id(event: &nostr::Event) -> Result<String> {
748 Ok(if let Some(reply_tag) = event
749 .tags
750 .iter()
751 .find(|t| t.as_vec().len().gt(&3) && t.as_vec()[3].eq("reply"))
752 {
753 reply_tag
754 } else {
755 event
756 .tags
757 .iter()
758 .find(|t| t.as_vec().len().gt(&3) && t.as_vec()[3].eq("root"))
759 .context("no reply or root e tag present".to_string())?
760 }
761 .as_vec()[1]
762 .clone())
763}
764
765pub fn get_most_recent_patch_with_ancestors(
766 mut patches: Vec<nostr::Event>,
767) -> Result<Vec<nostr::Event>> {
768 patches.sort_by_key(|e| e.created_at);
769
770 let youngest_patch = patches.last().context("no patches found")?;
771
772 let patches_with_youngest_created_at: Vec<&nostr::Event> = patches
773 .iter()
774 .filter(|p| p.created_at.eq(&youngest_patch.created_at))
775 .collect();
776
777 let mut res = vec![];
778
779 let mut event_id_to_search = patches_with_youngest_created_at
780 .clone()
781 .iter()
782 .find(|p| {
783 !patches_with_youngest_created_at.iter().any(|p2| {
784 if let Ok(reply_to) = get_event_parent_id(p2) {
785 reply_to.eq(&p.id.to_string())
786 } else {
787 false
788 }
789 })
790 })
791 .context("cannot find patches_with_youngest_created_at")?
792 .id
793 .to_string();
794
795 while let Some(event) = patches
796 .iter()
797 .find(|e| e.id.to_string().eq(&event_id_to_search))
798 {
799 res.push(event.clone());
800 if event_is_patch_set_root(event) {
801 break;
802 }
803 event_id_to_search = get_event_parent_id(event).unwrap_or_default();
804 }
805 Ok(res)
806}
807
808pub fn status_kinds() -> Vec<nostr::Kind> {
809 vec![
810 nostr::Kind::GitStatusOpen,
811 nostr::Kind::GitStatusApplied,
812 nostr::Kind::GitStatusClosed,
813 nostr::Kind::GitStatusDraft,
814 ]
815}
816
817pub async fn get_proposals_and_revisions_from_cache(
818 git_repo_path: &Path,
819 repo_coordinates: HashSet<Coordinate>,
820) -> Result<Vec<nostr::Event>> {
821 let mut proposals = get_events_from_cache(
822 git_repo_path,
823 vec![
824 nostr::Filter::default()
825 .kind(nostr::Kind::GitPatch)
826 .custom_tag(
827 nostr::SingleLetterTag::lowercase(nostr_sdk::Alphabet::A),
828 repo_coordinates
829 .iter()
830 .map(std::string::ToString::to_string)
831 .collect::<Vec<String>>(),
832 ),
833 ],
834 )
835 .await?
836 .iter()
837 .filter(|e| event_is_patch_set_root(e))
838 .cloned()
839 .collect::<Vec<nostr::Event>>();
840 proposals.sort_by_key(|e| e.created_at);
841 proposals.reverse();
842 Ok(proposals)
843}
844
845pub async fn get_all_proposal_patch_events_from_cache(
846 git_repo_path: &Path,
847 repo_ref: &RepoRef,
848 proposal_id: &nostr::EventId,
849) -> Result<Vec<nostr::Event>> {
850 let mut commit_events = get_events_from_cache(
851 git_repo_path,
852 vec![
853 nostr::Filter::default()
854 .kind(nostr::Kind::GitPatch)
855 .event(*proposal_id),
856 nostr::Filter::default()
857 .kind(nostr::Kind::GitPatch)
858 .id(*proposal_id),
859 ],
860 )
861 .await?;
862
863 let permissioned_users: HashSet<PublicKey> = [
864 repo_ref.maintainers.clone(),
865 vec![
866 commit_events
867 .iter()
868 .find(|e| e.id().eq(proposal_id))
869 .context("proposal not in cache")?
870 .author(),
871 ],
872 ]
873 .concat()
874 .iter()
875 .copied()
876 .collect();
877 commit_events.retain(|e| permissioned_users.contains(&e.author()));
878
879 let revision_roots: HashSet<nostr::EventId> = commit_events
880 .iter()
881 .filter(|e| event_is_revision_root(e))
882 .map(nostr::Event::id)
883 .collect();
884
885 if !revision_roots.is_empty() {
886 for event in get_events_from_cache(
887 git_repo_path,
888 vec![
889 nostr::Filter::default()
890 .kind(nostr::Kind::GitPatch)
891 .events(revision_roots)
892 .authors(permissioned_users.clone()),
893 ],
894 )
895 .await?
896 {
897 commit_events.push(event);
898 }
899 }
900
901 Ok(commit_events
902 .iter()
903 .filter(|e| !event_is_cover_letter(e) && permissioned_users.contains(&e.author()))
904 .cloned()
905 .collect())
906}