mirror of
https://github.com/restic/restic.git
synced 2025-08-23 11:27:54 +00:00
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:
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
1782
internal/restic/testdata/policy_keep_snapshots_36
vendored
Normal file
1782
internal/restic/testdata/policy_keep_snapshots_36
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1872
internal/restic/testdata/policy_keep_snapshots_37
vendored
Normal file
1872
internal/restic/testdata/policy_keep_snapshots_37
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1538
internal/restic/testdata/policy_keep_snapshots_38
vendored
Normal file
1538
internal/restic/testdata/policy_keep_snapshots_38
vendored
Normal file
File diff suppressed because it is too large
Load Diff
194
internal/restic/testdata/policy_keep_snapshots_39
vendored
Normal file
194
internal/restic/testdata/policy_keep_snapshots_39
vendored
Normal 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}
|
||||
}
|
||||
]
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user