upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr/push.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/git_remote_nostr/push.rs')
-rw-r--r--src/bin/git_remote_nostr/push.rs249
1 files changed, 186 insertions, 63 deletions
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index dde4ab0..40e9584 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -25,7 +25,7 @@ use ngit::{
25 nostr_url::{CloneUrl, NostrUrlDecoded}, 25 nostr_url::{CloneUrl, NostrUrlDecoded},
26 oid_to_shorthand_string, 26 oid_to_shorthand_string,
27 }, 27 },
28 git_events::{self, get_event_root}, 28 git_events::{self, event_to_cover_letter, get_event_root},
29 login::{self, get_curent_user}, 29 login::{self, get_curent_user},
30 repo_ref::{self, get_repo_config_from_yaml}, 30 repo_ref::{self, get_repo_config_from_yaml},
31 repo_state, 31 repo_state,
@@ -965,72 +965,181 @@ async fn get_merged_status_events(
965 }; 965 };
966 let (ahead, _) = 966 let (ahead, _) =
967 git_repo.get_commits_ahead_behind(&tip_of_remote_branch, &tip_of_pushed_branch)?; 967 git_repo.get_commits_ahead_behind(&tip_of_remote_branch, &tip_of_pushed_branch)?;
968 for commit_hash in ahead {
969 let commit = git_repo.git_repo.find_commit(sha1_to_oid(&commit_hash)?)?;
970 if commit.parent_count() > 1 {
971 // merge commit
972 for parent in commit.parents() {
973 // lookup parent id
974 let commit_events = get_events_from_local_cache(
975 git_repo.get_path()?,
976 vec![
977 nostr::Filter::default()
978 .kind(nostr::Kind::GitPatch)
979 .reference(parent.id().to_string()),
980 ],
981 )
982 .await?;
983 if let Some(commit_event) = commit_events.iter().find(|e| {
984 e.tags.iter().any(|t| {
985 t.as_slice()[0].eq("commit")
986 && t.as_slice()[1].eq(&parent.id().to_string())
987 })
988 }) {
989 let (proposal_id, revision_id) =
990 get_proposal_and_revision_root_from_patch(git_repo, commit_event)
991 .await?;
992 term.write_line(
993 format!(
994 "merge commit {}: create nostr proposal status event",
995 &commit.id().to_string()[..7],
996 )
997 .as_str(),
998 )?;
999 968
1000 events.push( 969 let commit_events = get_events_from_local_cache(
1001 create_merge_status( 970 git_repo.get_path()?,
1002 signer, 971 vec![
1003 repo_ref, 972 nostr::Filter::default().kind(nostr::Kind::GitPatch),
1004 &get_event_from_cache_by_id(git_repo, &proposal_id).await?, 973 // TODO: limit by repo_ref
1005 &if let Some(revision_id) = revision_id { 974 ],
1006 Some( 975 )
1007 get_event_from_cache_by_id(git_repo, &revision_id) 976 .await?;
1008 .await?, 977
1009 ) 978 let merged_proposals_info =
1010 } else { 979 get_merged_proposals_info(git_repo, &ahead, &commit_events).await?;
1011 None 980
1012 }, 981 for event in
1013 &commit_hash, 982 create_merge_events(term, git_repo, repo_ref, signer, &merged_proposals_info)
1014 commit_event.id, 983 .await?
1015 ) 984 {
1016 .await?, 985 events.push(event);
1017 ); 986 }
987 }
988 }
989 Ok(events)
990}
991
992/// (`proposal_id`, `revision_id`)
993type MergedProposalsInfo =
994 HashMap<EventId, (Option<EventId>, HashMap<Sha1Hash, MergedPRCommitType>)>;
995
996async fn get_merged_proposals_info(
997 git_repo: &Repo,
998 ahead: &Vec<Sha1Hash>,
999 available_patches: &[Event],
1000) -> Result<MergedProposalsInfo> {
1001 let mut proposals: MergedProposalsInfo = HashMap::new();
1002
1003 for commit_hash in ahead {
1004 let commit = git_repo.git_repo.find_commit(sha1_to_oid(commit_hash)?)?;
1005 // three-way merge - just to set merge commit id as the merged branch commits
1006 // are in ahead
1007 if commit.parent_count() > 1 {
1008 for parent in commit.parents() {
1009 for patch_event in available_patches
1010 .iter()
1011 .filter(|e| {
1012 e.tags.iter().any(|t| {
1013 t.as_slice()[0].eq("commit")
1014 && t.as_slice()[1].eq(&parent.id().to_string())
1015 })
1016 })
1017 .collect::<Vec<&Event>>()
1018 {
1019 if let Ok((proposal_id, revision_id)) =
1020 get_proposal_and_revision_root_from_patch(git_repo, patch_event).await
1021 {
1022 let (entry_revision_id, merged_patches) =
1023 proposals.entry(proposal_id).or_default();
1024 if entry_revision_id == &revision_id {
1025 merged_patches.insert(*commit_hash, MergedPRCommitType::MergeCommit);
1018 } 1026 }
1019 } 1027 }
1020 } 1028 }
1021 } 1029 }
1030 } else {
1031 // three way merge or fast forward merge commits
1032 // note: ahead included commits of three-way merged branches
1033 for patch_event in available_patches
1034 .iter()
1035 .filter(|e| {
1036 e.tags.iter().any(|t| {
1037 t.as_slice()[0].eq("commit") && t.as_slice()[1].eq(&commit_hash.to_string())
1038 })
1039 })
1040 .collect::<Vec<&Event>>()
1041 {
1042 if let Ok((proposal_id, revision_id)) =
1043 get_proposal_and_revision_root_from_patch(git_repo, patch_event).await
1044 {
1045 let (entry_revision_id, merged_patches) =
1046 proposals.entry(proposal_id).or_default();
1047 // ignore revisions without all the merged commits
1048 if entry_revision_id == &revision_id {
1049 merged_patches.insert(
1050 *commit_hash,
1051 MergedPRCommitType::PatchCommit {
1052 event_id: patch_event.id,
1053 },
1054 );
1055 }
1056 }
1057 }
1058 }
1059 }
1060 Ok(proposals)
1061}
1062
1063async fn create_merge_events(
1064 term: &console::Term,
1065 git_repo: &Repo,
1066 repo_ref: &RepoRef,
1067 signer: &Arc<dyn NostrSigner>,
1068 merged_proposals_info: &MergedProposalsInfo,
1069) -> Result<Vec<Event>> {
1070 let mut events = vec![];
1071 for (proposal_id, (revision_id, merged_patches)) in merged_proposals_info {
1072 let proposal = get_event_from_cache_by_id(git_repo, proposal_id).await?;
1073
1074 if merged_patches
1075 .values()
1076 .any(|m| *m == MergedPRCommitType::MergeCommit)
1077 {
1078 term.write_line(
1079 format!(
1080 "merge commit {}: create nostr proposal status event",
1081 &merged_patches.keys().next().unwrap().to_string()[..7],
1082 )
1083 .as_str(),
1084 )?;
1085 } else {
1086 term.write_line(
1087 format!(
1088 "fast-forward merge: create nostr proposal status event for {}",
1089 event_to_cover_letter(&proposal)?.get_branch_name()?,
1090 )
1091 .as_str(),
1092 )?;
1022 } 1093 }
1094 events.push(
1095 create_merge_status(
1096 signer,
1097 repo_ref,
1098 &proposal,
1099 &if let Some(revision_id) = revision_id {
1100 Some(get_event_from_cache_by_id(git_repo, revision_id).await?)
1101 } else {
1102 None
1103 },
1104 if merged_patches
1105 .values()
1106 .any(|m| m == &MergedPRCommitType::MergeCommit)
1107 {
1108 vec![*merged_patches.keys().next().unwrap()]
1109 } else {
1110 let mut t: Vec<Sha1Hash> = merged_patches.keys().copied().collect();
1111 t.reverse();
1112 t
1113 },
1114 merged_patches
1115 .values()
1116 .filter_map(|m| match m {
1117 MergedPRCommitType::MergeCommit => None,
1118 MergedPRCommitType::PatchApplied { event_id }
1119 | MergedPRCommitType::PatchCommit { event_id } => Some(*event_id),
1120 })
1121 .collect(),
1122 )
1123 .await?,
1124 );
1023 } 1125 }
1024 Ok(events) 1126 Ok(events)
1025} 1127}
1026 1128
1129#[derive(PartialEq)]
1130enum MergedPRCommitType {
1131 MergeCommit,
1132 PatchCommit { event_id: EventId },
1133 PatchApplied { event_id: EventId },
1134}
1135
1027async fn create_merge_status( 1136async fn create_merge_status(
1028 signer: &Arc<dyn NostrSigner>, 1137 signer: &Arc<dyn NostrSigner>,
1029 repo_ref: &RepoRef, 1138 repo_ref: &RepoRef,
1030 proposal: &Event, 1139 proposal: &Event,
1031 revision: &Option<Event>, 1140 revision: &Option<Event>,
1032 merge_commit: &Sha1Hash, 1141 merge_commits: Vec<Sha1Hash>,
1033 merged_patch: EventId, 1142 merged_patches: Vec<EventId>,
1034) -> Result<Event> { 1143) -> Result<Event> {
1035 let mut public_keys = repo_ref 1144 let mut public_keys = repo_ref
1036 .maintainers 1145 .maintainers
@@ -1056,14 +1165,20 @@ async fn create_merge_status(
1056 public_key: None, 1165 public_key: None,
1057 uppercase: false, 1166 uppercase: false,
1058 }), 1167 }),
1059 Tag::from_standardized(nostr::TagStandard::Event {
1060 event_id: merged_patch,
1061 relay_url: repo_ref.relays.first().cloned(),
1062 marker: Some(Marker::Mention),
1063 public_key: None,
1064 uppercase: false,
1065 }),
1066 ], 1168 ],
1169 // Tags for merged patches
1170 merged_patches
1171 .iter()
1172 .map(|merged_patch| {
1173 Tag::from_standardized(nostr::TagStandard::Event {
1174 event_id: *merged_patch,
1175 relay_url: repo_ref.relays.first().cloned(),
1176 marker: Some(Marker::Mention),
1177 public_key: None,
1178 uppercase: false,
1179 })
1180 })
1181 .collect::<Vec<Tag>>(),
1067 if let Some(revision) = revision { 1182 if let Some(revision) = revision {
1068 vec![Tag::from_standardized(nostr::TagStandard::Event { 1183 vec![Tag::from_standardized(nostr::TagStandard::Event {
1069 event_id: revision.id, 1184 event_id: revision.id,
@@ -1085,14 +1200,22 @@ async fn create_merge_status(
1085 Tag::from_standardized(nostr::TagStandard::Reference( 1200 Tag::from_standardized(nostr::TagStandard::Reference(
1086 repo_ref.root_commit.to_string(), 1201 repo_ref.root_commit.to_string(),
1087 )), 1202 )),
1088 Tag::from_standardized(nostr::TagStandard::Reference(format!(
1089 "{merge_commit}"
1090 ))),
1091 Tag::custom( 1203 Tag::custom(
1092 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("merge-commit-id")), 1204 nostr::TagKind::Custom(std::borrow::Cow::Borrowed("merge-commit-id")),
1093 vec![format!("{merge_commit}")], 1205 merge_commits
1206 .iter()
1207 .map(|merge_commit| format!("{merge_commit}"))
1208 .collect::<Vec<String>>(),
1094 ), 1209 ),
1095 ], 1210 ],
1211 merge_commits
1212 .iter()
1213 .map(|merge_commit| {
1214 Tag::from_standardized(nostr::TagStandard::Reference(format!(
1215 "{merge_commit}"
1216 )))
1217 })
1218 .collect::<Vec<Tag>>(),
1096 ] 1219 ]
1097 .concat(), 1220 .concat(),
1098 ), 1221 ),