diff options
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 | ), |