diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-12-06 22:16:44 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-12-06 22:16:44 +0000 |
| commit | f0d0e1ba1cba11d3a98a5ab0c7f1dc72b6bc4e17 (patch) | |
| tree | a7f459091dd2fa6da7f3805a864dbc530f422e24 /src/bin/git_remote_nostr | |
| parent | cabbdea4ae39a57cbff10d24084a888ca9948083 (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.rs | 249 |
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`) | ||
| 993 | type MergedProposalsInfo = | ||
| 994 | HashMap<EventId, (Option<EventId>, HashMap<Sha1Hash, MergedPRCommitType>)>; | ||
| 995 | |||
| 996 | async 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 | |||
| 1063 | async 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)] | ||
| 1130 | enum MergedPRCommitType { | ||
| 1131 | MergeCommit, | ||
| 1132 | PatchCommit { event_id: EventId }, | ||
| 1133 | PatchApplied { event_id: EventId }, | ||
| 1134 | } | ||
| 1135 | |||
| 1027 | async fn create_merge_status( | 1136 | async 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 | ), |