upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-12-13 15:11:30 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-12-13 15:11:30 +0000
commit411b381ebd5a01bdfe678d9014a160768b9bd730 (patch)
tree718d65cfcdd63602c60538d0a840f48491c5e8ca
parentee0e048ed9c244449ffb12284e444933eb67e118 (diff)
refactor(push): split `run_push` function
as it was way too long
-rw-r--r--src/bin/git_remote_nostr/push.rs405
1 files changed, 222 insertions, 183 deletions
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs
index 98e93ca..9d4e9e7 100644
--- a/src/bin/git_remote_nostr/push.rs
+++ b/src/bin/git_remote_nostr/push.rs
@@ -26,7 +26,7 @@ use ngit::{
26 oid_to_shorthand_string, 26 oid_to_shorthand_string,
27 }, 27 },
28 git_events::{self, event_to_cover_letter, 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, user::UserRef},
30 repo_ref::{self, get_repo_config_from_yaml}, 30 repo_ref::{self, get_repo_config_from_yaml},
31 repo_state, 31 repo_state,
32}; 32};
@@ -49,7 +49,6 @@ use crate::{
49 }, 49 },
50}; 50};
51 51
52#[allow(clippy::too_many_lines)]
53pub async fn run_push( 52pub async fn run_push(
54 git_repo: &Repo, 53 git_repo: &Repo,
55 repo_ref: &RepoRef, 54 repo_ref: &RepoRef,
@@ -118,34 +117,91 @@ pub async fn run_push(
118 } 117 }
119 }); 118 });
120 119
121 let mut events = vec![];
122
123 if git_server_refspecs.is_empty() && proposal_refspecs.is_empty() { 120 if git_server_refspecs.is_empty() && proposal_refspecs.is_empty() {
124 // all refspecs rejected 121 // all refspecs rejected
125 println!(); 122 println!();
126 return Ok(()); 123 return Ok(());
127 } 124 }
128 125
126 let (rejected_proposal_refspecs, rejected) = create_and_publish_events(
127 git_repo,
128 repo_ref,
129 &git_server_refspecs,
130 &proposal_refspecs,
131 client,
132 existing_state,
133 &term,
134 )
135 .await?;
136
137 if !rejected {
138 for refspec in &[git_server_refspecs.clone(), proposal_refspecs.clone()].concat() {
139 if rejected_proposal_refspecs.contains(refspec) {
140 continue;
141 }
142 let (_, to) = refspec_to_from_to(refspec)?;
143 println!("ok {to}");
144 update_remote_refs_pushed(
145 &git_repo.git_repo,
146 refspec,
147 &decoded_nostr_url.original_string,
148 )
149 .context("could not update remote_ref locally")?;
150 }
151
152 // TODO make async - check gitlib2 callbacks work async
153
154 for (git_server_url, remote_refspecs) in remote_refspecs {
155 let remote_refspecs = remote_refspecs
156 .iter()
157 .filter(|refspec| git_server_refspecs.contains(refspec))
158 .cloned()
159 .collect::<Vec<String>>();
160 if !refspecs.is_empty() {
161 let _ = push_to_remote(
162 git_repo,
163 &git_server_url,
164 decoded_nostr_url,
165 &remote_refspecs,
166 &term,
167 );
168 }
169 }
170 }
171
172 println!();
173 Ok(())
174}
175
176async fn create_and_publish_events(
177 git_repo: &Repo,
178 repo_ref: &RepoRef,
179 git_server_refspecs: &Vec<String>,
180 proposal_refspecs: &Vec<String>,
181 client: &Client,
182 existing_state: HashMap<String, String>,
183 term: &Term,
184) -> Result<(Vec<String>, bool)> {
129 let (signer, user_ref, _) = 185 let (signer, user_ref, _) =
130 login::login_or_signup(&Some(git_repo), &None, &None, Some(client), true).await?; 186 login::login_or_signup(&Some(git_repo), &None, &None, Some(client), true).await?;
131 187
132 if !repo_ref.maintainers.contains(&user_ref.public_key) { 188 if !repo_ref.maintainers.contains(&user_ref.public_key) {
133 for refspec in &git_server_refspecs { 189 for refspec in git_server_refspecs {
134 let (_, to) = refspec_to_from_to(refspec).unwrap(); 190 let (_, to) = refspec_to_from_to(refspec).unwrap();
135 println!( 191 eprintln!(
136 "error {to} your nostr account {} isn't listed as a maintainer of the repo", 192 "error {to} your nostr account {} isn't listed as a maintainer of the repo",
137 user_ref.metadata.name 193 user_ref.metadata.name
138 ); 194 );
139 } 195 }
140 git_server_refspecs.clear();
141 if proposal_refspecs.is_empty() { 196 if proposal_refspecs.is_empty() {
142 println!(); 197 return Ok((vec![], true));
143 return Ok(());
144 } 198 }
145 } 199 }
146 200
201 let mut events = vec![];
202
147 if !git_server_refspecs.is_empty() { 203 if !git_server_refspecs.is_empty() {
148 let new_state = generate_updated_state(git_repo, &existing_state, &git_server_refspecs)?; 204 let new_state = generate_updated_state(git_repo, &existing_state, git_server_refspecs)?;
149 205
150 let store_state = 206 let store_state =
151 if let Ok(Some(nostate)) = git_repo.get_git_config_item("nostr.nostate", None) { 207 if let Ok(Some(nostate)) = git_repo.get_git_config_item("nostr.nostate", None) {
@@ -160,152 +216,31 @@ pub async fn run_push(
160 events.push(new_repo_state.event); 216 events.push(new_repo_state.event);
161 } 217 }
162 218
163 for event in get_merged_status_events( 219 for event in
164 &term, 220 get_merged_status_events(term, repo_ref, git_repo, &signer, git_server_refspecs).await?
165 repo_ref,
166 git_repo,
167 &decoded_nostr_url.original_string,
168 &signer,
169 &git_server_refspecs,
170 )
171 .await?
172 { 221 {
173 events.push(event); 222 events.push(event);
174 } 223 }
175 224
176 if let Ok(Some(repo_ref_event)) = get_maintainers_yaml_update( 225 if let Ok(Some(repo_ref_event)) =
177 &term, 226 get_maintainers_yaml_update(term, repo_ref, git_repo, &signer, git_server_refspecs)
178 repo_ref, 227 .await
179 git_repo,
180 &decoded_nostr_url.original_string,
181 &signer,
182 &git_server_refspecs,
183 )
184 .await
185 { 228 {
186 events.push(repo_ref_event); 229 events.push(repo_ref_event);
187 } 230 }
188 } 231 }
189 232
190 let mut rejected_proposal_refspecs = vec![]; 233 let (proposal_events, rejected_proposal_refspecs) = process_proposal_refspecs(
191 if !proposal_refspecs.is_empty() { 234 git_repo,
192 let all_proposals = get_all_proposals(git_repo, repo_ref).await?; 235 repo_ref,
193 let current_user = get_curent_user(git_repo)?; 236 proposal_refspecs,
194 237 &user_ref,
195 for refspec in &proposal_refspecs { 238 &signer,
196 let (from, to) = refspec_to_from_to(refspec).unwrap(); 239 term,
197 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; 240 )
198 241 .await?;
199 if let Some((_, (proposal, patches))) = 242 for e in proposal_events {
200 find_proposal_and_patches_by_branch_name(to, &all_proposals, &current_user) 243 events.push(e);
201 {
202 if [repo_ref.maintainers.clone(), vec![proposal.pubkey]]
203 .concat()
204 .contains(&user_ref.public_key)
205 {
206 if refspec.starts_with('+') {
207 // force push
208 let (_, main_tip) = git_repo.get_main_or_master_branch()?;
209 let (mut ahead, _) =
210 git_repo.get_commits_ahead_behind(&main_tip, &tip_of_pushed_branch)?;
211 ahead.reverse();
212 for patch in generate_cover_letter_and_patch_events(
213 None,
214 git_repo,
215 &ahead,
216 &signer,
217 repo_ref,
218 &Some(proposal.id.to_string()),
219 &[],
220 )
221 .await?
222 {
223 events.push(patch);
224 }
225 } else {
226 // fast forward push
227 let tip_patch = patches.first().unwrap();
228 let tip_of_proposal = get_commit_id_from_patch(tip_patch)?;
229 let tip_of_proposal_commit =
230 git_repo.get_commit_or_tip_of_reference(&tip_of_proposal)?;
231
232 let (mut ahead, behind) = git_repo.get_commits_ahead_behind(
233 &tip_of_proposal_commit,
234 &tip_of_pushed_branch,
235 )?;
236 if behind.is_empty() {
237 let thread_id = if let Ok(root_event_id) = get_event_root(tip_patch) {
238 root_event_id
239 } else {
240 // tip patch is the root proposal
241 tip_patch.id
242 };
243 let mut parent_patch = tip_patch.clone();
244 ahead.reverse();
245 for (i, commit) in ahead.iter().enumerate() {
246 let new_patch = generate_patch_event(
247 git_repo,
248 &git_repo.get_root_commit()?,
249 commit,
250 Some(thread_id),
251 &signer,
252 repo_ref,
253 Some(parent_patch.id),
254 Some((
255 (patches.len() + i + 1).try_into().unwrap(),
256 (patches.len() + ahead.len()).try_into().unwrap(),
257 )),
258 None,
259 &None,
260 &[],
261 )
262 .await
263 .context("failed to make patch event from commit")?;
264 events.push(new_patch.clone());
265 parent_patch = new_patch;
266 }
267 } else {
268 // we shouldn't get here
269 term.write_line(
270 format!(
271 "WARNING: failed to push {from} as nostr proposal. Try and force push ",
272 )
273 .as_str(),
274 )
275 .unwrap();
276 println!(
277 "error {to} failed to fastforward as newer patches found on proposal"
278 );
279 rejected_proposal_refspecs.push(refspec.to_string());
280 }
281 }
282 } else {
283 println!(
284 "error {to} permission denied. you are not the proposal author or a repo maintainer"
285 );
286 rejected_proposal_refspecs.push(refspec.to_string());
287 }
288 } else {
289 // TODO new proposal / couldn't find exisiting proposal
290 let (_, main_tip) = git_repo.get_main_or_master_branch()?;
291 let (mut ahead, _) =
292 git_repo.get_commits_ahead_behind(&main_tip, &tip_of_pushed_branch)?;
293 ahead.reverse();
294 for patch in generate_cover_letter_and_patch_events(
295 None,
296 git_repo,
297 &ahead,
298 &signer,
299 repo_ref,
300 &None,
301 &[],
302 )
303 .await?
304 {
305 events.push(patch);
306 }
307 }
308 }
309 } 244 }
310 245
311 // TODO check whether tip of each branch pushed is on at least one git server 246 // TODO check whether tip of each branch pushed is on at least one git server
@@ -323,41 +258,140 @@ pub async fn run_push(
323 ) 258 )
324 .await?; 259 .await?;
325 } 260 }
261 Ok((rejected_proposal_refspecs, false))
262}
326 263
327 for refspec in &[git_server_refspecs.clone(), proposal_refspecs.clone()].concat() { 264#[allow(clippy::too_many_lines)]
328 if rejected_proposal_refspecs.contains(refspec) { 265async fn process_proposal_refspecs(
329 continue; 266 git_repo: &Repo,
330 } 267 repo_ref: &RepoRef,
331 let (_, to) = refspec_to_from_to(refspec)?; 268 proposal_refspecs: &Vec<String>,
332 println!("ok {to}"); 269 user_ref: &UserRef,
333 update_remote_refs_pushed( 270 signer: &Arc<dyn NostrSigner>,
334 &git_repo.git_repo, 271 term: &Term,
335 refspec, 272) -> Result<(Vec<Event>, Vec<String>)> {
336 &decoded_nostr_url.original_string, 273 let mut events = vec![];
337 ) 274 let mut rejected_proposal_refspecs = vec![];
338 .context("could not update remote_ref locally")?; 275 if proposal_refspecs.is_empty() {
276 return Ok((events, rejected_proposal_refspecs));
339 } 277 }
278 let all_proposals = get_all_proposals(git_repo, repo_ref).await?;
279 let current_user = get_curent_user(git_repo)?;
340 280
341 // TODO make async - check gitlib2 callbacks work async 281 for refspec in proposal_refspecs {
282 let (from, to) = refspec_to_from_to(refspec).unwrap();
283 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?;
342 284
343 for (git_server_url, remote_refspecs) in remote_refspecs { 285 if let Some((_, (proposal, patches))) =
344 let remote_refspecs = remote_refspecs 286 find_proposal_and_patches_by_branch_name(to, &all_proposals, &current_user)
345 .iter() 287 {
346 .filter(|refspec| git_server_refspecs.contains(refspec)) 288 if [repo_ref.maintainers.clone(), vec![proposal.pubkey]]
347 .cloned() 289 .concat()
348 .collect::<Vec<String>>(); 290 .contains(&user_ref.public_key)
349 if !refspecs.is_empty() { 291 {
350 let _ = push_to_remote( 292 if refspec.starts_with('+') {
293 // force push
294 let (_, main_tip) = git_repo.get_main_or_master_branch()?;
295 let (mut ahead, _) =
296 git_repo.get_commits_ahead_behind(&main_tip, &tip_of_pushed_branch)?;
297 ahead.reverse();
298 for patch in generate_cover_letter_and_patch_events(
299 None,
300 git_repo,
301 &ahead,
302 signer,
303 repo_ref,
304 &Some(proposal.id.to_string()),
305 &[],
306 )
307 .await?
308 {
309 events.push(patch);
310 }
311 } else {
312 // fast forward push
313 let tip_patch = patches.first().unwrap();
314 let tip_of_proposal = get_commit_id_from_patch(tip_patch)?;
315 let tip_of_proposal_commit =
316 git_repo.get_commit_or_tip_of_reference(&tip_of_proposal)?;
317
318 let (mut ahead, behind) = git_repo
319 .get_commits_ahead_behind(&tip_of_proposal_commit, &tip_of_pushed_branch)?;
320 if behind.is_empty() {
321 let thread_id = if let Ok(root_event_id) = get_event_root(tip_patch) {
322 root_event_id
323 } else {
324 // tip patch is the root proposal
325 tip_patch.id
326 };
327 let mut parent_patch = tip_patch.clone();
328 ahead.reverse();
329 for (i, commit) in ahead.iter().enumerate() {
330 let new_patch = generate_patch_event(
331 git_repo,
332 &git_repo.get_root_commit()?,
333 commit,
334 Some(thread_id),
335 signer,
336 repo_ref,
337 Some(parent_patch.id),
338 Some((
339 (patches.len() + i + 1).try_into().unwrap(),
340 (patches.len() + ahead.len()).try_into().unwrap(),
341 )),
342 None,
343 &None,
344 &[],
345 )
346 .await
347 .context("failed to make patch event from commit")?;
348 events.push(new_patch.clone());
349 parent_patch = new_patch;
350 }
351 } else {
352 // we shouldn't get here
353 term.write_line(
354 format!(
355 "WARNING: failed to push {from} as nostr proposal. Try and force push ",
356 )
357 .as_str(),
358 )
359 .unwrap();
360 println!(
361 "error {to} failed to fastforward as newer patches found on proposal"
362 );
363 rejected_proposal_refspecs.push(refspec.to_string());
364 }
365 }
366 } else {
367 println!(
368 "error {to} permission denied. you are not the proposal author or a repo maintainer"
369 );
370 rejected_proposal_refspecs.push(refspec.to_string());
371 }
372 } else {
373 // TODO new proposal / couldn't find exisiting proposal
374 let (_, main_tip) = git_repo.get_main_or_master_branch()?;
375 let (mut ahead, _) =
376 git_repo.get_commits_ahead_behind(&main_tip, &tip_of_pushed_branch)?;
377 ahead.reverse();
378 for patch in generate_cover_letter_and_patch_events(
379 None,
351 git_repo, 380 git_repo,
352 &git_server_url, 381 &ahead,
353 decoded_nostr_url, 382 signer,
354 &remote_refspecs, 383 repo_ref,
355 &term, 384 &None,
356 ); 385 &[],
386 )
387 .await?
388 {
389 events.push(patch);
390 }
357 } 391 }
358 } 392 }
359 println!(); 393
360 Ok(()) 394 Ok((events, rejected_proposal_refspecs))
361} 395}
362 396
363fn push_to_remote( 397fn push_to_remote(
@@ -882,7 +916,6 @@ async fn get_maintainers_yaml_update(
882 term: &console::Term, 916 term: &console::Term,
883 repo_ref: &RepoRef, 917 repo_ref: &RepoRef,
884 git_repo: &Repo, 918 git_repo: &Repo,
885 remote_nostr_url: &str,
886 signer: &Arc<dyn NostrSigner>, 919 signer: &Arc<dyn NostrSigner>,
887 refspecs_to_git_server: &Vec<String>, 920 refspecs_to_git_server: &Vec<String>,
888) -> Result<Option<Event>> { 921) -> Result<Option<Event>> {
@@ -890,9 +923,12 @@ async fn get_maintainers_yaml_update(
890 let (from, to) = refspec_to_from_to(refspec)?; 923 let (from, to) = refspec_to_from_to(refspec)?;
891 if to.eq("refs/heads/main") || to.eq("refs/heads/master") { 924 if to.eq("refs/heads/main") || to.eq("refs/heads/master") {
892 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; 925 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?;
893 let tip_of_remote_branch = git_repo.get_commit_or_tip_of_reference( 926 let tip_of_remote_branch =
894 &refspec_remote_ref_name(&git_repo.git_repo, refspec, remote_nostr_url)?, 927 git_repo.get_commit_or_tip_of_reference(&refspec_remote_ref_name(
895 )?; 928 &git_repo.git_repo,
929 refspec,
930 &repo_ref.to_nostr_git_url(&Some(git_repo)),
931 )?)?;
896 let diff = git_repo.git_repo.diff_tree_to_tree( 932 let diff = git_repo.git_repo.diff_tree_to_tree(
897 Some( 933 Some(
898 &git_repo 934 &git_repo
@@ -948,7 +984,6 @@ async fn get_merged_status_events(
948 term: &console::Term, 984 term: &console::Term,
949 repo_ref: &RepoRef, 985 repo_ref: &RepoRef,
950 git_repo: &Repo, 986 git_repo: &Repo,
951 remote_nostr_url: &str,
952 signer: &Arc<dyn NostrSigner>, 987 signer: &Arc<dyn NostrSigner>,
953 refspecs_to_git_server: &Vec<String>, 988 refspecs_to_git_server: &Vec<String>,
954) -> Result<Vec<Event>> { 989) -> Result<Vec<Event>> {
@@ -957,9 +992,13 @@ async fn get_merged_status_events(
957 let (from, to) = refspec_to_from_to(refspec)?; 992 let (from, to) = refspec_to_from_to(refspec)?;
958 if to.eq("refs/heads/main") || to.eq("refs/heads/master") { 993 if to.eq("refs/heads/main") || to.eq("refs/heads/master") {
959 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; 994 let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?;
960 let Ok(tip_of_remote_branch) = git_repo.get_commit_or_tip_of_reference( 995 let Ok(tip_of_remote_branch) =
961 &refspec_remote_ref_name(&git_repo.git_repo, refspec, remote_nostr_url)?, 996 git_repo.get_commit_or_tip_of_reference(&refspec_remote_ref_name(
962 ) else { 997 &git_repo.git_repo,
998 refspec,
999 &repo_ref.to_nostr_git_url(&Some(git_repo)),
1000 )?)
1001 else {
963 // branch not on remote 1002 // branch not on remote
964 continue; 1003 continue;
965 }; 1004 };