//! Removing snapshots, and with them the chunks nothing references any //! more. //! //! The care this takes not to sweep away something still wanted lives in //! [`crate::sweep`], which it shares with [`crate::excise`]. What is //! particular to a prune is the decision: the snapshots named on the //! command line go, and everything else stays. use repository::{Coverage, Repository, Snapshot, SnapshotId}; pub use crate::sweep::Error; use crate::sweep::{mark, refuse_if_locked, sweep}; /// Says so before a snapshot that a suspended backup may be relying on /// is taken away from it. /// /// Only a prune has this to say. An excision replaces such a snapshot /// rather than removing it, and tells the run where its replacement is; /// where it cannot, it refuses outright instead of going ahead under a /// warning. fn warn_if_partial(snapshot: &Snapshot, progress: &mut impl FnMut(&str)) { if snapshot.coverage != Coverage::Partial { return; } progress(&format!( "warning: {} is a partial snapshot, left by an interrupted backup of \ {}. If that backup is still suspended, removing this leaves it \ unable to finish, and it will have to start again.", snapshot.id, snapshot.root.display() )); } /// What a prune did, or would do. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Pruned { pub removed: Vec, pub kept: usize, pub swept: usize, pub retained: usize, } /// Removes `remove` from the repository and sweeps whatever chunks are /// then unreferenced. /// /// With `dry_run`, nothing is deleted or recorded and the counts /// describe what would have happened. pub fn prune( repository: &Repository, remove: &[SnapshotId], dry_run: bool, break_lock: bool, mut progress: impl FnMut(&str), ) -> Result { refuse_if_locked(repository, break_lock)?; let all = repository.snapshots()?; for id in remove { if !all.contains(id) { return Err(Error::UnknownSnapshot(*id)); } } for id in remove { warn_if_partial(&repository.load_snapshot(*id)?, &mut progress); } let keeping: Vec = all .iter() .copied() .filter(|id| !remove.contains(id)) .collect(); let counts = if dry_run { mark(repository, &keeping, &mut progress)?.counts() } else { // The snapshot records go before anything is swept, so that an // interruption can only ever leave chunks nothing references — // never a snapshot whose chunks have gone for id in remove { repository.delete_snapshot(*id)?; } sweep(repository, &keeping, remove.to_vec(), &mut progress)? }; Ok(Pruned { removed: remove.to_vec(), kept: keeping.len(), swept: counts.swept, retained: counts.retained, }) }