upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/git/sync.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-07 23:31:38 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-07 23:31:38 +0000
commitc67ebe6f33bfa191f17eb0df24d3ee18092c74e1 (patch)
treeb86911bbb406f4aa0253b1cf1e0a82aed16c972b /src/git/sync.rs
parent4dc0ed66a0bd3b4b00804bb13adf93b207bb5fc4 (diff)
refactor: unify event processing logic
Eliminates code duplication by extracting core event processing into reusable functions. All state and PR event processing now uses the same unified logic from src/git/process.rs. Changes: - Add src/git/process.rs with unified processing functions - process_state_with_git_data() for state events - process_pr_with_git_data() for PR events - Pure functions with comprehensive result types - Refactor policy handlers to use unified processing - src/nostr/policy/state.rs: Remove ~70 lines of duplicated logic - src/nostr/policy/pr_event.rs: Remove ~40 lines of duplicated logic - Refactor purgatory processing to use unified functions - src/git/sync.rs: Remove ~125 lines of duplicated logic - Make extract_owner_from_repo_path() public for reuse Benefits: - DRY: Single source of truth for event processing - Testable: Pure functions with clear contracts - Maintainable: Changes happen in one place - Consistent: All code paths use same logic All 217 unit tests + 40 integration tests pass (257/257).
Diffstat (limited to 'src/git/sync.rs')
-rw-r--r--src/git/sync.rs190
1 files changed, 43 insertions, 147 deletions
diff --git a/src/git/sync.rs b/src/git/sync.rs
index 2f43e6e..5e2d3f2 100644
--- a/src/git/sync.rs
+++ b/src/git/sync.rs
@@ -908,12 +908,9 @@ async fn process_purgatory_state_events(
908 } 908 }
909 }; 909 };
910 910
911 // Collect authorized maintainers per owner (computed once)
912 let by_owner = collect_authorized_maintainers(&db_repo_data.announcements);
913
914 // Process each state event in chronological order 911 // Process each state event in chronological order
915 for entry in &purgatory_states { 912 for entry in &purgatory_states {
916 // Step 0: Check if we have all the git data needed to apply this state event 913 // Check if we have all the git data needed to apply this state event
917 if !can_apply_state(&entry.event, source_repo_path) { 914 if !can_apply_state(&entry.event, source_repo_path) {
918 debug!( 915 debug!(
919 identifier = %identifier, 916 identifier = %identifier,
@@ -940,122 +937,19 @@ async fn process_purgatory_state_events(
940 } 937 }
941 }; 938 };
942 939
943 let state_author = state.event.pubkey.to_hex(); 940 // Use unified processing function
944 941 let process_result = crate::git::process::process_state_with_git_data(
945 // Step 1: Identify owner repos that the state event author is maintainer for 942 &state,
946 let authorized_owners: Vec<&String> = by_owner 943 source_repo_path,
947 .iter() 944 &db_repo_data,
948 .filter(|(_, maintainers)| maintainers.contains(&state_author)) 945 git_data_path,
949 .map(|(owner, _)| owner) 946 );
950 .collect();
951
952 if authorized_owners.is_empty() {
953 debug!(
954 identifier = %identifier,
955 event_id = %entry.event.id,
956 pubkey = %state_author,
957 "State event author not authorized for any owner - skipping"
958 );
959 continue;
960 }
961
962 // Track if we applied to at least one owner repo
963 let mut applied_to_any = false;
964
965 // Process each owner repo that authorizes this state event author
966 for owner in &authorized_owners {
967 let maintainers = by_owner.get(*owner).unwrap();
968
969 // Step 2: Check if this state event is the latest authorized for this owner
970 // Only consider database states, not other purgatory states
971 let is_latest = is_latest_authorized_state(
972 &state,
973 maintainers,
974 &db_repo_data.states,
975 );
976
977 if !is_latest {
978 debug!(
979 identifier = %identifier,
980 event_id = %entry.event.id,
981 owner = %owner,
982 "Skipping owner - a newer authorized state exists"
983 );
984 continue;
985 }
986
987 // Find the announcement for this owner
988 let announcement = db_repo_data
989 .announcements
990 .iter()
991 .find(|a| a.event.pubkey.to_hex() == **owner);
992
993 let Some(announcement) = announcement else {
994 continue;
995 };
996
997 let target_repo_path = git_data_path.join(announcement.repo_path());
998
999 // Step 3: Check git repo exists for that owner
1000 if !target_repo_path.exists() {
1001 debug!(
1002 identifier = %identifier,
1003 owner = %owner,
1004 repo_path = %target_repo_path.display(),
1005 "Skipping owner - repository doesn't exist"
1006 );
1007 continue;
1008 }
1009
1010 // Step 4: Copy all required OIDs to that repo (unless it's source_repo_path)
1011 if target_repo_path != source_repo_path {
1012 if let Err(e) =
1013 copy_missing_oids_between_repos(source_repo_path, &target_repo_path, &state)
1014 {
1015 warn!(
1016 identifier = %identifier,
1017 source = %source_repo_path.display(),
1018 target = %target_repo_path.display(),
1019 error = %e,
1020 "Failed to copy OIDs between repos"
1021 );
1022 result
1023 .errors
1024 .push((target_repo_path.display().to_string(), e).1);
1025 // Continue anyway - we'll try to align what we can
1026 }
1027 }
1028
1029 // Step 5: Reset the git state in that repo to match the state event
1030 // (excluding refs/nostr/*)
1031 let align_result = align_repository_with_state(&target_repo_path, &state);
1032 result.repos_synced += 1;
1033 result.refs_created += align_result.refs_created;
1034 result.refs_updated += align_result.refs_updated;
1035 result.refs_deleted += align_result.refs_deleted;
1036
1037 info!(
1038 identifier = %identifier,
1039 owner = %owner,
1040 event_id = %entry.event.id,
1041 repo_path = %target_repo_path.display(),
1042 refs_created = align_result.refs_created,
1043 refs_updated = align_result.refs_updated,
1044 refs_deleted = align_result.refs_deleted,
1045 head_set = align_result.head_set,
1046 "Aligned repository with state from purgatory"
1047 );
1048
1049 applied_to_any = true;
1050 }
1051 947
1052 // We have the git data now, so we should release from purgatory regardless of 948 result.repos_synced += process_result.repos_synced;
1053 // whether we applied to any repo. The question is: should we save to DB or just remove? 949 result.refs_created += process_result.refs_created;
1054 // 950 result.refs_updated += process_result.refs_updated;
1055 // - If there's a newer state event from the same author already in the DB, just remove 951 result.refs_deleted += process_result.refs_deleted;
1056 // (no point saving an older event that will never be used) 952 result.errors.extend(process_result.errors);
1057 // - Otherwise, save it to the DB (even if we didn't apply to any repo, because in the
1058 // future the currently-authorized state event might be deleted and this one should apply)
1059 953
1060 // Check if there's a newer state from the same author in the database 954 // Check if there's a newer state from the same author in the database
1061 let has_newer_from_same_author = db_repo_data.states.iter().any(|s| { 955 let has_newer_from_same_author = db_repo_data.states.iter().any(|s| {
@@ -1073,17 +967,16 @@ async fn process_purgatory_state_events(
1073 debug!( 967 debug!(
1074 identifier = %identifier, 968 identifier = %identifier,
1075 event_id = %entry.event.id, 969 event_id = %entry.event.id,
1076 author = %state_author,
1077 "Removed older state event from purgatory - newer event from same author exists in DB" 970 "Removed older state event from purgatory - newer event from same author exists in DB"
1078 ); 971 );
1079 } else { 972 } else {
1080 // Save to database (even if we didn't apply to any repo) 973 // Save to database
1081 match database.save_event(&entry.event).await { 974 match database.save_event(&entry.event).await {
1082 Ok(_) => { 975 Ok(_) => {
1083 info!( 976 info!(
1084 identifier = %identifier, 977 identifier = %identifier,
1085 event_id = %entry.event.id, 978 event_id = %entry.event.id,
1086 applied_to_repos = applied_to_any, 979 repos_synced = process_result.repos_synced,
1087 "Saved purgatory state event to database" 980 "Saved purgatory state event to database"
1088 ); 981 );
1089 982
@@ -1169,6 +1062,25 @@ fn is_latest_authorized_state(
1169 } 1062 }
1170} 1063}
1171 1064
1065/// Check if a state event is the latest authorized state for a given maintainer set.
1066///
1067/// Only considers states already in the database, not other purgatory states.
1068///
1069/// # Arguments
1070/// * `state` - The state event to check
1071/// * `maintainers` - The set of authorized maintainers for the owner
1072/// * `db_states` - State events from the database
1073///
1074/// # Returns
1075/// true if this state is the latest (or equal latest) among all authorized states in the DB
1076pub fn is_latest_authorized_state_public(
1077 state: &RepositoryState,
1078 maintainers: &[String],
1079 db_states: &[RepositoryState],
1080) -> bool {
1081 is_latest_authorized_state(state, maintainers, db_states)
1082}
1083
1172/// Process PR events from purgatory that can now be satisfied. 1084/// Process PR events from purgatory that can now be satisfied.
1173async fn process_purgatory_pr_events( 1085async fn process_purgatory_pr_events(
1174 identifier: &str, 1086 identifier: &str,
@@ -1224,39 +1136,23 @@ async fn process_purgatory_pr_events(
1224 continue; 1136 continue;
1225 } 1137 }
1226 1138
1227 // Sync PR ref to owner repos 1139 // Extract owner pubkey
1228 let pr_refs = vec![(event.id.to_hex(), entry.commit.clone())];
1229 let pr_events = vec![event.clone()];
1230
1231 // Get owner pubkey from source repo path
1232 let owner_pubkey = extract_owner_from_repo_path(source_repo_path, git_data_path) 1140 let owner_pubkey = extract_owner_from_repo_path(source_repo_path, git_data_path)
1233 .unwrap_or_default(); 1141 .unwrap_or_default();
1234 1142
1235 let sync_result = sync_pr_refs_to_tagged_owner_repos( 1143 // Use unified processing function
1144 let process_result = crate::git::process::process_pr_with_git_data(
1145 event,
1146 &entry.commit,
1236 source_repo_path, 1147 source_repo_path,
1237 &pr_refs,
1238 &pr_events,
1239 &db_repo_data, 1148 &db_repo_data,
1240 git_data_path, 1149 git_data_path,
1241 &owner_pubkey, 1150 &owner_pubkey,
1242 ); 1151 );
1243 result.repos_synced += sync_result.repos_synced;
1244 result.refs_created += sync_result.refs_created;
1245 1152
1246 // Create the ref in the source repo if it doesn't exist 1153 result.repos_synced += process_result.repos_synced;
1247 let ref_name = format!("refs/nostr/{}", event.id.to_hex()); 1154 result.refs_created += process_result.refs_created;
1248 if git::get_ref_commit(source_repo_path, &ref_name).is_none() { 1155 result.errors.extend(process_result.errors);
1249 if let Err(e) = git::update_ref(source_repo_path, &ref_name, &entry.commit) {
1250 warn!(
1251 identifier = %identifier,
1252 event_id = %event.id,
1253 error = %e,
1254 "Failed to create PR ref in source repo"
1255 );
1256 } else {
1257 result.refs_created += 1;
1258 }
1259 }
1260 1156
1261 // Save event to database 1157 // Save event to database
1262 match database.save_event(event).await { 1158 match database.save_event(event).await {
@@ -1307,7 +1203,7 @@ async fn process_purgatory_pr_events(
1307/// Extract owner pubkey from a repository path. 1203/// Extract owner pubkey from a repository path.
1308/// 1204///
1309/// Given a path like `{git_data_path}/{npub}/{identifier}.git`, extracts the npub. 1205/// Given a path like `{git_data_path}/{npub}/{identifier}.git`, extracts the npub.
1310fn extract_owner_from_repo_path(repo_path: &Path, git_data_path: &Path) -> Option<String> { 1206pub fn extract_owner_from_repo_path(repo_path: &Path, git_data_path: &Path) -> Option<String> {
1311 let relative = repo_path.strip_prefix(git_data_path).ok()?; 1207 let relative = repo_path.strip_prefix(git_data_path).ok()?;
1312 let components: Vec<_> = relative.components().collect(); 1208 let components: Vec<_> = relative.components().collect();
1313 if !components.is_empty() { 1209 if !components.is_empty() {