prune.rs
raw
//! 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<SnapshotId>,
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<Pruned, Error> {
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<SnapshotId> = 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,
})
}