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>2026-02-03 16:13:59 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-03 17:05:16 +0000
commitc163d717147b92b16d89da2fbccef775647b5a07 (patch)
tree8e512d1d64a3665c2b90954fac9f3d707d7e99d3
parent874a8abe1d076cfafd9baf919ec23d7d58200698 (diff)
fix: accept no-op pushes where old_oid == new_oid
Fixes race condition where user's push becomes no-op after state event is applied between fetch and push. Now accepts these as successful no-ops, matching Git's 'Everything up-to-date' behavior. - Add early detection in get_state_authorization_for_specific_owner_repo - Return success for all-noop pushes without requiring purgatory event - Document behavior in inline-authorization.md
-rw-r--r--docs/explanation/inline-authorization.md4
-rw-r--r--src/git/authorization.rs22
2 files changed, 26 insertions, 0 deletions
diff --git a/docs/explanation/inline-authorization.md b/docs/explanation/inline-authorization.md
index 7081f63..80bd98f 100644
--- a/docs/explanation/inline-authorization.md
+++ b/docs/explanation/inline-authorization.md
@@ -352,6 +352,10 @@ pub async fn authorize_push(
352 - If no event found, create placeholder (git-data-first scenario) 352 - If no event found, create placeholder (git-data-first scenario)
353 - Collect PR events from purgatory for post-push processing 353 - Collect PR events from purgatory for post-push processing
354 354
355**No-Op Push Acceptance:** Pushes where all refs have `old_oid == new_oid` are accepted without requiring a purgatory state event, matching Git's "Everything up-to-date" behavior and avoiding race condition rejections.
356
357---
358
355## State Event Authorization 359## State Event Authorization
356 360
357State events (kind 30618) undergo authorization checks at three points (defense-in-depth): 361State events (kind 30618) undergo authorization checks at three points (defense-in-depth):
diff --git a/src/git/authorization.rs b/src/git/authorization.rs
index e174b51..db2b992 100644
--- a/src/git/authorization.rs
+++ b/src/git/authorization.rs
@@ -575,6 +575,28 @@ pub async fn get_state_authorization_for_specific_owner_repo(
575 owner_pubkey 575 owner_pubkey
576 ); 576 );
577 577
578 // Accept pushes where all refs are already at the desired state (old_oid == new_oid)
579 // This handles race conditions where state events are applied between fetch and push
580 if !pushed_refs.is_empty() {
581 let all_refs_unchanged = pushed_refs
582 .iter()
583 .all(|(old_oid, new_oid, _)| old_oid == new_oid);
584
585 if all_refs_unchanged {
586 debug!(
587 "All pushed refs unchanged (old_oid == new_oid) for {} owned by {}, accepting without purgatory check",
588 identifier, owner_pubkey
589 );
590 return Ok(AuthorizationResult {
591 authorized: true,
592 reason: "Push accepted: all refs already at desired state (no-op)".to_string(),
593 state: None,
594 maintainers: authorized.into_iter().collect(),
595 purgatory_events: vec![],
596 });
597 }
598 }
599
578 // Check purgatory for matching state events 600 // Check purgatory for matching state events
579 // Convert pushed refs to RefUpdate (filter out refs/nostr/* refs) 601 // Convert pushed refs to RefUpdate (filter out refs/nostr/* refs)
580 let pushed_updates: Vec<RefUpdate> = pushed_refs 602 let pushed_updates: Vec<RefUpdate> = pushed_refs