From 74ebc650ab9d76faaf2066db38c88fedc9f4a63a Mon Sep 17 00:00:00 2001 From: Magnus Thor Torfason Date: Sat, 24 Jul 2021 12:29:16 +0000 Subject: [PATCH 1/2] forget: Add --keep-within-hourly (and friends) Allow keeping hourly/daily/weekly/monthly/yearly snapshots for a given time period. This adds the following flags/parameters to restic forget: --keep-within-hourly duration --keep-within-daily duration --keep-within-weekly duration --keep-within-monthly duration --keep-within-yearly duration Includes following changes: - Add tests for --keep-within-hourly (and friends) - Add documentation for --keep-within-hourly (and friends) - Add changelog for --keep-within-hourly (and friends) --- changelog/unreleased/issue-3414 | 20 + cmd/restic/cmd_forget.go | 47 +- doc/060_forget.rst | 22 + internal/restic/snapshot_policy.go | 86 +- internal/restic/snapshot_policy_test.go | 11 + .../restic/testdata/policy_keep_snapshots_30 | 926 ++++++++++++++++++ .../restic/testdata/policy_keep_snapshots_31 | 862 ++++++++++++++++ .../restic/testdata/policy_keep_snapshots_32 | 318 ++++++ .../restic/testdata/policy_keep_snapshots_33 | 102 ++ .../restic/testdata/policy_keep_snapshots_34 | 54 + .../restic/testdata/policy_keep_snapshots_35 | 157 +++ 11 files changed, 2580 insertions(+), 25 deletions(-) create mode 100644 changelog/unreleased/issue-3414 create mode 100644 internal/restic/testdata/policy_keep_snapshots_30 create mode 100644 internal/restic/testdata/policy_keep_snapshots_31 create mode 100644 internal/restic/testdata/policy_keep_snapshots_32 create mode 100644 internal/restic/testdata/policy_keep_snapshots_33 create mode 100644 internal/restic/testdata/policy_keep_snapshots_34 create mode 100644 internal/restic/testdata/policy_keep_snapshots_35 diff --git a/changelog/unreleased/issue-3414 b/changelog/unreleased/issue-3414 new file mode 100644 index 000000000..e1e50afa5 --- /dev/null +++ b/changelog/unreleased/issue-3414 @@ -0,0 +1,20 @@ +Enhancement: Add --keep-within-hourly switch to restic forget + +restic forget allowed users to specify keeping a given number of +hourly backups, or to keep all backups within a given interval, +but not both, that is to specify keeping hourly backups within +a given interval. + +The --keep-within-hourly switch offers this functionality, and +parallell switches for daily/weekly/monthly/yearly are also +implemneted. The new switches are: + + --keep-within-hourly <1y2m3d4h> + --keep-within-daily <1y2m3d4h> + --keep-within-weekly <1y2m3d4h> + --keep-within-monthly <1y2m3d4h> + --keep-within-yearly <1y2m3d4h> + +https://github.com/restic/restic/issues/3414 +https://github.com/restic/restic/pull/3416 +https://forum.restic.net/t/forget-policy/4014/11 diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 83b0ac91d..602c897d8 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -31,14 +31,19 @@ Exit status is 0 if the command was successful, and non-zero if there was any er // ForgetOptions collects all options for the forget command. type ForgetOptions struct { - Last int - Hourly int - Daily int - Weekly int - Monthly int - Yearly int - Within restic.Duration - KeepTags restic.TagLists + Last int + Hourly int + Daily int + Weekly int + Monthly int + Yearly int + Within restic.Duration + WithinHourly restic.Duration + WithinDaily restic.Duration + WithinWeekly restic.Duration + WithinMonthly restic.Duration + WithinYearly restic.Duration + KeepTags restic.TagLists Hosts []string Tags restic.TagLists @@ -64,6 +69,11 @@ func init() { f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots") f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots") f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&forgetOptions.WithinHourly, "keep-within-hourly", "", "keep hourly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&forgetOptions.WithinDaily, "keep-within-daily", "", "keep daily snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&forgetOptions.WithinWeekly, "keep-within-weekly", "", "keep weekly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&forgetOptions.WithinMonthly, "keep-within-monthly", "", "keep monthly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&forgetOptions.WithinYearly, "keep-within-yearly", "", "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)") f.StringArrayVar(&forgetOptions.Hosts, "host", nil, "only consider snapshots with the given `host` (can be specified multiple times)") @@ -128,14 +138,19 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { } policy := restic.ExpirePolicy{ - Last: opts.Last, - Hourly: opts.Hourly, - Daily: opts.Daily, - Weekly: opts.Weekly, - Monthly: opts.Monthly, - Yearly: opts.Yearly, - Within: opts.Within, - Tags: opts.KeepTags, + Last: opts.Last, + Hourly: opts.Hourly, + Daily: opts.Daily, + Weekly: opts.Weekly, + Monthly: opts.Monthly, + Yearly: opts.Yearly, + Within: opts.Within, + WithinHourly: opts.WithinHourly, + WithinDaily: opts.WithinDaily, + WithinWeekly: opts.WithinWeekly, + WithinMonthly: opts.WithinMonthly, + WithinYearly: opts.WithinYearly, + Tags: opts.KeepTags, } if policy.Empty() && len(args) == 0 { diff --git a/doc/060_forget.rst b/doc/060_forget.rst index e9453ae9f..1071feb5b 100644 --- a/doc/060_forget.rst +++ b/doc/060_forget.rst @@ -190,6 +190,18 @@ The ``forget`` command accepts the following parameters: years, months, days, and hours, e.g. ``2y5m7d3h`` will keep all snapshots made in the two years, five months, seven days, and three hours before the latest snapshot. +- ``--keep-within-hourly duration`` keep all hourly snapshots made within + specified duration of the latest snapshot. The duration is specified in + the same way as for ``--keep-within`` and the method for determining + hourly snapshots is the same as for ``--keep-hourly``. +- ``--keep-within-daily duration`` keep all daily snapshots made within + specified duration of the latest snapshot. +- ``--keep-within-weekly duration`` keep all weekly snapshots made within + specified duration of the latest snapshot. +- ``--keep-within-monthly duration`` keep all monthly snapshots made within + specified duration of the latest snapshot. +- ``--keep-within-yearly duration`` keep all yearly snapshots made within + specified duration of the latest snapshot. .. note:: All calendar related ``--keep-*`` options work on the natural time boundaries and not relative to when you run the ``forget`` command. Weeks @@ -305,6 +317,16 @@ last-day-of-the-months (11 or 12 depends if the 5 weeklies cross a month). And finally 75 last-day-of-the-year snapshots. All other snapshots are removed. +You might want to maintain the same policy as for the example above, but have +irregular backups. For example, the 7 snapshots specified with ``--keep-daily 7`` +might be spread over a longer period. If what you want is to keep daily snapshots +for a week, weekly for a month, monthly for a year and yearly for 75 years, you +could specify: +``forget --keep-daily-within 7d --keep-weekly-within 1m --keep-monthly-within 1y +--keep-yearly-within 75y`` +(Note that `1w` is not a recognized duration, so you will have to specify +`7d` instead) + Customize pruning ***************** diff --git a/internal/restic/snapshot_policy.go b/internal/restic/snapshot_policy.go index c6427c7c0..d063a9ec2 100644 --- a/internal/restic/snapshot_policy.go +++ b/internal/restic/snapshot_policy.go @@ -12,18 +12,25 @@ import ( // ExpirePolicy configures which snapshots should be automatically removed. type ExpirePolicy struct { - Last int // keep the last n snapshots - Hourly int // keep the last n hourly snapshots - Daily int // keep the last n daily snapshots - Weekly int // keep the last n weekly snapshots - Monthly int // keep the last n monthly snapshots - Yearly int // keep the last n yearly snapshots - Within Duration // keep snapshots made within this duration - Tags []TagList // keep all snapshots that include at least one of the tag lists. + Last int // keep the last n snapshots + Hourly int // keep the last n hourly snapshots + Daily int // keep the last n daily snapshots + Weekly int // keep the last n weekly snapshots + Monthly int // keep the last n monthly snapshots + Yearly int // keep the last n yearly snapshots + Within Duration // keep snapshots made within this duration + WithinHourly Duration // keep hourly snapshots made within this duration + WithinDaily Duration // keep daily snapshots made within this duration + WithinWeekly Duration // keep weekly snapshots made within this duration + WithinMonthly Duration // keep monthly snapshots made within this duration + WithinYearly Duration // keep yearly snapshots made within this duration + Tags []TagList // keep all snapshots that include at least one of the tag lists. } func (e ExpirePolicy) String() (s string) { var keeps []string + var keepw []string + if e.Last > 0 { keeps = append(keeps, fmt.Sprintf("%d latest", e.Last)) } @@ -43,8 +50,35 @@ func (e ExpirePolicy) String() (s string) { keeps = append(keeps, fmt.Sprintf("%d yearly", e.Yearly)) } + if !e.WithinHourly.Zero() { + keepw = append(keepw, fmt.Sprintf("hourly snapshots within %v", e.WithinHourly)) + } + + if !e.WithinDaily.Zero() { + keepw = append(keepw, fmt.Sprintf("daily snapshots within %v", e.WithinDaily)) + } + + if !e.WithinWeekly.Zero() { + keepw = append(keepw, fmt.Sprintf("weekly snapshots within %v", e.WithinWeekly)) + } + + if !e.WithinMonthly.Zero() { + keepw = append(keepw, fmt.Sprintf("monthly snapshots within %v", e.WithinMonthly)) + } + + if !e.WithinYearly.Zero() { + keepw = append(keepw, fmt.Sprintf("yearly snapshots within %v", e.WithinYearly)) + } + if len(keeps) > 0 { - s = fmt.Sprintf("keep %s snapshots", strings.Join(keeps, ", ")) + s = fmt.Sprintf("%s snapshots", strings.Join(keeps, ", ")) + } + + if len(keepw) > 0 { + if s != "" { + s += ", " + } + s += strings.Join(keepw, ", ") } if len(e.Tags) > 0 { @@ -61,6 +95,8 @@ func (e ExpirePolicy) String() (s string) { s += fmt.Sprintf("all snapshots within %s of the newest", e.Within) } + s = "keep " + s + return s } @@ -166,6 +202,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason return list, nil, nil } + // These buckets are for keeping last n snapshots of given type var buckets = [6]struct { Count int bucker func(d time.Time, nr int) int @@ -180,6 +217,20 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason {p.Yearly, y, -1, "yearly snapshot"}, } + // These buckets are for keeping snapshots of given type within duration + var bucketsWithin = [5]struct { + Within Duration + bucker func(d time.Time, nr int) int + Last int + reason string + }{ + {p.WithinHourly, ymdh, -1, "hourly within"}, + {p.WithinDaily, ymd, -1, "daily within"}, + {p.WithinWeekly, yw, -1, "weekly within"}, + {p.WithinMonthly, ym, -1, "monthly within"}, + {p.WithinYearly, y, -1, "yearly within"}, + } + latest := findLatestTimestamp(list) for nr, cur := range list { @@ -217,6 +268,23 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason } } + // If the timestamp is within range, and the snapshot is an hourly/daily/weekly/monthly/yearly snapshot, then keep it + for i, b := range bucketsWithin { + if !b.Within.Zero() { + t := latest.AddDate(-b.Within.Years, -b.Within.Months, -b.Within.Days).Add(time.Hour * time.Duration(-b.Within.Hours)) + + if cur.Time.After(t) { + val := b.bucker(cur.Time, nr) + if val != b.Last { + debug.Log("keep %v, time %v, ID %v, bucker %v, val %v %v\n", b.reason, cur.Time, cur.id.Str(), i, val, b.Last) + keepSnap = true + bucketsWithin[i].Last = val + keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("%v %v", b.reason, b.Within)) + } + } + } + } + if keepSnap { keep = append(keep, cur) kr := KeepReason{ diff --git a/internal/restic/snapshot_policy_test.go b/internal/restic/snapshot_policy_test.go index 7c9be67e7..cb35a946d 100644 --- a/internal/restic/snapshot_policy_test.go +++ b/internal/restic/snapshot_policy_test.go @@ -228,6 +228,17 @@ func TestApplyPolicy(t *testing.T) { {Within: parseDuration("13d23h")}, {Within: parseDuration("2m2h")}, {Within: parseDuration("1y2m3d3h")}, + {WithinHourly: parseDuration("1y2m3d3h")}, + {WithinDaily: parseDuration("1y2m3d3h")}, + {WithinWeekly: parseDuration("1y2m3d3h")}, + {WithinMonthly: parseDuration("1y2m3d3h")}, + {WithinYearly: parseDuration("1y2m3d3h")}, + {Within: parseDuration("1h"), + WithinHourly: parseDuration("1d"), + WithinDaily: parseDuration("7d"), + WithinWeekly: parseDuration("1m"), + WithinMonthly: parseDuration("1y"), + WithinYearly: parseDuration("9999y")}, } for i, p := range tests { diff --git a/internal/restic/testdata/policy_keep_snapshots_30 b/internal/restic/testdata/policy_keep_snapshots_30 new file mode 100644 index 000000000..b2649358f --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_30 @@ -0,0 +1,926 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T12:30:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T11:23:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T10:23:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-01T01:03:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-12T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-09T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-06T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-05T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-01T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-09T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-05T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-02T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-01T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-12T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:30:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T11:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T10:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T01:03:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "hourly within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_31 b/internal/restic/testdata/policy_keep_snapshots_31 new file mode 100644 index 000000000..51f42e25d --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_31 @@ -0,0 +1,862 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-12T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-09T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-06T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-05T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-01T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-09T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-05T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-02T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-01T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-12T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-10T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "daily within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_32 b/internal/restic/testdata/policy_keep_snapshots_32 new file mode 100644 index 000000000..c4fa50878 --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_32 @@ -0,0 +1,318 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "weekly within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_33 b/internal/restic/testdata/policy_keep_snapshots_33 new file mode 100644 index 000000000..3e3fceeb5 --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_33 @@ -0,0 +1,102 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_34 b/internal/restic/testdata/policy_keep_snapshots_34 new file mode 100644 index 000000000..a3dbc9335 --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_34 @@ -0,0 +1,54 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "yearly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "yearly within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "yearly within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_35 b/internal/restic/testdata/policy_keep_snapshots_35 new file mode 100644 index 000000000..a4def907a --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_35 @@ -0,0 +1,157 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1h", + "hourly within 1d", + "daily within 7d", + "weekly within 1m", + "monthly within 1y", + "yearly within 9999y" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "daily within 7d", + "weekly within 1m" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1m" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "weekly within 1m" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y", + "yearly within 9999y" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "monthly within 1y" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "yearly within 9999y" + ], + "counters": {} + } + ] +} \ No newline at end of file From 2081bd12fbfec4574df75796c1bb2336d1b2ec05 Mon Sep 17 00:00:00 2001 From: Magnus Thor Torfason Date: Sat, 24 Jul 2021 15:58:26 +0000 Subject: [PATCH 2/2] forget: Ensure future snapshots do not affect --keep-within-* Ensure that only snapshots made in the past are taken into account when running restic forget with the within switches (--keep-within, --keep-within- hourly, and friends) --- internal/restic/snapshot_policy.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/restic/snapshot_policy.go b/internal/restic/snapshot_policy.go index d063a9ec2..8ce6cb2e3 100644 --- a/internal/restic/snapshot_policy.go +++ b/internal/restic/snapshot_policy.go @@ -147,15 +147,19 @@ func always(d time.Time, nr int) int { return nr } -// findLatestTimestamp returns the time stamp for the newest snapshot. +// findLatestTimestamp returns the time stamp for the latest (newest) snapshot, +// for use with policies based on time relative to latest. func findLatestTimestamp(list Snapshots) time.Time { if len(list) == 0 { panic("list of snapshots is empty") } var latest time.Time + now := time.Now() for _, sn := range list { - if sn.Time.After(latest) { + // Find the latest snapshot in the list + // The latest snapshot must, however, not be in the future. + if sn.Time.After(latest) && sn.Time.Before(now) { latest = sn.Time } }