Merge pull request #4234 from thndrbrrr/forget-opts-neg1-means-forever-issue-2565

restic forget --keep-* options will interpret -1 as "forever"
This commit is contained in:
Michael Eischer
2023-04-14 23:18:47 +02:00
committed by GitHub
12 changed files with 5555 additions and 62 deletions

View File

@@ -68,7 +68,7 @@ type SnapshotGroupKey struct {
}
// GroupSnapshots takes a list of snapshots and a grouping criteria and creates
// a group list of snapshots.
// a grouped list of snapshots.
func GroupSnapshots(snapshots Snapshots, groupBy SnapshotGroupByOptions) (map[string]Snapshots, bool, error) {
// group by hostname and dirs
snapshotGroups := make(map[string]Snapshots)

View File

@@ -31,23 +31,22 @@ 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))
}
if e.Hourly > 0 {
keeps = append(keeps, fmt.Sprintf("%d hourly", e.Hourly))
}
if e.Daily > 0 {
keeps = append(keeps, fmt.Sprintf("%d daily", e.Daily))
}
if e.Weekly > 0 {
keeps = append(keeps, fmt.Sprintf("%d weekly", e.Weekly))
}
if e.Monthly > 0 {
keeps = append(keeps, fmt.Sprintf("%d monthly", e.Monthly))
}
if e.Yearly > 0 {
keeps = append(keeps, fmt.Sprintf("%d yearly", e.Yearly))
for _, opt := range []struct {
count int
descr string
}{
{e.Last, "latest"},
{e.Hourly, "hourly"},
{e.Daily, "daily"},
{e.Weekly, "weekly"},
{e.Monthly, "monthly"},
{e.Yearly, "yearly"},
} {
if opt.count > 0 {
keeps = append(keeps, fmt.Sprintf("%d %s", opt.count, opt.descr))
} else if opt.count == -1 {
keeps = append(keeps, fmt.Sprintf("all %s", opt.descr))
}
}
if !e.WithinHourly.Zero() {
@@ -100,13 +99,7 @@ func (e ExpirePolicy) String() (s string) {
return s
}
// Sum returns the maximum number of snapshots to be kept according to this
// policy.
func (e ExpirePolicy) Sum() int {
return e.Last + e.Hourly + e.Daily + e.Weekly + e.Monthly + e.Yearly
}
// Empty returns true iff no policy has been configured (all values zero).
// Empty returns true if no policy has been configured (all values zero).
func (e ExpirePolicy) Empty() bool {
if len(e.Tags) != 0 {
return false
@@ -260,13 +253,16 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason
// Now update the other buckets and see if they have some counts left.
for i, b := range buckets {
if b.Count > 0 {
// -1 means "keep all"
if b.Count > 0 || b.Count == -1 {
val := b.bucker(cur.Time, nr)
if val != b.Last {
debug.Log("keep %v %v, bucker %v, val %v\n", cur.Time, cur.id.Str(), i, val)
keepSnap = true
buckets[i].Last = val
buckets[i].Count--
if buckets[i].Count > 0 {
buckets[i].Count--
}
keepSnapReasons = append(keepSnapReasons, b.reason)
}
}

View File

@@ -22,13 +22,14 @@ func parseTimeUTC(s string) time.Time {
return t.UTC()
}
func parseDuration(s string) restic.Duration {
d, err := restic.ParseDuration(s)
if err != nil {
panic(err)
// Returns the maximum number of snapshots to be kept according to this policy.
// If any of the counts is -1 it will return 0.
func policySum(e *restic.ExpirePolicy) int {
if e.Last == -1 || e.Hourly == -1 || e.Daily == -1 || e.Weekly == -1 || e.Monthly == -1 || e.Yearly == -1 {
return 0
}
return d
return e.Last + e.Hourly + e.Daily + e.Weekly + e.Monthly + e.Yearly
}
func TestExpireSnapshotOps(t *testing.T) {
@@ -46,7 +47,7 @@ func TestExpireSnapshotOps(t *testing.T) {
if isEmpty != d.expectEmpty {
t.Errorf("empty test %v: wrong result, want:\n %#v\ngot:\n %#v", i, d.expectEmpty, isEmpty)
}
hasSum := d.p.Sum()
hasSum := policySum(d.p)
if hasSum != d.expectSum {
t.Errorf("sum test %v: wrong result, want:\n %#v\ngot:\n %#v", i, d.expectSum, hasSum)
}
@@ -219,26 +220,30 @@ func TestApplyPolicy(t *testing.T) {
{Tags: []restic.TagList{{"foo"}}},
{Tags: []restic.TagList{{"foo", "bar"}}},
{Tags: []restic.TagList{{"foo"}, {"bar"}}},
{Within: parseDuration("1d")},
{Within: parseDuration("2d")},
{Within: parseDuration("7d")},
{Within: parseDuration("1m")},
{Within: parseDuration("1m14d")},
{Within: parseDuration("1y1d1m")},
{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")},
{Within: restic.ParseDurationOrPanic("1d")},
{Within: restic.ParseDurationOrPanic("2d")},
{Within: restic.ParseDurationOrPanic("7d")},
{Within: restic.ParseDurationOrPanic("1m")},
{Within: restic.ParseDurationOrPanic("1m14d")},
{Within: restic.ParseDurationOrPanic("1y1d1m")},
{Within: restic.ParseDurationOrPanic("13d23h")},
{Within: restic.ParseDurationOrPanic("2m2h")},
{Within: restic.ParseDurationOrPanic("1y2m3d3h")},
{WithinHourly: restic.ParseDurationOrPanic("1y2m3d3h")},
{WithinDaily: restic.ParseDurationOrPanic("1y2m3d3h")},
{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d3h")},
{WithinMonthly: restic.ParseDurationOrPanic("1y2m3d3h")},
{WithinYearly: restic.ParseDurationOrPanic("1y2m3d3h")},
{Within: restic.ParseDurationOrPanic("1h"),
WithinHourly: restic.ParseDurationOrPanic("1d"),
WithinDaily: restic.ParseDurationOrPanic("7d"),
WithinWeekly: restic.ParseDurationOrPanic("1m"),
WithinMonthly: restic.ParseDurationOrPanic("1y"),
WithinYearly: restic.ParseDurationOrPanic("9999y")},
{Last: -1}, // keep all
{Last: -1, Hourly: -1}, // keep all (Last overrides Hourly)
{Hourly: -1}, // keep all hourlies
{Daily: 3, Weekly: 2, Monthly: -1, Yearly: -1},
}
for i, p := range tests {
@@ -251,9 +256,9 @@ func TestApplyPolicy(t *testing.T) {
len(keep)+len(remove), len(testExpireSnapshots))
}
if p.Sum() > 0 && len(keep) > p.Sum() {
if policySum(&p) > 0 && len(keep) > policySum(&p) {
t.Errorf("not enough snapshots removed: policy allows %v snapshots to remain, but ended up with %v",
p.Sum(), len(keep))
policySum(&p), len(keep))
}
if len(keep) != len(reasons) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
{
"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": "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
},
{
"time": "2014-10-22T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-09-22T10:20:30Z",
"tree": null,
"paths": null
},
{
"time": "2014-08-22T10:20:30Z",
"tree": null,
"paths": null
}
],
"reasons": [
{
"snapshot": {
"time": "2016-01-18T12:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"daily snapshot",
"weekly snapshot",
"monthly snapshot",
"yearly snapshot"
],
"counters": {"Daily": 2, "Weekly": 1, "Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2016-01-12T21:08:03Z",
"tree": null,
"paths": null
},
"matches": [
"daily snapshot",
"weekly snapshot"
],
"counters": {"Daily": 1, "Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": null
},
"matches": [
"daily snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2015-11-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot",
"yearly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2015-10-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2015-09-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2015-08-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2014-11-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot",
"yearly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2014-10-22T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2014-09-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
},
{
"snapshot": {
"time": "2014-08-22T10:20:30Z",
"tree": null,
"paths": null
},
"matches": [
"monthly snapshot"
],
"counters": {"Monthly": -1, "Yearly": -1}
}
]
}

View File

@@ -212,3 +212,14 @@ func TestParseHandle(s string, t BlobType) BlobHandle {
func TestSetSnapshotID(t testing.TB, sn *Snapshot, id ID) {
sn.id = &id
}
// ParseDurationOrPanic parses a duration from a string or panics if string is invalid.
// The format is `6y5m234d37h`.
func ParseDurationOrPanic(s string) Duration {
d, err := ParseDuration(s)
if err != nil {
panic(err)
}
return d
}