From 7486bfea5bbbcccdf01379c36c2af9fe937c1962 Mon Sep 17 00:00:00 2001 From: plumbeo Date: Wed, 14 Nov 2018 16:28:17 +0100 Subject: [PATCH 1/3] Extend custom Duration granularity to hours and add tests --- internal/restic/duration.go | 14 ++++++++++---- internal/restic/duration_test.go | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/internal/restic/duration.go b/internal/restic/duration.go index 09289849b..ad56bcc81 100644 --- a/internal/restic/duration.go +++ b/internal/restic/duration.go @@ -10,9 +10,9 @@ import ( ) // Duration is similar to time.Duration, except it only supports larger ranges -// like days, months, and years. +// like hours, days, months, and years. type Duration struct { - Days, Months, Years int + Hours, Days, Months, Years int } func (d Duration) String() string { @@ -29,6 +29,10 @@ func (d Duration) String() string { s += fmt.Sprintf("%dd", d.Days) } + if d.Hours != 0 { + s += fmt.Sprintf("%dh", d.Hours) + } + return s } @@ -73,7 +77,7 @@ func nextNumber(input string) (num int, rest string, err error) { } // ParseDuration parses a duration from a string. The format is: -// 6y5m234d +// 6y5m234d37h func ParseDuration(s string) (Duration, error) { var ( d Duration @@ -100,6 +104,8 @@ func ParseDuration(s string) (Duration, error) { d.Months = num case 'd': d.Days = num + case 'h': + d.Hours = num } s = s[1:] @@ -127,5 +133,5 @@ func (d Duration) Type() string { // Zero returns true if the duration is empty (all values are set to zero). func (d Duration) Zero() bool { - return d.Years == 0 && d.Months == 0 && d.Days == 0 + return d.Years == 0 && d.Months == 0 && d.Days == 0 && d.Hours == 0 } diff --git a/internal/restic/duration_test.go b/internal/restic/duration_test.go index 0d5306069..716c00cc9 100644 --- a/internal/restic/duration_test.go +++ b/internal/restic/duration_test.go @@ -13,15 +13,24 @@ func TestNextNumber(t *testing.T) { rest string err bool }{ + { + input: "12h", num: 12, rest: "h", + }, { input: "3d", num: 3, rest: "d", }, + { + input: "4d9h", num: 4, rest: "d9h", + }, { input: "7m5d", num: 7, rest: "m5d", }, { input: "-23y7m5d", num: -23, rest: "y7m5d", }, + { + input: "-13y5m11d12h", num: -13, rest: "y5m11d12h", + }, { input: " 5d", num: 0, rest: " 5d", err: true, }, @@ -55,10 +64,15 @@ func TestParseDuration(t *testing.T) { d Duration output string }{ + {"9h", Duration{Hours: 9}, "9h"}, {"3d", Duration{Days: 3}, "3d"}, + {"4d2h", Duration{Days: 4, Hours: 2}, "4d2h"}, {"7m5d", Duration{Months: 7, Days: 5}, "7m5d"}, + {"6m4d8h", Duration{Months: 6, Days: 4, Hours: 8}, "6m4d8h"}, {"5d7m", Duration{Months: 7, Days: 5}, "7m5d"}, + {"4h3d9m", Duration{Months: 9, Days: 3, Hours: 4}, "9m3d4h"}, {"-7m5d", Duration{Months: -7, Days: 5}, "-7m5d"}, + {"1y4m-5d-3h", Duration{Years: 1, Months: 4, Days: -5, Hours: -3}, "1y4m-5d-3h"}, {"2y7m-5d", Duration{Years: 2, Months: 7, Days: -5}, "2y7m-5d"}, } From 71891b340ccdee5d32a1f8c6371bba8902bf4108 Mon Sep 17 00:00:00 2001 From: plumbeo Date: Wed, 14 Nov 2018 17:24:59 +0100 Subject: [PATCH 2/3] Support time ranges expressed in hours in snapshot retention policies Make restic forget --keep-within accept time ranges measured in hours and choose accordingly which snapshots to keep and which to forget. Add relative tests. --- cmd/restic/cmd_forget.go | 2 +- internal/restic/snapshot_policy.go | 2 +- internal/restic/snapshot_policy_test.go | 3 + .../restic/testdata/policy_keep_snapshots_27 | 150 +++ .../restic/testdata/policy_keep_snapshots_28 | 374 ++++++ .../restic/testdata/policy_keep_snapshots_29 | 1132 +++++++++++++++++ 6 files changed, 1661 insertions(+), 2 deletions(-) create mode 100644 internal/restic/testdata/policy_keep_snapshots_27 create mode 100644 internal/restic/testdata/policy_keep_snapshots_28 create mode 100644 internal/restic/testdata/policy_keep_snapshots_29 diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 3b9862171..bafd540bd 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -59,7 +59,7 @@ func init() { f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots") 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 older than `duration` (eg. 1y5m7d) relative to the latest snapshot") + f.VarP(&forgetOptions.Within, "keep-within", "", "keep 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.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`") diff --git a/internal/restic/snapshot_policy.go b/internal/restic/snapshot_policy.go index 7fc5e384e..08ed843c8 100644 --- a/internal/restic/snapshot_policy.go +++ b/internal/restic/snapshot_policy.go @@ -196,7 +196,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason // If the timestamp of the snapshot is within the range, then keep it. if !p.Within.Zero() { - t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days) + t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days).Add(time.Hour * time.Duration(-p.Within.Hours)) if cur.Time.After(t) { keepSnap = true keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("within %v", p.Within)) diff --git a/internal/restic/snapshot_policy_test.go b/internal/restic/snapshot_policy_test.go index a9a80dd79..7c9be67e7 100644 --- a/internal/restic/snapshot_policy_test.go +++ b/internal/restic/snapshot_policy_test.go @@ -225,6 +225,9 @@ func TestApplyPolicy(t *testing.T) { {Within: parseDuration("1m")}, {Within: parseDuration("1m14d")}, {Within: parseDuration("1y1d1m")}, + {Within: parseDuration("13d23h")}, + {Within: parseDuration("2m2h")}, + {Within: parseDuration("1y2m3d3h")}, } for i, p := range tests { diff --git a/internal/restic/testdata/policy_keep_snapshots_27 b/internal/restic/testdata/policy_keep_snapshots_27 new file mode 100644 index 000000000..a70c49525 --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_27 @@ -0,0 +1,150 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:02: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 + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 13d23h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_28 b/internal/restic/testdata/policy_keep_snapshots_28 new file mode 100644 index 000000000..d78171a2f --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_28 @@ -0,0 +1,374 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:02: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-04T12:28:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T12:24:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T12:23: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": "2016-01-01T01:02: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 + } + ], + "reasons": [ + { + "snapshot": { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:30:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:28:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:24:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T11:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T10:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T01:03:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T01:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 2m2h" + ], + "counters": {} + } + ] +} \ No newline at end of file diff --git a/internal/restic/testdata/policy_keep_snapshots_29 b/internal/restic/testdata/policy_keep_snapshots_29 new file mode 100644 index 000000000..172d3000f --- /dev/null +++ b/internal/restic/testdata/policy_keep_snapshots_29 @@ -0,0 +1,1132 @@ +{ + "keep": [ + { + "time": "2016-01-18T12:02:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-12T21:02: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-04T12:28:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T12:24:03Z", + "tree": null, + "paths": null + }, + { + "time": "2016-01-04T12:23: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": "2016-01-01T01:02: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-13T10:20:30Z", + "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-22T10:20:30Z", + "tree": null, + "paths": [ + "path1", + "path2" + ], + "tags": [ + "foo", + "bar" + ] + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + { + "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-13T10:20:30Z", + "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": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-12T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-09T21:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-08T20:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-07T10:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-06T08:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-05T09:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T16:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:30:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:28:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:24:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T12:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T11:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-04T10:23:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-03T07:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T07:08:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T01:03:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2016-01-01T01:02:03Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-13T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-11-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": [ + "path1", + "path2" + ], + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-22T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-10-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-11T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-09T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-06T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-05T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-02T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-09-01T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-15T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-13T10:20:30.1Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-13T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-12T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-10T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2015-08-08T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-22T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-21T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-20T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-18T10:20:30Z", + "tree": null, + "paths": null + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + }, + { + "snapshot": { + "time": "2014-11-15T10:20:30Z", + "tree": null, + "paths": null, + "tags": [ + "foo", + "bar" + ] + }, + "matches": [ + "within 1y2m3d3h" + ], + "counters": {} + } + ] +} \ No newline at end of file From 3edc723bf00c1abce9db40bdf4a018d5a60dc432 Mon Sep 17 00:00:00 2001 From: plumbeo Date: Fri, 16 Nov 2018 17:35:33 +0100 Subject: [PATCH 3/3] Update 'restic forget --keep-within' documentation and add changelog entry --- changelog/unreleased/issue-2089 | 9 +++++++++ doc/060_forget.rst | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/issue-2089 diff --git a/changelog/unreleased/issue-2089 b/changelog/unreleased/issue-2089 new file mode 100644 index 000000000..d867c4fa4 --- /dev/null +++ b/changelog/unreleased/issue-2089 @@ -0,0 +1,9 @@ +Enhancement: increase granularity of the "keep within" retention policy + +The `keep-within` option of the `forget` command now accepts time ranges with +an hourly granularity. For example, running `restic forget --keep-within 3d12h` +will keep all the snapshots made within three days and twelve hours from the +time of the latest snapshot. + +https://github.com/restic/restic/issues/2089 +https://github.com/restic/restic/pull/2090 diff --git a/doc/060_forget.rst b/doc/060_forget.rst index 7222c1711..a1edc55fc 100644 --- a/doc/060_forget.rst +++ b/doc/060_forget.rst @@ -168,8 +168,9 @@ The ``forget`` command accepts the following parameters: this option (can be specified multiple times). - ``--keep-within duration`` keep all snapshots which have been made within the duration of the latest snapshot. ``duration`` needs to be a number of - years, months, and days, e.g. ``2y5m7d`` will keep all snapshots made in the - two years, five months, and seven days before the latest snapshot. + 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. Multiple policies will be ORed together so as to be as inclusive as possible for keeping snapshots.