diff --git a/CHANGELOG.md b/CHANGELOG.md index b891537..cc58b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Removed superseded same-author repository states from purgatory after their - replacement is promoted when the pushed Git data cannot reconstruct them. - Reconstructable rollback states and other maintainers' states are retained. + replacement is promoted when the locally available Git data cannot + reconstruct them. Reconstructable rollback states and other maintainers' + states are retained. ## [2.0.0] - 2026-07-27 diff --git a/docs/explanation/purgatory-design.md b/docs/explanation/purgatory-design.md index a118463..aa66744 100644 --- a/docs/explanation/purgatory-design.md +++ b/docs/explanation/purgatory-design.md @@ -442,9 +442,10 @@ sequenceDiagram State promotion remains oldest-first so reconstructable predecessors reach relay history before a newer replacement. After a promotion pass, purgatory -removes only older events on the promoted event's exact coordinate (same author -and `d` tag) when their Git objects are still unavailable. Reconstructable -predecessors and other maintainers' states remain as rollback candidates. +removes only non-preferred events on the promoted event's exact coordinate +(same author and `d` tag) when their Git objects are still unavailable. +Reconstructable predecessors and other maintainers' states remain as rollback +candidates. ### PR Event Arrival (Kind 1617/1618) diff --git a/src/git/sync.rs b/src/git/sync.rs index 8ccac63..1f4f151 100644 --- a/src/git/sync.rs +++ b/src/git/sync.rs @@ -35,7 +35,7 @@ use std::process::Command; use tracing::{debug, info, warn}; use async_trait::async_trait; -use nostr_sdk::prelude::Event; +use nostr_sdk::prelude::{Event, SaveEventStatus}; use crate::git::authorization::{ collect_authorized_maintainers, fetch_repository_data_excluding_purgatory, @@ -863,7 +863,7 @@ async fn save_promoted_event( event: &Event, context: PurgatorySaveContext, promotion_hooks: Option<&dyn PurgatoryPromotionHooks>, -) -> anyhow::Result<()> { +) -> anyhow::Result { if let Some(hooks) = promotion_hooks { hooks.before_event_saved(event, context).await; } @@ -871,7 +871,6 @@ async fn save_promoted_event( database .save_event(event) .await - .map(|_| ()) .map_err(|e| anyhow::anyhow!("Failed to save promoted event {}: {e}", event.id)) } @@ -1210,7 +1209,7 @@ async fn process_purgatory_state_events( ) .await { - Ok(_) => { + Ok(save_status) => { info!( identifier = %identifier, event_id = %entry.event.id, @@ -1236,7 +1235,9 @@ async fn process_purgatory_state_events( // Remove from purgatory purgatory.remove_state_event(identifier, &entry.event.id); result.states_released += 1; - promoted_states.push(entry.event.clone()); + if save_status.is_success() { + promoted_states.push(entry.event.clone()); + } // Add the newly saved state to db_repo_data so subsequent iterations // can correctly determine if they're the latest diff --git a/src/purgatory/mod.rs b/src/purgatory/mod.rs index b93e7f8..db8cfca 100644 --- a/src/purgatory/mod.rs +++ b/src/purgatory/mod.rs @@ -640,15 +640,19 @@ impl Purgatory { } } - /// Remove older states on coordinates promoted in this pass when their Git - /// objects are still unavailable. Reconstructable predecessors and states - /// from other authors remain available as rollback candidates. + /// Remove non-preferred states on coordinates promoted in this pass when + /// their Git objects are still unavailable. Reconstructable predecessors + /// and states from other authors remain available as rollback candidates. pub(crate) fn remove_unreconstructable_state_replacements( &self, identifier: &str, promoted: &[Event], source_repo_path: &Path, ) -> usize { + if promoted.is_empty() { + return 0; + } + let is_preferred = |candidate: &Event, current: &Event| { candidate.created_at > current.created_at || (candidate.created_at == current.created_at && candidate.id < current.id)