upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/bin/git_remote_nostr/push.rs107
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)]