mirror of
https://github.com/restic/restic.git
synced 2025-03-09 00:00:02 +01:00
Merge 238d65ec3f
into de9a040d27
This commit is contained in:
commit
67570dffe4
9 changed files with 121 additions and 4 deletions
10
changelog/unreleased/issue-5136
Normal file
10
changelog/unreleased/issue-5136
Normal file
|
@ -0,0 +1,10 @@
|
|||
Enhancement: Add snapshot filtering options --older-than and --newer-than
|
||||
|
||||
Restic subcommands which work on a set of snapshots (``copy``,
|
||||
``find``, ``forget``, ``snapshots``, ``tag`` and more) can now filter
|
||||
on the absolute age of the snapshots specified as a duration, e.g.,
|
||||
``--newer-than 1d12h`` to only operate on snapshots created less than
|
||||
36 hours ago, or ``--older-than 90d`` to operate on snapshots older
|
||||
than three months.
|
||||
|
||||
https://github.com/restic/restic/issues/5136
|
|
@ -17,6 +17,8 @@ func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter,
|
|||
}
|
||||
flags.StringArrayVarP(&filt.Hosts, "host", hostShorthand, nil, "only consider snapshots for this `host` (can be specified multiple times) (default: $RESTIC_HOST)")
|
||||
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]` (can be specified multiple times)")
|
||||
flags.Var(&filt.OlderThan, "older-than", "only consider snapshots made more than `duration` time ago")
|
||||
flags.Var(&filt.NewerThan, "newer-than", "only consider snapshots made less than `duration` time ago")
|
||||
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path` (can be specified multiple times, snapshots must include all specified paths)")
|
||||
|
||||
// set default based on env if set
|
||||
|
|
|
@ -34,6 +34,9 @@ size of the contained files at the time when the snapshot was created.
|
|||
590c8fc8 2015-05-08 21:47:38 kazik /srv 580.200MiB
|
||||
9f0bc19e 2015-05-08 21:46:11 luigi /srv 572.180MiB
|
||||
|
||||
If you have many snapshots, you can restrict it to only snapshots made
|
||||
the last week, using ``-newer-than 7d``.
|
||||
|
||||
You can filter the listing by directory path:
|
||||
|
||||
.. code-block:: console
|
||||
|
|
|
@ -353,7 +353,9 @@ Since restic 0.17.0, it is possible to delete all snapshots for a specific
|
|||
host, tag or path using the ``--unsafe-allow-remove-all`` option. The option
|
||||
must always be combined with a snapshot filter (by host, path or tag).
|
||||
For example the command ``forget --tag example --unsafe-allow-remove-all``
|
||||
removes all snapshots with tag ``example``.
|
||||
removes all snapshots with tag ``example``. Since restic 0.17.4, this
|
||||
can be combined with an option like ``--older-than 1y`` to implement
|
||||
a hard maximum retention for snapshots.
|
||||
|
||||
|
||||
Security considerations in append-only mode
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
@ -126,6 +127,18 @@ func (d *Duration) Set(s string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PastTime returns past point in time where this duration is relative to time t. If t is not given, use time.Now().
|
||||
func (d Duration) PastTime(t ...time.Time) time.Time {
|
||||
var reftime time.Time
|
||||
if t == nil {
|
||||
reftime = time.Now()
|
||||
} else {
|
||||
reftime = t[0]
|
||||
}
|
||||
|
||||
return reftime.AddDate(-d.Years, -d.Months, -d.Days).Add(-time.Duration(d.Hours) * time.Hour)
|
||||
}
|
||||
|
||||
// Type returns the type of Duration, usable within github.com/spf13/pflag and
|
||||
// in help texts.
|
||||
func (d Duration) Type() string {
|
||||
|
|
|
@ -2,6 +2,7 @@ package restic
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
@ -104,3 +105,18 @@ func TestParseDuration(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPastTime(t *testing.T) {
|
||||
t.Run("", func(t *testing.T) {
|
||||
d, err := ParseDuration("1y2m3d4h")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reftime, _ := time.Parse(time.DateTime, "1999-12-30 15:16:17")
|
||||
expected := "1998-10-27 11:16:17"
|
||||
result := d.PastTime(reftime).Format(time.DateTime)
|
||||
if result != expected {
|
||||
t.Errorf("unexpected return of PastTime, wanted %q, got %q", expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -248,6 +248,22 @@ func (sn *Snapshot) HasHostname(hostnames []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// HasTimeBetween returns false if either
|
||||
// - start is given and Time is before start, or
|
||||
// - stop is given and Time is after stop
|
||||
// Otherwise return true
|
||||
// start is the value closest in time, ie. the shortest duration value.
|
||||
func (sn *Snapshot) HasTimeBetween(start Duration, stop Duration) bool {
|
||||
if !start.Zero() && sn.Time.After(start.PastTime()) {
|
||||
return false
|
||||
}
|
||||
if !stop.Zero() && sn.Time.Before(stop.PastTime()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Snapshots is a list of snapshots.
|
||||
type Snapshots []*Snapshot
|
||||
|
||||
|
|
|
@ -20,16 +20,20 @@ type SnapshotFilter struct {
|
|||
Hosts []string
|
||||
Tags TagLists
|
||||
Paths []string
|
||||
// Match snapshots from before this timestamp. Zero for no limit.
|
||||
// Match snapshots from before this timestamp. Zero for no limit. Only used to find parent.
|
||||
TimestampLimit time.Time
|
||||
// Match snapshots from before this timestamp. Zero for no limit.
|
||||
NewerThan Duration
|
||||
// Match snapshots from after this timestamp. Zero for no limit.
|
||||
OlderThan Duration
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) Empty() bool {
|
||||
return len(f.Hosts)+len(f.Tags)+len(f.Paths) == 0
|
||||
return len(f.Hosts)+len(f.Tags)+len(f.Paths) == 0 && f.NewerThan.Zero() && f.OlderThan.Zero()
|
||||
}
|
||||
|
||||
func (f *SnapshotFilter) matches(sn *Snapshot) bool {
|
||||
return sn.HasHostname(f.Hosts) && sn.HasTagList(f.Tags) && sn.HasPaths(f.Paths)
|
||||
return sn.HasHostname(f.Hosts) && sn.HasTagList(f.Tags) && sn.HasPaths(f.Paths) && sn.HasTimeBetween(f.OlderThan, f.NewerThan)
|
||||
}
|
||||
|
||||
// findLatest finds the latest snapshot with optional target/directory,
|
||||
|
|
|
@ -3,6 +3,7 @@ package restic_test
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
@ -89,3 +90,53 @@ func TestFindAllSubpathError(t *testing.T) {
|
|||
}))
|
||||
test.Assert(t, count == 2, "unexpected number of subfolder errors: %v, wanted %v", count, 2)
|
||||
}
|
||||
|
||||
func TestFindAllNewerThan(t *testing.T) {
|
||||
repo := repository.TestRepository(t)
|
||||
now := time.Now()
|
||||
|
||||
oneDay := time.Duration(24) * time.Hour
|
||||
restic.TestCreateSnapshot(t, repo, now.Add(-14*oneDay), 1)
|
||||
desiredSnapshot := restic.TestCreateSnapshot(t, repo, now.Add(-4*oneDay), 1)
|
||||
|
||||
var found restic.Snapshot
|
||||
count := 0
|
||||
test.OK(t, (&restic.SnapshotFilter{
|
||||
NewerThan: restic.Duration{Days: 5},
|
||||
}).FindAll(context.TODO(), repo, repo, nil,
|
||||
func(id string, sn *restic.Snapshot, err error) error {
|
||||
if err == nil {
|
||||
found = *sn
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
test.Assert(t, count == 1, "unexpected number of snapshots: %v, wanted %v", count, 1)
|
||||
test.Assert(t, desiredSnapshot.ID().Equal(*found.ID()), "unexpected snapshot found: %s, wanted %s", desiredSnapshot, found)
|
||||
}
|
||||
|
||||
func TestFindAllWithin(t *testing.T) {
|
||||
repo := repository.TestRepository(t)
|
||||
now := time.Now()
|
||||
|
||||
oneDay := time.Duration(24) * time.Hour
|
||||
restic.TestCreateSnapshot(t, repo, now.Add(-14*oneDay), 1)
|
||||
restic.TestCreateSnapshot(t, repo, now.Add(-1*oneDay), 1)
|
||||
desiredSnapshot := restic.TestCreateSnapshot(t, repo, now.Add(-4*oneDay), 1)
|
||||
|
||||
var found restic.Snapshot
|
||||
count := 0
|
||||
test.OK(t, (&restic.SnapshotFilter{
|
||||
NewerThan: restic.Duration{Days: 5},
|
||||
OlderThan: restic.Duration{Days: 2},
|
||||
}).FindAll(context.TODO(), repo, repo, nil,
|
||||
func(id string, sn *restic.Snapshot, err error) error {
|
||||
if err == nil {
|
||||
found = *sn
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
test.Assert(t, count == 1, "unexpected number of snapshots: %v, wanted %v", count, 1)
|
||||
test.Assert(t, desiredSnapshot.ID().Equal(*found.ID()), "unexpected snapshot found: %s, wanted %s", desiredSnapshot, found)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue