upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-12-06 22:16:44 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-12-06 22:16:44 +0000
commitf0d0e1ba1cba11d3a98a5ab0c7f1dc72b6bc4e17 (patch)
treea7f459091dd2fa6da7f3805a864dbc530f422e24 /src/bin/git_remote_nostr
parentcabbdea4ae39a57cbff10d24084a888ca9948083 (diff)
feat(push): send fast-forward merge status event
when a proposal was merged using fast-forward status rather than by creating a three way merge commit. if there are multiple revisions, the first one that contains merged proposal tip will be referenced. if there are multiple proposals that contain one of the commits, they will all be marked as merged. the nip needs to be updated as there is no single `merge-commit-id` so that tag needs to support multiple values
Diffstat (limited to 'src/bin/git_remote_nostr')
-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 ),