diff options
Diffstat (limited to 'src/git_remote_helper.rs')
| -rw-r--r-- | src/git_remote_helper.rs | 230 |
1 files changed, 228 insertions, 2 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index 97db69a..a03c6cf 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs | |||
| @@ -18,9 +18,9 @@ use client::{ | |||
| 18 | consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache, | 18 | consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache, |
| 19 | get_state_from_cache, sign_event, Connect, STATE_KIND, | 19 | get_state_from_cache, sign_event, Connect, STATE_KIND, |
| 20 | }; | 20 | }; |
| 21 | use git::RepoActions; | 21 | use git::{sha1_to_oid, RepoActions}; |
| 22 | use git2::{Oid, Repository}; | 22 | use git2::{Oid, Repository}; |
| 23 | use nostr::nips::nip01::Coordinate; | 23 | use nostr::nips::{nip01::Coordinate, nip10::Marker}; |
| 24 | use nostr_sdk::{ | 24 | use nostr_sdk::{ |
| 25 | hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag, Url, | 25 | hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag, Url, |
| 26 | }; | 26 | }; |
| @@ -713,6 +713,18 @@ async fn push( | |||
| 713 | RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?; | 713 | RepoState::build(repo_ref.identifier.clone(), new_state, &signer).await?; |
| 714 | 714 | ||
| 715 | events.push(new_repo_state.event); | 715 | events.push(new_repo_state.event); |
| 716 | |||
| 717 | for event in get_merged_status_events( | ||
| 718 | repo_ref, | ||
| 719 | git_repo, | ||
| 720 | nostr_remote_url, | ||
| 721 | &signer, | ||
| 722 | &git_server_refspecs, | ||
| 723 | ) | ||
| 724 | .await? | ||
| 725 | { | ||
| 726 | events.push(event); | ||
| 727 | } | ||
| 716 | } | 728 | } |
| 717 | 729 | ||
| 718 | let mut rejected_proposal_refspecs = vec![]; | 730 | let mut rejected_proposal_refspecs = vec![]; |
| @@ -1115,6 +1127,220 @@ fn generate_updated_state( | |||
| 1115 | Ok(new_state) | 1127 | Ok(new_state) |
| 1116 | } | 1128 | } |
| 1117 | 1129 | ||
| 1130 | async fn get_merged_status_events( | ||
| 1131 | repo_ref: &RepoRef, | ||
| 1132 | git_repo: &Repo, | ||
| 1133 | remote_nostr_url: &str, | ||
| 1134 | signer: &NostrSigner, | ||
| 1135 | refspecs_to_git_server: &Vec<String>, | ||
| 1136 | ) -> Result<Vec<Event>> { | ||
| 1137 | let mut events = vec![]; | ||
| 1138 | for refspec in refspecs_to_git_server { | ||
| 1139 | let (from, to) = refspec_to_from_to(refspec)?; | ||
| 1140 | if to.eq("refs/heads/main") || to.eq("refs/heads/master") { | ||
| 1141 | let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; | ||
| 1142 | let Ok(tip_of_remote_branch) = git_repo.get_commit_or_tip_of_reference( | ||
| 1143 | &refspec_remote_ref_name(&git_repo.git_repo, refspec, remote_nostr_url)?, | ||
| 1144 | ) else { | ||
| 1145 | // branch not on remote | ||
| 1146 | continue; | ||
| 1147 | }; | ||
| 1148 | let (ahead, _) = | ||
| 1149 | git_repo.get_commits_ahead_behind(&tip_of_remote_branch, &tip_of_pushed_branch)?; | ||
| 1150 | for commit_hash in ahead { | ||
| 1151 | let commit = git_repo.git_repo.find_commit(sha1_to_oid(&commit_hash)?)?; | ||
| 1152 | if commit.parent_count() > 1 { | ||
| 1153 | // merge commit | ||
| 1154 | for parent in commit.parents() { | ||
| 1155 | // lookup parent id | ||
| 1156 | let commit_events = get_events_from_cache( | ||
| 1157 | git_repo.get_path()?, | ||
| 1158 | vec![ | ||
| 1159 | nostr::Filter::default() | ||
| 1160 | .kind(nostr::Kind::GitPatch) | ||
| 1161 | .reference(parent.id().to_string()), | ||
| 1162 | ], | ||
| 1163 | ) | ||
| 1164 | .await?; | ||
| 1165 | if let Some(commit_event) = commit_events.iter().find(|e| { | ||
| 1166 | e.tags.iter().any(|t| { | ||
| 1167 | t.as_vec()[0].eq("commit") | ||
| 1168 | && t.as_vec()[1].eq(&parent.id().to_string()) | ||
| 1169 | }) | ||
| 1170 | }) { | ||
| 1171 | let (proposal_id, revision_id) = | ||
| 1172 | get_proposal_and_revision_root_from_patch(git_repo, commit_event) | ||
| 1173 | .await?; | ||
| 1174 | // TODO: write to terminal to tell user | ||
| 1175 | events.push( | ||
| 1176 | create_merge_status( | ||
| 1177 | signer, | ||
| 1178 | repo_ref, | ||
| 1179 | &get_event_from_cache_by_id(git_repo, &proposal_id).await?, | ||
| 1180 | &if let Some(revision_id) = revision_id { | ||
| 1181 | Some( | ||
| 1182 | get_event_from_cache_by_id(git_repo, &revision_id) | ||
| 1183 | .await?, | ||
| 1184 | ) | ||
| 1185 | } else { | ||
| 1186 | None | ||
| 1187 | }, | ||
| 1188 | &commit_hash, | ||
| 1189 | commit_event.id(), | ||
| 1190 | ) | ||
| 1191 | .await?, | ||
| 1192 | ); | ||
| 1193 | } | ||
| 1194 | } | ||
| 1195 | } | ||
| 1196 | } | ||
| 1197 | } | ||
| 1198 | } | ||
| 1199 | Ok(events) | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | async fn get_event_from_cache_by_id(git_repo: &Repo, event_id: &EventId) -> Result<Event> { | ||
| 1203 | Ok(get_events_from_cache( | ||
| 1204 | git_repo.get_path()?, | ||
| 1205 | vec![nostr::Filter::default().id(*event_id)], | ||
| 1206 | ) | ||
| 1207 | .await? | ||
| 1208 | .first() | ||
| 1209 | .context("cannot find event in cache")? | ||
| 1210 | .clone()) | ||
| 1211 | } | ||
| 1212 | |||
| 1213 | async fn create_merge_status( | ||
| 1214 | signer: &NostrSigner, | ||
| 1215 | repo_ref: &RepoRef, | ||
| 1216 | proposal: &Event, | ||
| 1217 | revision: &Option<Event>, | ||
| 1218 | merge_commit: &Sha1Hash, | ||
| 1219 | merged_patch: EventId, | ||
| 1220 | ) -> Result<Event> { | ||
| 1221 | let mut public_keys = repo_ref | ||
| 1222 | .maintainers | ||
| 1223 | .iter() | ||
| 1224 | .copied() | ||
| 1225 | .collect::<HashSet<PublicKey>>(); | ||
| 1226 | public_keys.insert(proposal.author()); | ||
| 1227 | if let Some(revision) = revision { | ||
| 1228 | public_keys.insert(revision.author()); | ||
| 1229 | } | ||
| 1230 | sign_event( | ||
| 1231 | EventBuilder::new( | ||
| 1232 | nostr::event::Kind::GitStatusApplied, | ||
| 1233 | String::new(), | ||
| 1234 | [ | ||
| 1235 | vec![ | ||
| 1236 | Tag::custom( | ||
| 1237 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("alt")), | ||
| 1238 | vec!["git proposal merged / applied".to_string()], | ||
| 1239 | ), | ||
| 1240 | Tag::from_standardized(nostr::TagStandard::Event { | ||
| 1241 | event_id: proposal.id(), | ||
| 1242 | relay_url: repo_ref.relays.first().map(nostr::UncheckedUrl::new), | ||
| 1243 | marker: Some(Marker::Root), | ||
| 1244 | public_key: None, | ||
| 1245 | }), | ||
| 1246 | Tag::from_standardized(nostr::TagStandard::Event { | ||
| 1247 | event_id: merged_patch, | ||
| 1248 | relay_url: repo_ref.relays.first().map(nostr::UncheckedUrl::new), | ||
| 1249 | marker: Some(Marker::Mention), | ||
| 1250 | public_key: None, | ||
| 1251 | }), | ||
| 1252 | ], | ||
| 1253 | if let Some(revision) = revision { | ||
| 1254 | vec![Tag::from_standardized(nostr::TagStandard::Event { | ||
| 1255 | event_id: revision.id(), | ||
| 1256 | relay_url: repo_ref.relays.first().map(nostr::UncheckedUrl::new), | ||
| 1257 | marker: Some(Marker::Root), | ||
| 1258 | public_key: None, | ||
| 1259 | })] | ||
| 1260 | } else { | ||
| 1261 | vec![] | ||
| 1262 | }, | ||
| 1263 | public_keys.iter().map(|pk| Tag::public_key(*pk)).collect(), | ||
| 1264 | repo_ref | ||
| 1265 | .coordinates() | ||
| 1266 | .iter() | ||
| 1267 | .map(|c| Tag::coordinate(c.clone())) | ||
| 1268 | .collect::<Vec<Tag>>(), | ||
| 1269 | vec![ | ||
| 1270 | Tag::from_standardized(nostr::TagStandard::Reference( | ||
| 1271 | repo_ref.root_commit.to_string(), | ||
| 1272 | )), | ||
| 1273 | Tag::from_standardized(nostr::TagStandard::Reference(format!( | ||
| 1274 | "{merge_commit}" | ||
| 1275 | ))), | ||
| 1276 | Tag::custom( | ||
| 1277 | nostr::TagKind::Custom(std::borrow::Cow::Borrowed("merge-commit-id")), | ||
| 1278 | vec![format!("{merge_commit}")], | ||
| 1279 | ), | ||
| 1280 | ], | ||
| 1281 | ] | ||
| 1282 | .concat(), | ||
| 1283 | ), | ||
| 1284 | signer, | ||
| 1285 | ) | ||
| 1286 | .await | ||
| 1287 | } | ||
| 1288 | |||
| 1289 | async fn get_proposal_and_revision_root_from_patch( | ||
| 1290 | git_repo: &Repo, | ||
| 1291 | patch: &Event, | ||
| 1292 | ) -> Result<(EventId, Option<EventId>)> { | ||
| 1293 | let proposal_or_revision = if patch.tags.iter().any(|t| t.as_vec()[1].eq("root")) { | ||
| 1294 | patch.clone() | ||
| 1295 | } else { | ||
| 1296 | let proposal_or_revision_id = EventId::parse( | ||
| 1297 | if let Some(t) = patch.tags.iter().find(|t| t.is_root()) { | ||
| 1298 | t.clone() | ||
| 1299 | } else if let Some(t) = patch.tags.iter().find(|t| t.is_reply()) { | ||
| 1300 | t.clone() | ||
| 1301 | } else { | ||
| 1302 | Tag::event(patch.id()) | ||
| 1303 | } | ||
| 1304 | .as_vec()[1] | ||
| 1305 | .clone(), | ||
| 1306 | )?; | ||
| 1307 | |||
| 1308 | get_events_from_cache( | ||
| 1309 | git_repo.get_path()?, | ||
| 1310 | vec![nostr::Filter::default().id(proposal_or_revision_id)], | ||
| 1311 | ) | ||
| 1312 | .await? | ||
| 1313 | .first() | ||
| 1314 | .unwrap() | ||
| 1315 | .clone() | ||
| 1316 | }; | ||
| 1317 | |||
| 1318 | if !proposal_or_revision.kind().eq(&Kind::GitPatch) { | ||
| 1319 | bail!("thread root is not a git patch"); | ||
| 1320 | } | ||
| 1321 | |||
| 1322 | if proposal_or_revision | ||
| 1323 | .tags | ||
| 1324 | .iter() | ||
| 1325 | .any(|t| t.as_vec()[1].eq("revision-root")) | ||
| 1326 | { | ||
| 1327 | Ok(( | ||
| 1328 | EventId::parse( | ||
| 1329 | proposal_or_revision | ||
| 1330 | .tags | ||
| 1331 | .iter() | ||
| 1332 | .find(|t| t.is_reply()) | ||
| 1333 | .unwrap() | ||
| 1334 | .as_vec()[1] | ||
| 1335 | .clone(), | ||
| 1336 | )?, | ||
| 1337 | Some(proposal_or_revision.id()), | ||
| 1338 | )) | ||
| 1339 | } else { | ||
| 1340 | Ok((proposal_or_revision.id(), None)) | ||
| 1341 | } | ||
| 1342 | } | ||
| 1343 | |||
| 1118 | fn update_remote_refs_pushed( | 1344 | fn update_remote_refs_pushed( |
| 1119 | git_repo: &Repository, | 1345 | git_repo: &Repository, |
| 1120 | refspec: &str, | 1346 | refspec: &str, |