upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/git/process.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/git/process.rs')
-rw-r--r--src/git/process.rs255
1 files changed, 255 insertions, 0 deletions
diff --git a/src/git/process.rs b/src/git/process.rs
new file mode 100644
index 0000000..d052c04
--- /dev/null
+++ b/src/git/process.rs
@@ -0,0 +1,255 @@
1//! Event Processing - Unified logic for processing state and PR events with git data
2//!
3//! This module provides the core processing logic used when events have git data available.
4//! These functions are used in multiple scenarios:
5//! - When events arrive with git data already available (policy handlers)
6//! - When events are released from purgatory (purgatory sync)
7//! - When git pushes trigger purgatory releases (receive-pack handler)
8
9use std::path::Path;
10use nostr_sdk::Event;
11use crate::git::authorization::{collect_authorized_maintainers, RepositoryData};
12use crate::git::sync::{align_repository_with_state, sync_pr_refs_to_tagged_owner_repos, copy_missing_oids_between_repos};
13use crate::git;
14use crate::nostr::events::RepositoryState;
15
16/// Result of processing a state event with git data
17#[derive(Debug, Default, Clone)]
18pub struct ProcessStateResult {
19 /// Number of repositories synced (OIDs copied + refs aligned)
20 pub repos_synced: usize,
21 /// Number of refs created across all repos
22 pub refs_created: usize,
23 /// Number of refs updated across all repos
24 pub refs_updated: usize,
25 /// Number of refs deleted across all repos
26 pub refs_deleted: usize,
27 /// Errors encountered (non-fatal)
28 pub errors: Vec<String>,
29}
30
31/// Result of processing a PR event with git data
32#[derive(Debug, Default, Clone)]
33pub struct ProcessPrResult {
34 /// Number of repositories synced
35 pub repos_synced: usize,
36 /// Number of refs created across all repos
37 pub refs_created: usize,
38 /// Errors encountered (non-fatal)
39 pub errors: Vec<String>,
40}
41
42/// Process a single state event that has git data available.
43///
44/// This is the core processing logic used when:
45/// - A state event arrives with git data already available
46/// - A state event is released from purgatory
47///
48/// Does NOT save to database or notify subscribers - caller handles that.
49///
50/// # Processing Steps
51/// 1. Identify owner repos where state author is an authorized maintainer
52/// 2. For each owner repo, check if this state is the latest authorized
53/// 3. Copy missing OIDs from source repo to target repo
54/// 4. Align refs (branches, tags, HEAD) with the state
55///
56/// # Arguments
57/// * `state` - The state event to process
58/// * `source_repo_path` - Path to repo that has the git data
59/// * `db_repo_data` - Repository data from database (announcements + states)
60/// * `git_data_path` - Base path for git repositories
61///
62/// # Returns
63/// ProcessStateResult with statistics
64pub fn process_state_with_git_data(
65 state: &RepositoryState,
66 source_repo_path: &Path,
67 db_repo_data: &RepositoryData,
68 git_data_path: &Path,
69) -> ProcessStateResult {
70 let mut result = ProcessStateResult::default();
71
72 let state_author = state.event.pubkey.to_hex();
73
74 // Collect authorized maintainers per owner
75 let by_owner = collect_authorized_maintainers(&db_repo_data.announcements);
76
77 // Step 1: Identify owner repos that the state event author is maintainer for
78 let authorized_owners: Vec<&String> = by_owner
79 .iter()
80 .filter(|(_, maintainers)| maintainers.contains(&state_author))
81 .map(|(owner, _)| owner)
82 .collect();
83
84 if authorized_owners.is_empty() {
85 tracing::debug!(
86 identifier = %state.identifier,
87 author = %state_author,
88 "State event author not authorized for any owner"
89 );
90 return result;
91 }
92
93 // Process each owner repo that authorizes this state event author
94 for owner in &authorized_owners {
95 let maintainers = by_owner.get(*owner).unwrap();
96
97 // Step 2: Check if this state event is the latest authorized for this owner
98 let is_latest = crate::git::sync::is_latest_authorized_state_public(
99 state,
100 maintainers,
101 &db_repo_data.states,
102 );
103
104 if !is_latest {
105 tracing::debug!(
106 identifier = %state.identifier,
107 owner = %owner,
108 "Skipping owner - newer authorized state exists"
109 );
110 continue;
111 }
112
113 // Find the announcement for this owner
114 let Some(announcement) = db_repo_data
115 .announcements
116 .iter()
117 .find(|a| a.event.pubkey.to_hex() == **owner)
118 else {
119 continue;
120 };
121
122 let target_repo_path = git_data_path.join(announcement.repo_path());
123
124 // Step 3: Check git repo exists for that owner
125 if !target_repo_path.exists() {
126 tracing::debug!(
127 identifier = %state.identifier,
128 owner = %owner,
129 repo_path = %target_repo_path.display(),
130 "Skipping owner - repository doesn't exist"
131 );
132 continue;
133 }
134
135 // Step 4: Copy all required OIDs to that repo (unless it's source_repo_path)
136 if target_repo_path != source_repo_path {
137 if let Err(e) = copy_missing_oids_between_repos(
138 source_repo_path,
139 &target_repo_path,
140 state,
141 ) {
142 tracing::warn!(
143 identifier = %state.identifier,
144 source = %source_repo_path.display(),
145 target = %target_repo_path.display(),
146 error = %e,
147 "Failed to copy OIDs between repos"
148 );
149 result.errors.push(e);
150 continue; // Skip this owner repo
151 }
152 }
153
154 // Step 5: Reset the git state in that repo to match the state event
155 let align_result = align_repository_with_state(&target_repo_path, state);
156 result.repos_synced += 1;
157 result.refs_created += align_result.refs_created;
158 result.refs_updated += align_result.refs_updated;
159 result.refs_deleted += align_result.refs_deleted;
160
161 tracing::info!(
162 identifier = %state.identifier,
163 owner = %owner,
164 repo_path = %target_repo_path.display(),
165 refs_created = align_result.refs_created,
166 refs_updated = align_result.refs_updated,
167 refs_deleted = align_result.refs_deleted,
168 head_set = align_result.head_set,
169 "Aligned repository with state"
170 );
171 }
172
173 result
174}
175
176/// Process a single PR event that has git data available.
177///
178/// This is the core processing logic used when:
179/// - A PR event arrives with git data already available
180/// - A PR event is released from purgatory
181///
182/// Does NOT save to database or notify subscribers - caller handles that.
183///
184/// # Processing Steps
185/// 1. Sync PR commit to owner repos using tagged maintainer logic
186/// 2. Create refs/nostr/<event-id> ref in source repo (if missing)
187/// 3. Create refs/nostr/<event-id> refs in all synced repos
188///
189/// # Arguments
190/// * `event` - The PR event to process
191/// * `commit` - The commit hash from the PR event
192/// * `source_repo_path` - Path to repo that has the commit
193/// * `db_repo_data` - Repository data from database (announcements + states)
194/// * `git_data_path` - Base path for git repositories
195/// * `source_owner_pubkey` - Owner pubkey of source repo (to skip)
196///
197/// # Returns
198/// ProcessPrResult with statistics
199pub fn process_pr_with_git_data(
200 event: &Event,
201 commit: &str,
202 source_repo_path: &Path,
203 db_repo_data: &RepositoryData,
204 git_data_path: &Path,
205 source_owner_pubkey: &str,
206) -> ProcessPrResult {
207 let mut result = ProcessPrResult::default();
208
209 let event_id = event.id.to_hex();
210
211 // Sync PR ref to owner repos using tagged maintainer logic
212 let pr_refs = vec![(event_id.clone(), commit.to_string())];
213 let pr_events = vec![event.clone()];
214
215 let sync_result = sync_pr_refs_to_tagged_owner_repos(
216 source_repo_path,
217 &pr_refs,
218 &pr_events,
219 db_repo_data,
220 git_data_path,
221 source_owner_pubkey,
222 );
223 result.repos_synced += sync_result.repos_synced;
224 result.refs_created += sync_result.refs_created;
225 result.errors.extend(
226 sync_result
227 .errors
228 .into_iter()
229 .map(|(_, e)| e),
230 );
231
232 // Create the ref in the source repo if it doesn't exist
233 let ref_name = format!("refs/nostr/{}", event_id);
234 if git::get_ref_commit(source_repo_path, &ref_name).is_none() {
235 if let Err(e) = git::update_ref(source_repo_path, &ref_name, commit) {
236 tracing::warn!(
237 event_id = %event_id,
238 repo = %source_repo_path.display(),
239 error = %e,
240 "Failed to create PR ref in source repo"
241 );
242 result.errors.push(e);
243 } else {
244 result.refs_created += 1;
245 tracing::info!(
246 event_id = %event_id,
247 commit = %commit,
248 repo = %source_repo_path.display(),
249 "Created PR ref in source repo"
250 );
251 }
252 }
253
254 result
255}