upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/git_remote_helper.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-08-18 08:04:49 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-08-18 08:04:49 +0100
commit948c8595acea9a783a38002371c40185868ce923 (patch)
tree10dce3b3dc487e5d27aee706d64738bd72f1e2ce /src/git_remote_helper.rs
parent5618fd9883d45de1443a40abada944cbe3bb8dfd (diff)
feat(remote): `push` publish merge event
when a merge commit is being pushed that merges a patch in a proposal
Diffstat (limited to 'src/git_remote_helper.rs')
-rw-r--r--src/git_remote_helper.rs230
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};
21use git::RepoActions; 21use git::{sha1_to_oid, RepoActions};
22use git2::{Oid, Repository}; 22use git2::{Oid, Repository};
23use nostr::nips::nip01::Coordinate; 23use nostr::nips::{nip01::Coordinate, nip10::Marker};
24use nostr_sdk::{ 24use 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
1130async 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
1202async 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
1213async 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
1289async 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
1118fn update_remote_refs_pushed( 1344fn update_remote_refs_pushed(
1119 git_repo: &Repository, 1345 git_repo: &Repository,
1120 refspec: &str, 1346 refspec: &str,