diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-26 12:33:52 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-26 15:26:17 +0000 |
| commit | f252dd0f1fb7374b5b6d44e77facdc902ee52c43 (patch) | |
| tree | 5891e8c06e0039886241aea169700647f88a95d6 | |
| parent | dc6c20d29ea4456eee1dbc2aa8757fe955d1afc7 (diff) | |
fix: skip grasp servers whose relay did not receive state event
When publishing the nostr state event before a git push, use the relay
results from send_events to skip any grasp server whose internal relay
did not receive the event. Print a clear warning for each skipped server
and emit error lines for all refs if no git servers remain to push to.
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/push.rs | 107 |
2 files changed, 75 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cafa52..ccb6e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 15 | 15 | ||
| 16 | - `ngit sync` using wrong refspec source (`refs/remotes/origin/refs/heads/master` instead of `refs/remotes/origin/master`), causing sync to fail with "src refspec does not match any existing object" | 16 | - `ngit sync` using wrong refspec source (`refs/remotes/origin/refs/heads/master` instead of `refs/remotes/origin/master`), causing sync to fail with "src refspec does not match any existing object" |
| 17 | - State event publish failures silently swallowed during push; summary now shows `"Published to X/N relays (failed: relay1 relay2)"` instead of unconditional success message | 17 | - State event publish failures silently swallowed during push; summary now shows `"Published to X/N relays (failed: relay1 relay2)"` instead of unconditional success message |
| 18 | - Grasp servers whose internal relay did not receive the state event are now skipped during push, with a clear warning; push fails with an error message when no servers remain | ||
| 18 | 19 | ||
| 19 | ## [2.2.1] - 2026-02-25 | 20 | ## [2.2.1] - 2026-02-25 |
| 20 | 21 | ||
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index 91b01b9..2ab01cb 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs | |||
| @@ -23,7 +23,10 @@ use ngit::{ | |||
| 23 | list::list_from_remotes, | 23 | list::list_from_remotes, |
| 24 | login::{existing::load_existing_login, user::UserRef}, | 24 | login::{existing::load_existing_login, user::UserRef}, |
| 25 | push::{push_to_remote, select_servers_push_refs_and_generate_pr_or_pr_update_event}, | 25 | push::{push_to_remote, select_servers_push_refs_and_generate_pr_or_pr_update_event}, |
| 26 | repo_ref::{self, get_repo_config_from_yaml, is_grasp_server_clone_url}, | 26 | repo_ref::{ |
| 27 | self, format_grasp_server_url_as_relay_url, get_repo_config_from_yaml, | ||
| 28 | is_grasp_server_clone_url, | ||
| 29 | }, | ||
| 27 | repo_state, | 30 | repo_state, |
| 28 | utils::{ | 31 | utils::{ |
| 29 | find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, | 32 | find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, |
| @@ -125,18 +128,19 @@ pub async fn run_push( | |||
| 125 | 128 | ||
| 126 | // all refspecs aren't rejected | 129 | // all refspecs aren't rejected |
| 127 | if !(git_state_refspecs.is_empty() && proposal_refspecs.is_empty()) { | 130 | if !(git_state_refspecs.is_empty() && proposal_refspecs.is_empty()) { |
| 128 | let (rejected_proposal_refspecs, rejected) = create_and_publish_events_and_proposals( | 131 | let (rejected_proposal_refspecs, rejected, relay_results) = |
| 129 | git_repo, | 132 | create_and_publish_events_and_proposals( |
| 130 | repo_ref, | 133 | git_repo, |
| 131 | &git_state_refspecs, | 134 | repo_ref, |
| 132 | &proposal_refspecs, | 135 | &git_state_refspecs, |
| 133 | client, // &mut Client | 136 | &proposal_refspecs, |
| 134 | existing_state, | 137 | client, // &mut Client |
| 135 | &term, | 138 | existing_state, |
| 136 | title_description.as_ref(), | 139 | &term, |
| 137 | &git_server_push_options, | 140 | title_description.as_ref(), |
| 138 | ) | 141 | &git_server_push_options, |
| 139 | .await?; | 142 | ) |
| 143 | .await?; | ||
| 140 | 144 | ||
| 141 | if !rejected { | 145 | if !rejected { |
| 142 | for refspec in git_state_refspecs.iter().chain(proposal_refspecs.iter()) { | 146 | for refspec in git_state_refspecs.iter().chain(proposal_refspecs.iter()) { |
| @@ -155,25 +159,60 @@ pub async fn run_push( | |||
| 155 | 159 | ||
| 156 | // TODO make async - check gitlib2 callbacks work async | 160 | // TODO make async - check gitlib2 callbacks work async |
| 157 | 161 | ||
| 158 | for (git_server_url, remote_refspecs) in remote_refspecs { | 162 | // Filter out grasp servers whose relay did not receive the state event |
| 159 | let remote_refspecs = remote_refspecs | 163 | let mut servers_to_push: Vec<(String, Vec<String>)> = vec![]; |
| 164 | for (git_server_url, server_refspecs) in remote_refspecs { | ||
| 165 | let server_refspecs = server_refspecs | ||
| 160 | .iter() | 166 | .iter() |
| 161 | .filter(|refspec| git_state_refspecs.contains(refspec)) | 167 | .filter(|refspec| git_state_refspecs.contains(refspec)) |
| 162 | .cloned() | 168 | .cloned() |
| 163 | .collect::<Vec<String>>(); | 169 | .collect::<Vec<String>>(); |
| 164 | if !refspecs.is_empty() { | 170 | if is_grasp_server_clone_url(&git_server_url) |
| 165 | let push_options_refs: Vec<&str> = | 171 | && !relay_results.is_empty() |
| 166 | git_server_push_options.iter().map(String::as_str).collect(); | 172 | { |
| 167 | let _ = push_to_remote( | 173 | if let Ok(relay_url) = |
| 168 | git_repo, | 174 | format_grasp_server_url_as_relay_url(&git_server_url) |
| 169 | &git_server_url, | 175 | { |
| 170 | &repo_ref.to_nostr_git_url(&None), | 176 | let relay_failed = relay_results |
| 171 | &remote_refspecs, | 177 | .iter() |
| 172 | &term, | 178 | .any(|(url, succeeded)| url == &relay_url && !succeeded); |
| 173 | is_grasp_server_clone_url(&git_server_url), | 179 | if relay_failed { |
| 174 | &push_options_refs, | 180 | let short_name = get_short_git_server_name(&git_server_url); |
| 181 | eprintln!( | ||
| 182 | "WARNING: skipping {short_name} - state event failed to reach its relay" | ||
| 183 | ); | ||
| 184 | continue; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | servers_to_push.push((git_server_url, server_refspecs)); | ||
| 189 | } | ||
| 190 | |||
| 191 | // If all git servers were skipped and there were refspecs to push, | ||
| 192 | // emit error lines for each ref using the git remote helper protocol | ||
| 193 | if servers_to_push.is_empty() && !git_state_refspecs.is_empty() { | ||
| 194 | for refspec in &git_state_refspecs { | ||
| 195 | let (_, to) = refspec_to_from_to(refspec)?; | ||
| 196 | println!( | ||
| 197 | "error {to} state event failed to reach any git server relay" | ||
| 175 | ); | 198 | ); |
| 176 | } | 199 | } |
| 200 | } else { | ||
| 201 | for (git_server_url, server_refspecs) in &servers_to_push { | ||
| 202 | if !server_refspecs.is_empty() { | ||
| 203 | let push_options_refs: Vec<&str> = | ||
| 204 | git_server_push_options.iter().map(String::as_str).collect(); | ||
| 205 | let _ = push_to_remote( | ||
| 206 | git_repo, | ||
| 207 | git_server_url, | ||
| 208 | &repo_ref.to_nostr_git_url(&None), | ||
| 209 | server_refspecs, | ||
| 210 | &term, | ||
| 211 | is_grasp_server_clone_url(git_server_url), | ||
| 212 | &push_options_refs, | ||
| 213 | ); | ||
| 214 | } | ||
| 215 | } | ||
| 177 | } | 216 | } |
| 178 | } | 217 | } |
| 179 | } | 218 | } |
| @@ -194,7 +233,7 @@ async fn create_and_publish_events_and_proposals( | |||
| 194 | term: &Term, | 233 | term: &Term, |
| 195 | title_description: Option<&(String, String)>, | 234 | title_description: Option<&(String, String)>, |
| 196 | git_server_push_options: &[String], | 235 | git_server_push_options: &[String], |
| 197 | ) -> Result<(Vec<String>, bool)> { | 236 | ) -> Result<(Vec<String>, bool, Vec<(String, bool)>)> { |
| 198 | let (signer, mut user_ref, _) = load_existing_login( | 237 | let (signer, mut user_ref, _) = load_existing_login( |
| 199 | &Some(git_repo), | 238 | &Some(git_repo), |
| 200 | &None, | 239 | &None, |
| @@ -217,7 +256,7 @@ async fn create_and_publish_events_and_proposals( | |||
| 217 | ); | 256 | ); |
| 218 | } | 257 | } |
| 219 | if proposal_refspecs.is_empty() { | 258 | if proposal_refspecs.is_empty() { |
| 220 | return Ok((vec![], true)); | 259 | return Ok((vec![], true, vec![])); |
| 221 | } | 260 | } |
| 222 | } else if repo_ref | 261 | } else if repo_ref |
| 223 | .maintainers_without_annoucnement | 262 | .maintainers_without_annoucnement |
| @@ -297,8 +336,8 @@ async fn create_and_publish_events_and_proposals( | |||
| 297 | 336 | ||
| 298 | // TODO check whether tip of each branch pushed is on at least one git server | 337 | // TODO check whether tip of each branch pushed is on at least one git server |
| 299 | // before broadcasting the nostr state | 338 | // before broadcasting the nostr state |
| 300 | if !events.is_empty() { | 339 | let relay_results = if !events.is_empty() { |
| 301 | let _relay_results = send_events( | 340 | send_events( |
| 302 | client, | 341 | client, |
| 303 | Some(git_repo.get_path()?), | 342 | Some(git_repo.get_path()?), |
| 304 | events, | 343 | events, |
| @@ -307,9 +346,11 @@ async fn create_and_publish_events_and_proposals( | |||
| 307 | true, | 346 | true, |
| 308 | false, | 347 | false, |
| 309 | ) | 348 | ) |
| 310 | .await?; | 349 | .await? |
| 311 | } | 350 | } else { |
| 312 | Ok((rejected_proposal_refspecs, false)) | 351 | vec![] |
| 352 | }; | ||
| 353 | Ok((rejected_proposal_refspecs, false, relay_results)) | ||
| 313 | } | 354 | } |
| 314 | 355 | ||
| 315 | #[allow(clippy::too_many_lines)] | 356 | #[allow(clippy::too_many_lines)] |