mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-30 11:07:33 +00:00
3.1 KiB
3.1 KiB
Accept No-Op Pushes (old_oid == new_oid)
ID: 6af5
Problem
When a user pushes refs where old_oid == new_oid (no actual change), ngit-grasp rejects the push with "No state event found in purgatory from authorized publishers". This creates a poor user experience because:
- Race condition: User fetches state, but between fetch and push, a state event arrives and is applied. Their push now has
old_oid == new_oidbut gets rejected. - User confusion: The rejection makes users think their desired state was denied, when in fact it's already accepted.
- Git compatibility: Git accepts no-op pushes with "Everything up-to-date" - we should too.
Example from logs (work/tmp.md)
Feb 03 14:10:12 de1 ngit-grasp[6385]: 2026-02-03T14:10:12.937204Z DEBUG ngit_grasp::git::authorization: refs/heads/main 1c1f162929df98557fa58c4a7a392c66de862a87 -> 1c1f162929df98557fa58c4a7a392c66de862a87
Feb 03 14:10:12 de1 ngit-grasp[6385]: 2026-02-03T14:10:12.938617Z WARN ngit_grasp::git::handlers: Push rejected for f1-sunset-racing: No state event found in purgatory from authorized publishers
The push is trying to update main from 1c1f162... to 1c1f162... (same commit), but it's rejected because:
- The state event was already applied (between fetch and push)
- No state event remains in purgatory
- Authorization requires a purgatory event
Plan
- Phase 1: Detect no-op pushes in
get_state_authorization_for_specific_owner_repo- Check if all pushed refs have
old_oid == new_oid - Return success immediately without checking purgatory
- Include appropriate reason message
- Check if all pushed refs have
- Phase 2: Add tests for no-op push scenarios
- Test single ref no-op
- Test multiple refs all no-op
- Test mixed (some no-op, some real changes) - should still require purgatory
- Phase 3: Update documentation
- Document no-op push behavior in architecture docs
- Note Git compatibility
Progress
2026-02-03 [Session 14:30]
- Created: Identified race condition from production logs
- Root cause: User's push becomes no-op after state event is applied
- Decision: Accept no-op pushes as successful without requiring purgatory event
- Next: Implement detection in authorization.rs
Notes
Location
- File:
src/git/authorization.rs - Function:
get_state_authorization_for_specific_owner_repo(line 531) - Before purgatory check (line 595)
Implementation Strategy
// Early detection: check if all refs are no-ops
let all_noop = pushed_refs
.iter()
.filter(|(_, _, name)| !name.starts_with("refs/nostr/"))
.all(|(old_oid, new_oid, _)| old_oid == new_oid);
if all_noop && !pushed_refs.is_empty() {
return Ok(AuthorizationResult {
authorized: true,
reason: "Push accepted: all refs already at desired state (no-op)".to_string(),
state: None,
maintainers: authorized.into_iter().collect(),
purgatory_events: vec![],
});
}
Edge Cases
- Empty push: Should not trigger no-op logic (no refs to check)
- Mixed push: Some refs no-op, some real changes → require purgatory (normal flow)
- refs/nostr/ refs: Should be excluded from no-op detection (different authorization path)