Skip to content

Commit

Permalink
move forget to rustic_core
Browse files Browse the repository at this point in the history
  • Loading branch information
aawsome committed Jun 26, 2023
1 parent 4250bb2 commit 53515fc
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 338 deletions.
1 change: 1 addition & 0 deletions crates/rustic_core/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod cat;
pub mod check;
pub mod forget;
pub mod prune;
pub mod repoinfo;
pub mod snapshots;
371 changes: 371 additions & 0 deletions crates/rustic_core/src/commands/forget.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
//! `forget` subcommand

use chrono::{DateTime, Datelike, Duration, Local, Timelike};
use derivative::Derivative;
use merge::Merge;
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};

use crate::{
Id, OpenRepository, ProgressBars, RusticResult, SnapshotFile, SnapshotGroup,
SnapshotGroupCriterion, StringList,
};

type CheckFunction = fn(&SnapshotFile, &SnapshotFile) -> bool;

#[derive(Debug)]
pub struct ForgetGroups(pub Vec<ForgetGroup>);

#[derive(Debug)]
pub struct ForgetGroup {
pub group: SnapshotGroup,
pub snapshots: Vec<ForgetSnapshot>,
}

#[derive(Debug)]
pub struct ForgetSnapshot {
pub snapshot: SnapshotFile,
pub forget: bool,
pub reasons: Vec<String>,
}

impl ForgetGroups {
pub fn into_forget_ids(self) -> Vec<Id> {
self.0
.into_iter()
.flat_map(|fg| {
fg.snapshots
.into_iter()
.filter_map(|fsn| fsn.forget.then_some(fsn.snapshot.id))
})
.collect()
}
}

pub(crate) fn get_forget_snapshots<P: ProgressBars>(
repo: &OpenRepository<P>,
keep: &KeepOptions,
group_by: SnapshotGroupCriterion,
filter: impl FnMut(&SnapshotFile) -> bool,
) -> RusticResult<ForgetGroups> {
let be = &repo.dbe;
let pb = &repo.pb;

let p = pb.progress_hidden();
let groups = SnapshotFile::group_from_backend(be, filter, group_by, &p)?;
let mut forget_snaps = Vec::new();

Check failure on line 56 in crates/rustic_core/src/commands/forget.rs

View workflow job for this annotation

GitHub Actions / Clippy Output

collection is never read

error: collection is never read --> crates/rustic_core/src/commands/forget.rs:56:5 | 56 | let mut forget_snaps = Vec::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read = note: `-D clippy::collection-is-never-read` implied by `-D warnings`

let groups = groups
.into_iter()
.map(|(group, mut snapshots)| -> RusticResult<_> {
let mut snaps = Vec::new();
snapshots.sort_unstable_by(|sn1, sn2| sn1.cmp(sn2).reverse());
let latest_time = snapshots[0].time;
let mut group_keep = keep.clone();

let mut iter = snapshots.into_iter().peekable();
let mut last = None;
let now = Local::now();

while let Some(sn) = iter.next() {
let (forget, reasons) = {
if sn.must_keep(now) {
(false, vec!["snapshot".to_string()])
} else if sn.must_delete(now) {
forget_snaps.push(sn.id);
(true, vec!["snapshot".to_string()])
} else {
match group_keep.matches(
&sn,
last.as_ref(),
iter.peek().is_some(),
latest_time,
) {
None => {
forget_snaps.push(sn.id);
(true, Vec::new())
}
Some(reason) => (false, vec![reason]),
}
}
};
last = Some(sn.clone());

snaps.push(ForgetSnapshot {
snapshot: sn,
forget,
reasons,
});
}
Ok(ForgetGroup {
group,
snapshots: snaps,
})
})
.collect::<RusticResult<_>>()?;

Ok(ForgetGroups(groups))
}

#[serde_as]
#[derive(Clone, Debug, PartialEq, Derivative, clap::Parser, Deserialize, Merge)]

Check failure on line 111 in crates/rustic_core/src/commands/forget.rs

View workflow job for this annotation

GitHub Actions / Clippy Output

you are deriving `PartialEq` and can implement `Eq`

error: you are deriving `PartialEq` and can implement `Eq` --> crates/rustic_core/src/commands/forget.rs:111:24 | 111 | #[derive(Clone, Debug, PartialEq, Derivative, clap::Parser, Deserialize, Merge)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq = note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings`
#[derivative(Default)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct KeepOptions {
/// Keep snapshots with this taglist (can be specified multiple times)
#[clap(long, value_name = "TAG[,TAG,..]")]
#[serde_as(as = "Vec<DisplayFromStr>")]
#[merge(strategy=merge::vec::overwrite_empty)]
keep_tags: Vec<StringList>,

/// Keep snapshots ids that start with ID (can be specified multiple times)
#[clap(long = "keep-id", value_name = "ID")]
#[merge(strategy=merge::vec::overwrite_empty)]
keep_ids: Vec<String>,

/// Keep the last N snapshots (N == -1: keep all snapshots)
#[clap(long, short = 'l', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_last: i32,

/// Keep the last N hourly snapshots (N == -1: keep all hourly snapshots)
#[clap(long, short = 'H', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_hourly: i32,

/// Keep the last N daily snapshots (N == -1: keep all daily snapshots)
#[clap(long, short = 'd', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_daily: i32,

/// Keep the last N weekly snapshots (N == -1: keep all weekly snapshots)
#[clap(long, short = 'w', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_weekly: i32,

/// Keep the last N monthly snapshots (N == -1: keep all monthly snapshots)
#[clap(long, short = 'm', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_monthly: i32,

/// Keep the last N quarter-yearly snapshots (N == -1: keep all quarter-yearly snapshots)
#[clap(long, value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_quarter_yearly: i32,

/// Keep the last N half-yearly snapshots (N == -1: keep all half-yearly snapshots)
#[clap(long, value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_half_yearly: i32,

/// Keep the last N yearly snapshots (N == -1: keep all yearly snapshots)
#[clap(long, short = 'y', value_name = "N", default_value = "0", allow_hyphen_values = true, value_parser = clap::value_parser!(i32).range(-1..))]
#[merge(strategy=merge::num::overwrite_zero)]
keep_yearly: i32,

/// Keep snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0h")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within: humantime::Duration,

/// Keep hourly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0h")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_hourly: humantime::Duration,

/// Keep daily snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0d")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_daily: humantime::Duration,

/// Keep weekly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0w")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_weekly: humantime::Duration,

/// Keep monthly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0m")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_monthly: humantime::Duration,

/// Keep quarter-yearly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0y")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_quarter_yearly: humantime::Duration,

/// Keep half-yearly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0y")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_half_yearly: humantime::Duration,

/// Keep yearly snapshots newer than DURATION relative to latest snapshot
#[clap(long, value_name = "DURATION", default_value = "0y")]
#[derivative(Default(value = "std::time::Duration::ZERO.into()"))]
#[serde_as(as = "DisplayFromStr")]
#[merge(strategy=overwrite_zero_duration)]
keep_within_yearly: humantime::Duration,
}

fn overwrite_zero_duration(left: &mut humantime::Duration, right: humantime::Duration) {
if *left == std::time::Duration::ZERO.into() {
*left = right;
}
}

const fn always_false(_sn1: &SnapshotFile, _sn2: &SnapshotFile) -> bool {
false
}

fn equal_year(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year()
}

fn equal_half_year(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.month0() / 6 == t2.month0() / 6
}

fn equal_quarter_year(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.month0() / 3 == t2.month0() / 3
}

fn equal_month(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.month() == t2.month()
}

fn equal_week(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.iso_week().week() == t2.iso_week().week()
}

fn equal_day(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.ordinal() == t2.ordinal()
}

fn equal_hour(sn1: &SnapshotFile, sn2: &SnapshotFile) -> bool {
let (t1, t2) = (sn1.time, sn2.time);
t1.year() == t2.year() && t1.ordinal() == t2.ordinal() && t1.hour() == t2.hour()
}

impl KeepOptions {
fn matches(
&mut self,
sn: &SnapshotFile,
last: Option<&SnapshotFile>,
has_next: bool,
latest_time: DateTime<Local>,
) -> Option<String> {
let mut keep = false;
let mut reason = Vec::new();

let snapshot_id_hex = sn.id.to_hex();
if self
.keep_ids
.iter()
.any(|id| snapshot_id_hex.starts_with(id))
{
keep = true;
reason.push("id");
}

if !self.keep_tags.is_empty() && sn.tags.matches(&self.keep_tags) {
keep = true;
reason.push("tags");
}

let keep_checks: [(CheckFunction, &mut i32, &str, humantime::Duration, &str); 8] = [
(
always_false,
&mut self.keep_last,
"last",
self.keep_within,
"within",
),
(
equal_hour,
&mut self.keep_hourly,
"hourly",
self.keep_within_hourly,
"within hourly",
),
(
equal_day,
&mut self.keep_daily,
"daily",
self.keep_within_daily,
"within daily",
),
(
equal_week,
&mut self.keep_weekly,
"weekly",
self.keep_within_weekly,
"within weekly",
),
(
equal_month,
&mut self.keep_monthly,
"monthly",
self.keep_within_monthly,
"within monthly",
),
(
equal_quarter_year,
&mut self.keep_quarter_yearly,
"quarter-yearly",
self.keep_within_quarter_yearly,
"within quarter-yearly",
),
(
equal_half_year,
&mut self.keep_half_yearly,
"half-yearly",
self.keep_within_half_yearly,
"within half-yearly",
),
(
equal_year,
&mut self.keep_yearly,
"yearly",
self.keep_within_yearly,
"within yearly",
),
];

for (check_fun, counter, reason1, within, reason2) in keep_checks {
if !has_next || last.is_none() || !check_fun(sn, last.unwrap()) {
if *counter != 0 {
keep = true;
reason.push(reason1);
if *counter > 0 {
*counter -= 1;
}
}
if sn.time + Duration::from_std(*within).unwrap() > latest_time {
keep = true;
reason.push(reason2);
}
}
}

keep.then_some(reason.join("\n"))
}
}
1 change: 1 addition & 0 deletions crates/rustic_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub use crate::{
chunker::random_poly,
commands::{
check::CheckOpts,
forget::{ForgetGroup, ForgetGroups, ForgetSnapshot, KeepOptions},
prune::{PruneOpts, PrunePlan, PruneStats},
repoinfo::{BlobInfo, IndexInfos, PackInfo, RepoFileInfo, RepoFileInfos},
},
Expand Down
Loading

0 comments on commit 53515fc

Please sign in to comment.