Merge pull request #1876 from restic/forget-explain
forget: Add --explain
This commit is contained in:
commit
de307ea2ab
45 changed files with 12275 additions and 3579 deletions
4
Gopkg.lock
generated
4
Gopkg.lock
generated
|
@ -104,10 +104,11 @@
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:2e3c336fc7fde5c984d2841455a658a6d626450b1754a854b3b32e7a8f49a07a"
|
digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0"
|
||||||
name = "github.com/google/go-cmp"
|
name = "github.com/google/go-cmp"
|
||||||
packages = [
|
packages = [
|
||||||
"cmp",
|
"cmp",
|
||||||
|
"cmp/cmpopts",
|
||||||
"cmp/internal/diff",
|
"cmp/internal/diff",
|
||||||
"cmp/internal/function",
|
"cmp/internal/function",
|
||||||
"cmp/internal/value",
|
"cmp/internal/value",
|
||||||
|
@ -449,6 +450,7 @@
|
||||||
"github.com/cenkalti/backoff",
|
"github.com/cenkalti/backoff",
|
||||||
"github.com/elithrar/simple-scrypt",
|
"github.com/elithrar/simple-scrypt",
|
||||||
"github.com/google/go-cmp/cmp",
|
"github.com/google/go-cmp/cmp",
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts",
|
||||||
"github.com/juju/ratelimit",
|
"github.com/juju/ratelimit",
|
||||||
"github.com/kurin/blazer/b2",
|
"github.com/kurin/blazer/b2",
|
||||||
"github.com/mattn/go-isatty",
|
"github.com/mattn/go-isatty",
|
||||||
|
|
7
changelog/unreleased/pull-1876
Normal file
7
changelog/unreleased/pull-1876
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Enhancement: Display reason why forget keeps snapshots
|
||||||
|
|
||||||
|
We've added a column to the list of snapshots `forget` keeps which details the
|
||||||
|
reasons to keep a particuliar snapshot. This makes debugging policies for
|
||||||
|
forget much easier. Please remember to always try things out with `--dry-run`!
|
||||||
|
|
||||||
|
https://github.com/restic/restic/pull/1876
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/restic/restic/internal/cache"
|
"github.com/restic/restic/internal/cache"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
"github.com/restic/restic/internal/ui/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,9 +86,17 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tab := NewTable()
|
tab := table.New()
|
||||||
tab.Header = fmt.Sprintf("%-14s %-16s %s", "Repository ID", "Last Used", "Old")
|
|
||||||
tab.RowFormat = "%-14s %-16s %s"
|
type data struct {
|
||||||
|
ID string
|
||||||
|
Last string
|
||||||
|
Old string
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.AddColumn("Repo ID", "{{ .ID }}")
|
||||||
|
tab.AddColumn("Last Used", "{{ .Last }}")
|
||||||
|
tab.AddColumn("Old", "{{ .Old }}")
|
||||||
|
|
||||||
dirs, err := cache.All(cachedir)
|
dirs, err := cache.All(cachedir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -109,7 +118,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||||
old = "yes"
|
old = "yes"
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.Rows = append(tab.Rows, []interface{}{
|
tab.AddRow(data{
|
||||||
entry.Name()[:10],
|
entry.Name()[:10],
|
||||||
fmt.Sprintf("%d days ago", uint(time.Since(entry.ModTime()).Hours()/24)),
|
fmt.Sprintf("%d days ago", uint(time.Since(entry.ModTime()).Hours()/24)),
|
||||||
old,
|
old,
|
||||||
|
|
|
@ -206,17 +206,17 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||||
}
|
}
|
||||||
Verbosef(":\n\n")
|
Verbosef(":\n\n")
|
||||||
|
|
||||||
keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
|
keep, remove, reasons := restic.ApplyPolicy(snapshotGroup, policy)
|
||||||
|
|
||||||
if len(keep) != 0 && !gopts.Quiet {
|
if len(keep) != 0 && !gopts.Quiet {
|
||||||
Printf("keep %d snapshots:\n", len(keep))
|
Printf("keep %d snapshots:\n", len(keep))
|
||||||
PrintSnapshots(globalOptions.stdout, keep, opts.Compact)
|
PrintSnapshots(globalOptions.stdout, keep, reasons, opts.Compact)
|
||||||
Printf("\n")
|
Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remove) != 0 && !gopts.Quiet {
|
if len(remove) != 0 && !gopts.Quiet {
|
||||||
Printf("remove %d snapshots:\n", len(remove))
|
Printf("remove %d snapshots:\n", len(remove))
|
||||||
PrintSnapshots(globalOptions.stdout, remove, opts.Compact)
|
PrintSnapshots(globalOptions.stdout, remove, nil, opts.Compact)
|
||||||
Printf("\n")
|
Printf("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,6 +10,7 @@ import (
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui/table"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -36,6 +36,7 @@ func init() {
|
||||||
flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "the file from which to load a new password")
|
flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "the file from which to load a new password")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error {
|
||||||
type keyInfo struct {
|
type keyInfo struct {
|
||||||
Current bool `json:"current"`
|
Current bool `json:"current"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -44,45 +45,9 @@ type keyInfo struct {
|
||||||
Created string `json:"created"`
|
Created string `json:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ki keyInfo) CurrentStr() string {
|
|
||||||
if ki.Current {
|
|
||||||
return "*"
|
|
||||||
}
|
|
||||||
return " "
|
|
||||||
}
|
|
||||||
|
|
||||||
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error {
|
|
||||||
var (
|
|
||||||
appendKey func(keyInfo)
|
|
||||||
printKeys func() error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch gopts.JSON {
|
|
||||||
case true:
|
|
||||||
var keys []keyInfo
|
var keys []keyInfo
|
||||||
|
|
||||||
appendKey = func(key keyInfo) {
|
err := s.List(ctx, restic.KeyFile, func(id restic.ID, size int64) error {
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
printKeys = func() error {
|
|
||||||
return json.NewEncoder(gopts.stdout).Encode(keys)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
tab := NewTable()
|
|
||||||
tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created")
|
|
||||||
tab.RowFormat = "%s%-10s %-10s %-10s %s"
|
|
||||||
|
|
||||||
appendKey = func(key keyInfo) {
|
|
||||||
tab.Rows = append(tab.Rows, []interface{}{key.CurrentStr(), key.ID, key.UserName, key.HostName, key.Created})
|
|
||||||
}
|
|
||||||
|
|
||||||
printKeys = func() error {
|
|
||||||
return tab.Write(globalOptions.stdout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.List(ctx, restic.KeyFile, func(id restic.ID, size int64) error {
|
|
||||||
k, err := repository.LoadKey(ctx, s, id.String())
|
k, err := repository.LoadKey(ctx, s, id.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("LoadKey() failed: %v\n", err)
|
Warnf("LoadKey() failed: %v\n", err)
|
||||||
|
@ -97,14 +62,29 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||||
Created: k.Created.Format(TimeFormat),
|
Created: k.Created.Format(TimeFormat),
|
||||||
}
|
}
|
||||||
|
|
||||||
appendKey(key)
|
keys = append(keys, key)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return printKeys()
|
if gopts.JSON {
|
||||||
|
return json.NewEncoder(globalOptions.stdout).Encode(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := table.New()
|
||||||
|
tab.AddColumn(" ID", "{{if .Current}}*{{else}} {{end}}{{ .ID }}")
|
||||||
|
tab.AddColumn("User", "{{ .UserName }}")
|
||||||
|
tab.AddColumn("Host", "{{ .HostName }}")
|
||||||
|
tab.AddColumn("Created", "{{ .Created }}")
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
tab.AddRow(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tab.Write(globalOptions.stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testKeyNewPassword is used to set a new password during integration testing.
|
// testKeyNewPassword is used to set a new password during integration testing.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
PrintSnapshots(gopts.stdout, list, opts.Compact)
|
PrintSnapshots(gopts.stdout, list, nil, opts.Compact)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -123,7 +124,16 @@ func FilterLastSnapshots(list restic.Snapshots) restic.Snapshots {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintSnapshots prints a text table of the snapshots in list to stdout.
|
// PrintSnapshots prints a text table of the snapshots in list to stdout.
|
||||||
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) {
|
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.KeepReason, compact bool) {
|
||||||
|
// keep the reasons a snasphot is being kept in a map, so that it doesn't
|
||||||
|
// get lost when the list of snapshots is sorted
|
||||||
|
keepReasons := make(map[restic.ID]restic.KeepReason, len(reasons))
|
||||||
|
if len(reasons) > 0 {
|
||||||
|
for i, sn := range list {
|
||||||
|
id := sn.ID()
|
||||||
|
keepReasons[*id] = reasons[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// always sort the snapshots so that the newer ones are listed last
|
// always sort the snapshots so that the newer ones are listed last
|
||||||
sort.SliceStable(list, func(i, j int) bool {
|
sort.SliceStable(list, func(i, j int) bool {
|
||||||
|
@ -143,72 +153,73 @@ func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tab := NewTable()
|
tab := table.New()
|
||||||
if !compact {
|
|
||||||
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s %-3s %s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags", "", "Directory")
|
if compact {
|
||||||
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%%ds %%-3s %%s", -maxHost, -maxTag)
|
tab.AddColumn("ID", "{{ .ID }}")
|
||||||
|
tab.AddColumn("Time", "{{ .Timestamp }}")
|
||||||
|
tab.AddColumn("Host", "{{ .Hostname }}")
|
||||||
|
tab.AddColumn("Tags ", `{{ join .Tags "\n" }}`)
|
||||||
} else {
|
} else {
|
||||||
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags")
|
tab.AddColumn("ID", "{{ .ID }}")
|
||||||
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%s", -maxHost)
|
tab.AddColumn("Time", "{{ .Timestamp }}")
|
||||||
|
tab.AddColumn("Host ", "{{ .Hostname }}")
|
||||||
|
tab.AddColumn("Tags ", `{{ join .Tags "," }}`)
|
||||||
|
if len(reasons) > 0 {
|
||||||
|
tab.AddColumn("Reasons", `{{ join .Reasons "\n" }}`)
|
||||||
|
}
|
||||||
|
tab.AddColumn("Paths", `{{ join .Paths "\n" }}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snapshot struct {
|
||||||
|
ID string
|
||||||
|
Timestamp string
|
||||||
|
Hostname string
|
||||||
|
Tags []string
|
||||||
|
Reasons []string
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiline bool
|
||||||
for _, sn := range list {
|
for _, sn := range list {
|
||||||
if len(sn.Paths) == 0 {
|
data := snapshot{
|
||||||
continue
|
ID: sn.ID().Str(),
|
||||||
|
Timestamp: sn.Time.Format(TimeFormat),
|
||||||
|
Hostname: sn.Hostname,
|
||||||
|
Tags: sn.Tags,
|
||||||
|
Paths: sn.Paths,
|
||||||
}
|
}
|
||||||
|
|
||||||
firstTag := ""
|
if len(reasons) > 0 {
|
||||||
if len(sn.Tags) > 0 {
|
id := sn.ID()
|
||||||
firstTag = sn.Tags[0]
|
data.Reasons = keepReasons[*id].Matches
|
||||||
}
|
}
|
||||||
|
|
||||||
rows := len(sn.Paths)
|
if len(sn.Paths) > 1 {
|
||||||
if rows < len(sn.Tags) {
|
multiline = true
|
||||||
rows = len(sn.Tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
treeElement := " "
|
tab.AddRow(data)
|
||||||
if rows != 1 {
|
|
||||||
treeElement = "┌──"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !compact {
|
tab.AddFooter(fmt.Sprintf("%d snapshots", len(list)))
|
||||||
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, treeElement, sn.Paths[0]})
|
|
||||||
|
if multiline {
|
||||||
|
// print an additional blank line between snapshots
|
||||||
|
|
||||||
|
var last int
|
||||||
|
tab.PrintData = func(w io.Writer, idx int, s string) error {
|
||||||
|
var err error
|
||||||
|
if idx == last {
|
||||||
|
_, err = fmt.Fprintf(w, "%s\n", s)
|
||||||
} else {
|
} else {
|
||||||
allTags := ""
|
_, err = fmt.Fprintf(w, "\n%s\n", s)
|
||||||
for _, tag := range sn.Tags {
|
|
||||||
allTags += tag + " "
|
|
||||||
}
|
}
|
||||||
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, allTags})
|
last = idx
|
||||||
continue
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
if len(sn.Tags) > rows {
|
|
||||||
rows = len(sn.Tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i < rows; i++ {
|
|
||||||
path := ""
|
|
||||||
if len(sn.Paths) > i {
|
|
||||||
path = sn.Paths[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
tag := ""
|
|
||||||
if len(sn.Tags) > i {
|
|
||||||
tag = sn.Tags[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
treeElement := "│"
|
|
||||||
if i == (rows - 1) {
|
|
||||||
treeElement = "└──"
|
|
||||||
}
|
|
||||||
|
|
||||||
tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, treeElement, path})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tab.Footer = fmt.Sprintf("%d snapshots", len(list))
|
|
||||||
|
|
||||||
tab.Write(stdout)
|
tab.Write(stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,9 @@ import (
|
||||||
|
|
||||||
var version = "0.9.2-dev (compiled manually)"
|
var version = "0.9.2-dev (compiled manually)"
|
||||||
|
|
||||||
|
// TimeFormat is the format used for all timestamps printed by restic.
|
||||||
|
const TimeFormat = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
// GlobalOptions hold all global options for restic.
|
// GlobalOptions hold all global options for restic.
|
||||||
type GlobalOptions struct {
|
type GlobalOptions struct {
|
||||||
Repo string
|
Repo string
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table contains data for a table to be printed.
|
|
||||||
type Table struct {
|
|
||||||
Header string
|
|
||||||
Rows [][]interface{}
|
|
||||||
Footer string
|
|
||||||
|
|
||||||
RowFormat string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTable initializes a new Table.
|
|
||||||
func NewTable() Table {
|
|
||||||
return Table{
|
|
||||||
Rows: [][]interface{}{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Table) printSeparationLine(w io.Writer) error {
|
|
||||||
_, err := fmt.Fprintln(w, strings.Repeat("-", 70))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write prints the table to w.
|
|
||||||
func (t Table) Write(w io.Writer) error {
|
|
||||||
_, err := fmt.Fprintln(w, t.Header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.printSeparationLine(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, row := range t.Rows {
|
|
||||||
_, err = fmt.Fprintf(w, t.RowFormat+"\n", row...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.printSeparationLine(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintln(w, t.Footer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TimeFormat is the format used for all timestamps printed by restic.
|
|
||||||
const TimeFormat = "2006-01-02 15:04:05"
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExpirePolicy configures which snapshots should be automatically removed.
|
// ExpirePolicy configures which snapshots should be automatically removed.
|
||||||
|
@ -125,41 +127,70 @@ func findLatestTimestamp(list Snapshots) time.Time {
|
||||||
return latest
|
return latest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeepReason specifies why a particular snapshot was kept, and the counters at
|
||||||
|
// that point in the policy evaluation.
|
||||||
|
type KeepReason struct {
|
||||||
|
Snapshot *Snapshot `json:"snapshot"`
|
||||||
|
|
||||||
|
// description text which criteria match, e.g. "daily", "monthly"
|
||||||
|
Matches []string `json:"matches"`
|
||||||
|
|
||||||
|
// the counters after evaluating the current snapshot
|
||||||
|
Counters struct {
|
||||||
|
Last int `json:"last,omitempty"`
|
||||||
|
Hourly int `json:"hourly,omitempty"`
|
||||||
|
Daily int `json:"daily,omitempty"`
|
||||||
|
Weekly int `json:"weekly,omitempty"`
|
||||||
|
Monthly int `json:"monthly,omitempty"`
|
||||||
|
Yearly int `json:"yearly,omitempty"`
|
||||||
|
} `json:"counters"`
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyPolicy returns the snapshots from list that are to be kept and removed
|
// ApplyPolicy returns the snapshots from list that are to be kept and removed
|
||||||
// according to the policy p. list is sorted in the process.
|
// according to the policy p. list is sorted in the process. reasons contains
|
||||||
func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
|
// the reasons to keep each snapshot, it is in the same order as keep.
|
||||||
|
func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reasons []KeepReason) {
|
||||||
sort.Sort(list)
|
sort.Sort(list)
|
||||||
|
|
||||||
if p.Empty() {
|
if p.Empty() {
|
||||||
return list, remove
|
for _, sn := range list {
|
||||||
|
reasons = append(reasons, KeepReason{
|
||||||
|
Snapshot: sn,
|
||||||
|
Matches: []string{"policy is empty"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list, remove, reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return list, remove
|
return list, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var buckets = [6]struct {
|
var buckets = [6]struct {
|
||||||
Count int
|
Count int
|
||||||
bucker func(d time.Time, nr int) int
|
bucker func(d time.Time, nr int) int
|
||||||
Last int
|
Last int
|
||||||
|
reason string
|
||||||
}{
|
}{
|
||||||
{p.Last, always, -1},
|
{p.Last, always, -1, "last snapshot"},
|
||||||
{p.Hourly, ymdh, -1},
|
{p.Hourly, ymdh, -1, "hourly snapshot"},
|
||||||
{p.Daily, ymd, -1},
|
{p.Daily, ymd, -1, "daily snapshot"},
|
||||||
{p.Weekly, yw, -1},
|
{p.Weekly, yw, -1, "weekly snapshot"},
|
||||||
{p.Monthly, ym, -1},
|
{p.Monthly, ym, -1, "monthly snapshot"},
|
||||||
{p.Yearly, y, -1},
|
{p.Yearly, y, -1, "yearly snapshot"},
|
||||||
}
|
}
|
||||||
|
|
||||||
latest := findLatestTimestamp(list)
|
latest := findLatestTimestamp(list)
|
||||||
|
|
||||||
for nr, cur := range list {
|
for nr, cur := range list {
|
||||||
var keepSnap bool
|
var keepSnap bool
|
||||||
|
var keepSnapReasons []string
|
||||||
|
|
||||||
// Tags are handled specially as they are not counted.
|
// Tags are handled specially as they are not counted.
|
||||||
for _, l := range p.Tags {
|
for _, l := range p.Tags {
|
||||||
if cur.HasTags(l) {
|
if cur.HasTags(l) {
|
||||||
keepSnap = true
|
keepSnap = true
|
||||||
|
keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("has tags %v", l))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +199,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
|
||||||
t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days)
|
t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days)
|
||||||
if cur.Time.After(t) {
|
if cur.Time.After(t) {
|
||||||
keepSnap = true
|
keepSnap = true
|
||||||
|
keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("within %v", p.Within))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,19 +208,32 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
|
||||||
if b.Count > 0 {
|
if b.Count > 0 {
|
||||||
val := b.bucker(cur.Time, nr)
|
val := b.bucker(cur.Time, nr)
|
||||||
if val != b.Last {
|
if val != b.Last {
|
||||||
|
debug.Log("keep %v %v, bucker %v, val %v\n", cur.Time, cur.id.Str(), i, val)
|
||||||
keepSnap = true
|
keepSnap = true
|
||||||
buckets[i].Last = val
|
buckets[i].Last = val
|
||||||
buckets[i].Count--
|
buckets[i].Count--
|
||||||
|
keepSnapReasons = append(keepSnapReasons, b.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if keepSnap {
|
if keepSnap {
|
||||||
keep = append(keep, cur)
|
keep = append(keep, cur)
|
||||||
|
kr := KeepReason{
|
||||||
|
Snapshot: cur,
|
||||||
|
Matches: keepSnapReasons,
|
||||||
|
}
|
||||||
|
kr.Counters.Last = buckets[0].Count
|
||||||
|
kr.Counters.Hourly = buckets[1].Count
|
||||||
|
kr.Counters.Daily = buckets[2].Count
|
||||||
|
kr.Counters.Weekly = buckets[3].Count
|
||||||
|
kr.Counters.Monthly = buckets[4].Count
|
||||||
|
kr.Counters.Yearly = buckets[5].Count
|
||||||
|
reasons = append(reasons, kr)
|
||||||
} else {
|
} else {
|
||||||
remove = append(remove, cur)
|
remove = append(remove, cur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return keep, remove
|
return keep, remove, reasons
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,6 +53,43 @@ func TestExpireSnapshotOps(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyPolicyResult is used to marshal/unmarshal the golden files for
|
||||||
|
// TestApplyPolicy.
|
||||||
|
type ApplyPolicyResult struct {
|
||||||
|
Keep restic.Snapshots `json:"keep"`
|
||||||
|
Reasons []restic.KeepReason `json:"reasons,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGoldenFile(t testing.TB, filename string) (res ApplyPolicyResult) {
|
||||||
|
buf, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error loading golden file %v: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, &res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error unmarshalling golden file %v: %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveGoldenFile(t testing.TB, filename string, keep restic.Snapshots, reasons []restic.KeepReason) {
|
||||||
|
res := ApplyPolicyResult{
|
||||||
|
Keep: keep,
|
||||||
|
Reasons: reasons,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.MarshalIndent(res, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error marshaling result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(filename, buf, 0644); err != nil {
|
||||||
|
t.Fatalf("unable to update golden file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyPolicy(t *testing.T) {
|
func TestApplyPolicy(t *testing.T) {
|
||||||
var testExpireSnapshots = restic.Snapshots{
|
var testExpireSnapshots = restic.Snapshots{
|
||||||
{Time: parseTimeUTC("2014-09-01 10:20:30")},
|
{Time: parseTimeUTC("2014-09-01 10:20:30")},
|
||||||
|
@ -191,10 +229,8 @@ func TestApplyPolicy(t *testing.T) {
|
||||||
|
|
||||||
for i, p := range tests {
|
for i, p := range tests {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
keep, remove := restic.ApplyPolicy(testExpireSnapshots, p)
|
|
||||||
|
|
||||||
t.Logf("returned keep %v, remove %v (of %v) expired snapshots for policy %v",
|
keep, remove, reasons := restic.ApplyPolicy(testExpireSnapshots, p)
|
||||||
len(keep), len(remove), len(testExpireSnapshots), p)
|
|
||||||
|
|
||||||
if len(keep)+len(remove) != len(testExpireSnapshots) {
|
if len(keep)+len(remove) != len(testExpireSnapshots) {
|
||||||
t.Errorf("len(keep)+len(remove) = %d != len(testExpireSnapshots) = %d",
|
t.Errorf("len(keep)+len(remove) = %d != len(testExpireSnapshots) = %d",
|
||||||
|
@ -206,39 +242,26 @@ func TestApplyPolicy(t *testing.T) {
|
||||||
p.Sum(), len(keep))
|
p.Sum(), len(keep))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sn := range keep {
|
if len(keep) != len(reasons) {
|
||||||
t.Logf(" keep snapshot at %v %s", sn.Time, sn.Tags)
|
t.Errorf("got %d keep reasons for %d snapshots to keep, these must be equal", len(reasons), len(keep))
|
||||||
}
|
|
||||||
for _, sn := range remove {
|
|
||||||
t.Logf(" forget snapshot at %v %s", sn.Time, sn.Tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goldenFilename := filepath.Join("testdata", fmt.Sprintf("policy_keep_snapshots_%d", i))
|
goldenFilename := filepath.Join("testdata", fmt.Sprintf("policy_keep_snapshots_%d", i))
|
||||||
|
|
||||||
if *updateGoldenFiles {
|
if *updateGoldenFiles {
|
||||||
buf, err := json.MarshalIndent(keep, "", " ")
|
saveGoldenFile(t, goldenFilename, keep, reasons)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error marshaling result: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ioutil.WriteFile(goldenFilename, buf, 0644); err != nil {
|
want := loadGoldenFile(t, goldenFilename)
|
||||||
t.Fatalf("unable to update golden file: %v", err)
|
|
||||||
}
|
cmpOpts := cmpopts.IgnoreUnexported(restic.Snapshot{})
|
||||||
|
|
||||||
|
if !cmp.Equal(want.Keep, keep, cmpOpts) {
|
||||||
|
t.Error(cmp.Diff(want.Keep, keep, cmpOpts))
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(goldenFilename)
|
if !cmp.Equal(want.Reasons, reasons, cmpOpts) {
|
||||||
if err != nil {
|
t.Error(cmp.Diff(want.Reasons, reasons, cmpOpts))
|
||||||
t.Fatalf("error loading golden file %v: %v", goldenFilename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var want restic.Snapshots
|
|
||||||
err = json.Unmarshal(buf, &want)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error unmarshalling golden file %v: %v", goldenFilename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(keep, want) {
|
|
||||||
t.Fatalf("wrong result, want:\n %v\ngot:\n %v", want, keep)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
1203
internal/restic/testdata/policy_keep_snapshots_0
vendored
1203
internal/restic/testdata/policy_keep_snapshots_0
vendored
File diff suppressed because it is too large
Load diff
134
internal/restic/testdata/policy_keep_snapshots_1
vendored
134
internal/restic/testdata/policy_keep_snapshots_1
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -49,4 +50,135 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
137
internal/restic/testdata/policy_keep_snapshots_10
vendored
137
internal/restic/testdata/policy_keep_snapshots_10
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -49,4 +50,138 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 1,
|
||||||
|
"daily": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -9,4 +10,31 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"weekly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -19,4 +20,57 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"weekly": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"weekly": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"weekly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -19,4 +20,62 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2,
|
||||||
|
"weekly": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1,
|
||||||
|
"weekly": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"weekly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -29,4 +30,83 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
100
internal/restic/testdata/policy_keep_snapshots_15
vendored
100
internal/restic/testdata/policy_keep_snapshots_15
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -34,4 +35,101 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot",
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1,
|
||||||
|
"weekly": 1,
|
||||||
|
"monthly": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -14,4 +15,46 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"yearly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"yearly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"yearly": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"yearly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"yearly": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
156
internal/restic/testdata/policy_keep_snapshots_17
vendored
156
internal/restic/testdata/policy_keep_snapshots_17
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -49,4 +50,157 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": 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": 6,
|
||||||
|
"weekly": 1,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot",
|
||||||
|
"weekly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 5,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 4,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 3,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1,
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 2,
|
||||||
|
"yearly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot",
|
||||||
|
"yearly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"monthly": 1,
|
||||||
|
"yearly": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"monthly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"yearly": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"yearly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"yearly": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
265
internal/restic/testdata/policy_keep_snapshots_18
vendored
265
internal/restic/testdata/policy_keep_snapshots_18
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2015-10-22T10:20:30Z",
|
"time": "2015-10-22T10:20:30Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -150,4 +151,266 @@
|
||||||
"foo"
|
"foo"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": [
|
||||||
|
"path1",
|
||||||
|
"path2"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-13T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-12T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-11T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-09T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-06T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-05T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-02T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-01T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2015-10-22T10:20:30Z",
|
"time": "2015-10-22T10:20:30Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -38,4 +39,70 @@
|
||||||
"bar"
|
"bar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": [
|
||||||
|
"path1",
|
||||||
|
"path2"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo, bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo, bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo, bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo, bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
199
internal/restic/testdata/policy_keep_snapshots_2
vendored
199
internal/restic/testdata/policy_keep_snapshots_2
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -74,4 +75,200 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:28:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:24:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T11:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T10:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
283
internal/restic/testdata/policy_keep_snapshots_20
vendored
283
internal/restic/testdata/policy_keep_snapshots_20
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2015-10-22T10:20:30Z",
|
"time": "2015-10-22T10:20:30Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -158,4 +159,284 @@
|
||||||
"foo"
|
"foo"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": [
|
||||||
|
"path1",
|
||||||
|
"path2"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]",
|
||||||
|
"has tags [bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]",
|
||||||
|
"has tags [bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]",
|
||||||
|
"has tags [bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]",
|
||||||
|
"has tags [bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [bar]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-13T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-12T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-11-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-11T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-09T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-06T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-05T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-02T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2014-10-01T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"has tags [foo]"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,7 +1,22 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,7 +1,22 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 2d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -14,4 +15,40 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 7d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 7d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 7d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
215
internal/restic/testdata/policy_keep_snapshots_24
vendored
215
internal/restic/testdata/policy_keep_snapshots_24
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -94,4 +95,216 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:28:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:24:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T11:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T10:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:03:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
215
internal/restic/testdata/policy_keep_snapshots_25
vendored
215
internal/restic/testdata/policy_keep_snapshots_25
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -94,4 +95,216 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:28:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:24:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T11:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T10:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:03:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1m14d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
714
internal/restic/testdata/policy_keep_snapshots_26
vendored
714
internal/restic/testdata/policy_keep_snapshots_26
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -329,4 +330,715 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:28:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:24:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T11:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T10:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:03:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-21T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-18T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-13T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-12T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": [
|
||||||
|
"path1",
|
||||||
|
"path2"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-11T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-09T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-06T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-05T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-02T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-01T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-11T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-09T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-06T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-05T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-02T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-01T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-21T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-18T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-13T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-12T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-08-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"within 1y1m1d"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1355
internal/restic/testdata/policy_keep_snapshots_3
vendored
1355
internal/restic/testdata/policy_keep_snapshots_3
vendored
File diff suppressed because it is too large
Load diff
1409
internal/restic/testdata/policy_keep_snapshots_4
vendored
1409
internal/restic/testdata/policy_keep_snapshots_4
vendored
File diff suppressed because it is too large
Load diff
264
internal/restic/testdata/policy_keep_snapshots_5
vendored
264
internal/restic/testdata/policy_keep_snapshots_5
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -99,4 +100,265 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 19
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T12:30:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T11:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T10:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T01:03:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-21T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-18T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"hourly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"hourly snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
43
internal/restic/testdata/policy_keep_snapshots_6
vendored
43
internal/restic/testdata/policy_keep_snapshots_6
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -14,4 +15,44 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
134
internal/restic/testdata/policy_keep_snapshots_7
vendored
134
internal/restic/testdata/policy_keep_snapshots_7
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -49,4 +50,135 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
394
internal/restic/testdata/policy_keep_snapshots_8
vendored
394
internal/restic/testdata/policy_keep_snapshots_8
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -149,4 +150,395 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 29
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 28
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 27
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 26
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-06T08:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-05T09:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 23
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-04T16:23:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 22
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-03T07:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 21
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-01T07:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 19
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-21T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-18T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-12T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-11-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-20T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-11T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-10T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-09T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-08T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-06T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-05T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-02T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-10-01T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2015-09-22T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
90
internal/restic/testdata/policy_keep_snapshots_9
vendored
90
internal/restic/testdata/policy_keep_snapshots_9
vendored
|
@ -1,4 +1,5 @@
|
||||||
[
|
{
|
||||||
|
"keep": [
|
||||||
{
|
{
|
||||||
"time": "2016-01-18T12:02:03Z",
|
"time": "2016-01-18T12:02:03Z",
|
||||||
"tree": null,
|
"tree": null,
|
||||||
|
@ -29,4 +30,91 @@
|
||||||
"tree": null,
|
"tree": null,
|
||||||
"paths": null
|
"paths": null
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"reasons": [
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-18T12:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 4,
|
||||||
|
"daily": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:08:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 3,
|
||||||
|
"daily": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-12T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 2,
|
||||||
|
"daily": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-09T21:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"last": 1,
|
||||||
|
"daily": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-08T20:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"last snapshot",
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {
|
||||||
|
"daily": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"snapshot": {
|
||||||
|
"time": "2016-01-07T10:02:03Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null
|
||||||
|
},
|
||||||
|
"matches": [
|
||||||
|
"daily snapshot"
|
||||||
|
],
|
||||||
|
"counters": {}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
206
internal/ui/table/table.go
Normal file
206
internal/ui/table/table.go
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Table contains data for a table to be printed.
|
||||||
|
type Table struct {
|
||||||
|
columns []string
|
||||||
|
templates []*template.Template
|
||||||
|
data []interface{}
|
||||||
|
footer []string
|
||||||
|
|
||||||
|
CellSeparator string
|
||||||
|
PrintHeader func(io.Writer, string) error
|
||||||
|
PrintSeparator func(io.Writer, string) error
|
||||||
|
PrintData func(io.Writer, int, string) error
|
||||||
|
PrintFooter func(io.Writer, string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcmap = template.FuncMap{
|
||||||
|
"join": strings.Join,
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new Table
|
||||||
|
func New() *Table {
|
||||||
|
p := func(w io.Writer, s string) error {
|
||||||
|
_, err := w.Write(append([]byte(s), '\n'))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return &Table{
|
||||||
|
CellSeparator: " ",
|
||||||
|
PrintHeader: p,
|
||||||
|
PrintSeparator: p,
|
||||||
|
PrintData: func(w io.Writer, _ int, s string) error {
|
||||||
|
return p(w, s)
|
||||||
|
},
|
||||||
|
PrintFooter: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddColumn adds a new header field with the header and format, which is
|
||||||
|
// expected to be template string compatible with text/template. When compiling
|
||||||
|
// the format fails, AddColumn panics.
|
||||||
|
func (t *Table) AddColumn(header, format string) {
|
||||||
|
t.columns = append(t.columns, header)
|
||||||
|
tmpl, err := template.New("template for " + header).Funcs(funcmap).Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.templates = append(t.templates, tmpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRow adds a new row to the table, which is filled with data.
|
||||||
|
func (t *Table) AddRow(data interface{}) {
|
||||||
|
t.data = append(t.data, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFooter prints line after the table
|
||||||
|
func (t *Table) AddFooter(line string) {
|
||||||
|
t.footer = append(t.footer, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLine(w io.Writer, print func(io.Writer, string) error, sep string, data []string, widths []int) error {
|
||||||
|
var fields [][]string
|
||||||
|
|
||||||
|
maxLines := 1
|
||||||
|
for _, d := range data {
|
||||||
|
lines := strings.Split(d, "\n")
|
||||||
|
if len(lines) > maxLines {
|
||||||
|
maxLines = len(lines)
|
||||||
|
}
|
||||||
|
fields = append(fields, lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < maxLines; i++ {
|
||||||
|
var s string
|
||||||
|
|
||||||
|
for fieldNum, lines := range fields {
|
||||||
|
var v string
|
||||||
|
|
||||||
|
if i < len(lines) {
|
||||||
|
v += lines[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply padding
|
||||||
|
pad := widths[fieldNum] - len(v)
|
||||||
|
if pad > 0 {
|
||||||
|
v += strings.Repeat(" ", pad)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldNum > 0 {
|
||||||
|
v = sep + v
|
||||||
|
}
|
||||||
|
|
||||||
|
s += v
|
||||||
|
}
|
||||||
|
|
||||||
|
err := print(w, strings.TrimRight(s, " "))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write prints the table to w.
|
||||||
|
func (t *Table) Write(w io.Writer) error {
|
||||||
|
columns := len(t.templates)
|
||||||
|
if columns == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect all data fields from all columns
|
||||||
|
lines := make([][]string, 0, len(t.data))
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
for _, data := range t.data {
|
||||||
|
row := make([]string, 0, len(t.templates))
|
||||||
|
for _, tmpl := range t.templates {
|
||||||
|
err := tmpl.Execute(buf, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
row = append(row, string(buf.Bytes()))
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
lines = append(lines, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find max width for each cell
|
||||||
|
columnWidths := make([]int, columns)
|
||||||
|
for i, desc := range t.columns {
|
||||||
|
for _, line := range strings.Split(desc, "\n") {
|
||||||
|
if columnWidths[i] < len(line) {
|
||||||
|
columnWidths[i] = len(desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, line := range lines {
|
||||||
|
for i, content := range line {
|
||||||
|
for _, l := range strings.Split(content, "\n") {
|
||||||
|
if columnWidths[i] < len(l) {
|
||||||
|
columnWidths[i] = len(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the total width of the table
|
||||||
|
totalWidth := 0
|
||||||
|
for _, width := range columnWidths {
|
||||||
|
totalWidth += width
|
||||||
|
}
|
||||||
|
totalWidth += (columns - 1) * len(t.CellSeparator)
|
||||||
|
|
||||||
|
// write header
|
||||||
|
if len(t.columns) > 0 {
|
||||||
|
err := printLine(w, t.PrintHeader, t.CellSeparator, t.columns, columnWidths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw separation line
|
||||||
|
err = t.PrintSeparator(w, strings.Repeat("-", totalWidth))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write all the lines
|
||||||
|
for i, line := range lines {
|
||||||
|
print := func(w io.Writer, s string) error {
|
||||||
|
return t.PrintData(w, i, s)
|
||||||
|
}
|
||||||
|
err := printLine(w, print, t.CellSeparator, line, columnWidths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw separation line
|
||||||
|
err := t.PrintSeparator(w, strings.Repeat("-", totalWidth))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.footer) > 0 {
|
||||||
|
// write the footer
|
||||||
|
for _, line := range t.footer {
|
||||||
|
err := t.PrintFooter(w, line)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
162
internal/ui/table/table_test.go
Normal file
162
internal/ui/table/table_test.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTable(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
create func(t testing.TB) *Table
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
return New()
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn("first column", "data: {{.First}}")
|
||||||
|
table.AddRow(struct{ First string }{"first data field"})
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
first column
|
||||||
|
----------------------
|
||||||
|
data: first data field
|
||||||
|
----------------------
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn(" first column ", "data: {{.First}}")
|
||||||
|
table.AddRow(struct{ First string }{"d"})
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
first column
|
||||||
|
----------------
|
||||||
|
data: d
|
||||||
|
----------------
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn("first column", "data: {{.First}}")
|
||||||
|
table.AddRow(struct{ First string }{"first data field"})
|
||||||
|
table.AddRow(struct{ First string }{"second data field"})
|
||||||
|
table.AddFooter("footer1")
|
||||||
|
table.AddFooter("footer2")
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
first column
|
||||||
|
-----------------------
|
||||||
|
data: first data field
|
||||||
|
data: second data field
|
||||||
|
-----------------------
|
||||||
|
footer1
|
||||||
|
footer2
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn(" first name", `{{printf "%12s" .FirstName}}`)
|
||||||
|
table.AddColumn("last name", "{{.LastName}}")
|
||||||
|
table.AddRow(struct{ FirstName, LastName string }{"firstname", "lastname"})
|
||||||
|
table.AddRow(struct{ FirstName, LastName string }{"John", "Doe"})
|
||||||
|
table.AddRow(struct{ FirstName, LastName string }{"Johann", "van den Berjen"})
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
first name last name
|
||||||
|
----------------------------
|
||||||
|
firstname lastname
|
||||||
|
John Doe
|
||||||
|
Johann van den Berjen
|
||||||
|
----------------------------
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn("host name", `{{.Host}}`)
|
||||||
|
table.AddColumn("time", `{{.Time}}`)
|
||||||
|
table.AddColumn("zz", "xxx")
|
||||||
|
table.AddColumn("tags", `{{join .Tags ","}}`)
|
||||||
|
table.AddColumn("dirs", `{{join .Dirs ","}}`)
|
||||||
|
|
||||||
|
type data struct {
|
||||||
|
Host string
|
||||||
|
Time string
|
||||||
|
Tags, Dirs []string
|
||||||
|
}
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"work"}, []string{"/home/user/work"}})
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other"}, []string{"/home/user/other"}})
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other"}, []string{"/home/user/other"}})
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
host name time zz tags dirs
|
||||||
|
------------------------------------------------------------
|
||||||
|
foo 2018-08-19 22:22:22 xxx work /home/user/work
|
||||||
|
foo 2018-08-19 22:22:22 xxx other /home/user/other
|
||||||
|
foo 2018-08-19 22:22:22 xxx other /home/user/other
|
||||||
|
------------------------------------------------------------
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(t testing.TB) *Table {
|
||||||
|
table := New()
|
||||||
|
table.AddColumn("host name", `{{.Host}}`)
|
||||||
|
table.AddColumn("time", `{{.Time}}`)
|
||||||
|
table.AddColumn("zz", "xxx")
|
||||||
|
table.AddColumn("tags", `{{join .Tags "\n"}}`)
|
||||||
|
table.AddColumn("dirs", `{{join .Dirs "\n"}}`)
|
||||||
|
|
||||||
|
type data struct {
|
||||||
|
Host string
|
||||||
|
Time string
|
||||||
|
Tags, Dirs []string
|
||||||
|
}
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"work", "go"}, []string{"/home/user/work", "/home/user/go"}})
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other"}, []string{"/home/user/other"}})
|
||||||
|
table.AddRow(data{"foo", "2018-08-19 22:22:22", []string{"other", "bar"}, []string{"/home/user/other"}})
|
||||||
|
return table
|
||||||
|
},
|
||||||
|
`
|
||||||
|
host name time zz tags dirs
|
||||||
|
------------------------------------------------------------
|
||||||
|
foo 2018-08-19 22:22:22 xxx work /home/user/work
|
||||||
|
go /home/user/go
|
||||||
|
foo 2018-08-19 22:22:22 xxx other /home/user/other
|
||||||
|
foo 2018-08-19 22:22:22 xxx other /home/user/other
|
||||||
|
bar
|
||||||
|
------------------------------------------------------------
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
table := test.create(t)
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
err := table.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := strings.TrimLeft(test.output, "\n")
|
||||||
|
if string(buf.Bytes()) != want {
|
||||||
|
t.Errorf("wrong output\n---- want ---\n%s\n---- got ---\n%s\n-------\n", want, buf.Bytes())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
89
vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
generated
vendored
Normal file
89
vendor/github.com/google/go-cmp/cmp/cmpopts/equate.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// Package cmpopts provides common options for the cmp package.
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func equateAlways(_, _ interface{}) bool { return true }
|
||||||
|
|
||||||
|
// EquateEmpty returns a Comparer option that determines all maps and slices
|
||||||
|
// with a length of zero to be equal, regardless of whether they are nil.
|
||||||
|
//
|
||||||
|
// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
|
||||||
|
func EquateEmpty() cmp.Option {
|
||||||
|
return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(x, y interface{}) bool {
|
||||||
|
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
|
||||||
|
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
|
||||||
|
(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
|
||||||
|
(vx.Len() == 0 && vy.Len() == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EquateApprox returns a Comparer option that determines float32 or float64
|
||||||
|
// values to be equal if they are within a relative fraction or absolute margin.
|
||||||
|
// This option is not used when either x or y is NaN or infinite.
|
||||||
|
//
|
||||||
|
// The fraction determines that the difference of two values must be within the
|
||||||
|
// smaller fraction of the two values, while the margin determines that the two
|
||||||
|
// values must be within some absolute margin.
|
||||||
|
// To express only a fraction or only a margin, use 0 for the other parameter.
|
||||||
|
// The fraction and margin must be non-negative.
|
||||||
|
//
|
||||||
|
// The mathematical expression used is equivalent to:
|
||||||
|
// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
|
||||||
|
//
|
||||||
|
// EquateApprox can be used in conjunction with EquateNaNs.
|
||||||
|
func EquateApprox(fraction, margin float64) cmp.Option {
|
||||||
|
if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
|
||||||
|
panic("margin or fraction must be a non-negative number")
|
||||||
|
}
|
||||||
|
a := approximator{fraction, margin}
|
||||||
|
return cmp.Options{
|
||||||
|
cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
|
||||||
|
cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type approximator struct{ frac, marg float64 }
|
||||||
|
|
||||||
|
func areRealF64s(x, y float64) bool {
|
||||||
|
return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
|
||||||
|
}
|
||||||
|
func areRealF32s(x, y float32) bool {
|
||||||
|
return areRealF64s(float64(x), float64(y))
|
||||||
|
}
|
||||||
|
func (a approximator) compareF64(x, y float64) bool {
|
||||||
|
relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
|
||||||
|
return math.Abs(x-y) <= math.Max(a.marg, relMarg)
|
||||||
|
}
|
||||||
|
func (a approximator) compareF32(x, y float32) bool {
|
||||||
|
return a.compareF64(float64(x), float64(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EquateNaNs returns a Comparer option that determines float32 and float64
|
||||||
|
// NaN values to be equal.
|
||||||
|
//
|
||||||
|
// EquateNaNs can be used in conjunction with EquateApprox.
|
||||||
|
func EquateNaNs() cmp.Option {
|
||||||
|
return cmp.Options{
|
||||||
|
cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
|
||||||
|
cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func areNaNsF64s(x, y float64) bool {
|
||||||
|
return math.IsNaN(x) && math.IsNaN(y)
|
||||||
|
}
|
||||||
|
func areNaNsF32s(x, y float32) bool {
|
||||||
|
return areNaNsF64s(float64(x), float64(y))
|
||||||
|
}
|
145
vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
generated
vendored
Normal file
145
vendor/github.com/google/go-cmp/cmp/cmpopts/ignore.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IgnoreFields returns an Option that ignores exported fields of the
|
||||||
|
// given names on a single struct type.
|
||||||
|
// The struct type is specified by passing in a value of that type.
|
||||||
|
//
|
||||||
|
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
|
||||||
|
// specific sub-field that is embedded or nested within the parent struct.
|
||||||
|
//
|
||||||
|
// This does not handle unexported fields; use IgnoreUnexported instead.
|
||||||
|
func IgnoreFields(typ interface{}, names ...string) cmp.Option {
|
||||||
|
sf := newStructFilter(typ, names...)
|
||||||
|
return cmp.FilterPath(sf.filter, cmp.Ignore())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreTypes returns an Option that ignores all values assignable to
|
||||||
|
// certain types, which are specified by passing in a value of each type.
|
||||||
|
func IgnoreTypes(typs ...interface{}) cmp.Option {
|
||||||
|
tf := newTypeFilter(typs...)
|
||||||
|
return cmp.FilterPath(tf.filter, cmp.Ignore())
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeFilter []reflect.Type
|
||||||
|
|
||||||
|
func newTypeFilter(typs ...interface{}) (tf typeFilter) {
|
||||||
|
for _, typ := range typs {
|
||||||
|
t := reflect.TypeOf(typ)
|
||||||
|
if t == nil {
|
||||||
|
// This occurs if someone tries to pass in sync.Locker(nil)
|
||||||
|
panic("cannot determine type; consider using IgnoreInterfaces")
|
||||||
|
}
|
||||||
|
tf = append(tf, t)
|
||||||
|
}
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
func (tf typeFilter) filter(p cmp.Path) bool {
|
||||||
|
if len(p) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t := p.Last().Type()
|
||||||
|
for _, ti := range tf {
|
||||||
|
if t.AssignableTo(ti) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreInterfaces returns an Option that ignores all values or references of
|
||||||
|
// values assignable to certain interface types. These interfaces are specified
|
||||||
|
// by passing in an anonymous struct with the interface types embedded in it.
|
||||||
|
// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
|
||||||
|
func IgnoreInterfaces(ifaces interface{}) cmp.Option {
|
||||||
|
tf := newIfaceFilter(ifaces)
|
||||||
|
return cmp.FilterPath(tf.filter, cmp.Ignore())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifaceFilter []reflect.Type
|
||||||
|
|
||||||
|
func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
|
||||||
|
t := reflect.TypeOf(ifaces)
|
||||||
|
if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
|
||||||
|
panic("input must be an anonymous struct")
|
||||||
|
}
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
fi := t.Field(i)
|
||||||
|
switch {
|
||||||
|
case !fi.Anonymous:
|
||||||
|
panic("struct cannot have named fields")
|
||||||
|
case fi.Type.Kind() != reflect.Interface:
|
||||||
|
panic("embedded field must be an interface type")
|
||||||
|
case fi.Type.NumMethod() == 0:
|
||||||
|
// This matches everything; why would you ever want this?
|
||||||
|
panic("cannot ignore empty interface")
|
||||||
|
default:
|
||||||
|
tf = append(tf, fi.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
func (tf ifaceFilter) filter(p cmp.Path) bool {
|
||||||
|
if len(p) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t := p.Last().Type()
|
||||||
|
for _, ti := range tf {
|
||||||
|
if t.AssignableTo(ti) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreUnexported returns an Option that only ignores the immediate unexported
|
||||||
|
// fields of a struct, including anonymous fields of unexported types.
|
||||||
|
// In particular, unexported fields within the struct's exported fields
|
||||||
|
// of struct types, including anonymous fields, will not be ignored unless the
|
||||||
|
// type of the field itself is also passed to IgnoreUnexported.
|
||||||
|
func IgnoreUnexported(typs ...interface{}) cmp.Option {
|
||||||
|
ux := newUnexportedFilter(typs...)
|
||||||
|
return cmp.FilterPath(ux.filter, cmp.Ignore())
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexportedFilter struct{ m map[reflect.Type]bool }
|
||||||
|
|
||||||
|
func newUnexportedFilter(typs ...interface{}) unexportedFilter {
|
||||||
|
ux := unexportedFilter{m: make(map[reflect.Type]bool)}
|
||||||
|
for _, typ := range typs {
|
||||||
|
t := reflect.TypeOf(typ)
|
||||||
|
if t == nil || t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||||
|
}
|
||||||
|
ux.m[t] = true
|
||||||
|
}
|
||||||
|
return ux
|
||||||
|
}
|
||||||
|
func (xf unexportedFilter) filter(p cmp.Path) bool {
|
||||||
|
sf, ok := p.Index(-1).(cmp.StructField)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExported reports whether the identifier is exported.
|
||||||
|
func isExported(id string) bool {
|
||||||
|
r, _ := utf8.DecodeRuneInString(id)
|
||||||
|
return unicode.IsUpper(r)
|
||||||
|
}
|
146
vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
generated
vendored
Normal file
146
vendor/github.com/google/go-cmp/cmp/cmpopts/sort.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/internal/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SortSlices returns a Transformer option that sorts all []V.
|
||||||
|
// The less function must be of the form "func(T, T) bool" which is used to
|
||||||
|
// sort any slice with element type V that is assignable to T.
|
||||||
|
//
|
||||||
|
// The less function must be:
|
||||||
|
// • Deterministic: less(x, y) == less(x, y)
|
||||||
|
// • Irreflexive: !less(x, x)
|
||||||
|
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
|
||||||
|
//
|
||||||
|
// The less function does not have to be "total". That is, if !less(x, y) and
|
||||||
|
// !less(y, x) for two elements x and y, their relative order is maintained.
|
||||||
|
//
|
||||||
|
// SortSlices can be used in conjunction with EquateEmpty.
|
||||||
|
func SortSlices(less interface{}) cmp.Option {
|
||||||
|
vf := reflect.ValueOf(less)
|
||||||
|
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
|
||||||
|
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||||
|
}
|
||||||
|
ss := sliceSorter{vf.Type().In(0), vf}
|
||||||
|
return cmp.FilterValues(ss.filter, cmp.Transformer("Sort", ss.sort))
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceSorter struct {
|
||||||
|
in reflect.Type // T
|
||||||
|
fnc reflect.Value // func(T, T) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sliceSorter) filter(x, y interface{}) bool {
|
||||||
|
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
|
||||||
|
if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
|
||||||
|
!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
|
||||||
|
(vx.Len() <= 1 && vy.Len() <= 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Check whether the slices are already sorted to avoid an infinite
|
||||||
|
// recursion cycle applying the same transform to itself.
|
||||||
|
ok1 := sliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
|
||||||
|
ok2 := sliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
|
||||||
|
return !ok1 || !ok2
|
||||||
|
}
|
||||||
|
func (ss sliceSorter) sort(x interface{}) interface{} {
|
||||||
|
src := reflect.ValueOf(x)
|
||||||
|
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
dst.Index(i).Set(src.Index(i))
|
||||||
|
}
|
||||||
|
sortSliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
|
||||||
|
ss.checkSort(dst)
|
||||||
|
return dst.Interface()
|
||||||
|
}
|
||||||
|
func (ss sliceSorter) checkSort(v reflect.Value) {
|
||||||
|
start := -1 // Start of a sequence of equal elements.
|
||||||
|
for i := 1; i < v.Len(); i++ {
|
||||||
|
if ss.less(v, i-1, i) {
|
||||||
|
// Check that first and last elements in v[start:i] are equal.
|
||||||
|
if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
|
||||||
|
panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
|
||||||
|
}
|
||||||
|
start = -1
|
||||||
|
} else if start == -1 {
|
||||||
|
start = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
|
||||||
|
vx, vy := v.Index(i), v.Index(j)
|
||||||
|
return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortMaps returns a Transformer option that flattens map[K]V types to be a
|
||||||
|
// sorted []struct{K, V}. The less function must be of the form
|
||||||
|
// "func(T, T) bool" which is used to sort any map with key K that is
|
||||||
|
// assignable to T.
|
||||||
|
//
|
||||||
|
// Flattening the map into a slice has the property that cmp.Equal is able to
|
||||||
|
// use Comparers on K or the K.Equal method if it exists.
|
||||||
|
//
|
||||||
|
// The less function must be:
|
||||||
|
// • Deterministic: less(x, y) == less(x, y)
|
||||||
|
// • Irreflexive: !less(x, x)
|
||||||
|
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
|
||||||
|
// • Total: if x != y, then either less(x, y) or less(y, x)
|
||||||
|
//
|
||||||
|
// SortMaps can be used in conjunction with EquateEmpty.
|
||||||
|
func SortMaps(less interface{}) cmp.Option {
|
||||||
|
vf := reflect.ValueOf(less)
|
||||||
|
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
|
||||||
|
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||||
|
}
|
||||||
|
ms := mapSorter{vf.Type().In(0), vf}
|
||||||
|
return cmp.FilterValues(ms.filter, cmp.Transformer("Sort", ms.sort))
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapSorter struct {
|
||||||
|
in reflect.Type // T
|
||||||
|
fnc reflect.Value // func(T, T) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms mapSorter) filter(x, y interface{}) bool {
|
||||||
|
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
|
||||||
|
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
|
||||||
|
(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
|
||||||
|
(vx.Len() != 0 || vy.Len() != 0)
|
||||||
|
}
|
||||||
|
func (ms mapSorter) sort(x interface{}) interface{} {
|
||||||
|
src := reflect.ValueOf(x)
|
||||||
|
outType := mapEntryType(src.Type())
|
||||||
|
dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
|
||||||
|
for i, k := range src.MapKeys() {
|
||||||
|
v := reflect.New(outType).Elem()
|
||||||
|
v.Field(0).Set(k)
|
||||||
|
v.Field(1).Set(src.MapIndex(k))
|
||||||
|
dst.Index(i).Set(v)
|
||||||
|
}
|
||||||
|
sortSlice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
|
||||||
|
ms.checkSort(dst)
|
||||||
|
return dst.Interface()
|
||||||
|
}
|
||||||
|
func (ms mapSorter) checkSort(v reflect.Value) {
|
||||||
|
for i := 1; i < v.Len(); i++ {
|
||||||
|
if !ms.less(v, i-1, i) {
|
||||||
|
panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (ms mapSorter) less(v reflect.Value, i, j int) bool {
|
||||||
|
vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
|
||||||
|
if !hasReflectStructOf {
|
||||||
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
|
}
|
||||||
|
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
|
||||||
|
}
|
46
vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
generated
vendored
Normal file
46
vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go17.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasReflectStructOf = false
|
||||||
|
|
||||||
|
func mapEntryType(reflect.Type) reflect.Type {
|
||||||
|
return reflect.TypeOf(struct{ K, V interface{} }{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
|
||||||
|
return sort.IsSorted(reflectSliceSorter{reflect.ValueOf(slice), less})
|
||||||
|
}
|
||||||
|
func sortSlice(slice interface{}, less func(i, j int) bool) {
|
||||||
|
sort.Sort(reflectSliceSorter{reflect.ValueOf(slice), less})
|
||||||
|
}
|
||||||
|
func sortSliceStable(slice interface{}, less func(i, j int) bool) {
|
||||||
|
sort.Stable(reflectSliceSorter{reflect.ValueOf(slice), less})
|
||||||
|
}
|
||||||
|
|
||||||
|
type reflectSliceSorter struct {
|
||||||
|
slice reflect.Value
|
||||||
|
less func(i, j int) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss reflectSliceSorter) Len() int {
|
||||||
|
return ss.slice.Len()
|
||||||
|
}
|
||||||
|
func (ss reflectSliceSorter) Less(i, j int) bool {
|
||||||
|
return ss.less(i, j)
|
||||||
|
}
|
||||||
|
func (ss reflectSliceSorter) Swap(i, j int) {
|
||||||
|
vi := ss.slice.Index(i).Interface()
|
||||||
|
vj := ss.slice.Index(j).Interface()
|
||||||
|
ss.slice.Index(i).Set(reflect.ValueOf(vj))
|
||||||
|
ss.slice.Index(j).Set(reflect.ValueOf(vi))
|
||||||
|
}
|
31
vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
generated
vendored
Normal file
31
vendor/github.com/google/go-cmp/cmp/cmpopts/sort_go18.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasReflectStructOf = true
|
||||||
|
|
||||||
|
func mapEntryType(t reflect.Type) reflect.Type {
|
||||||
|
return reflect.StructOf([]reflect.StructField{
|
||||||
|
{Name: "K", Type: t.Key()},
|
||||||
|
{Name: "V", Type: t.Elem()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceIsSorted(slice interface{}, less func(i, j int) bool) bool {
|
||||||
|
return sort.SliceIsSorted(slice, less)
|
||||||
|
}
|
||||||
|
func sortSlice(slice interface{}, less func(i, j int) bool) {
|
||||||
|
sort.Slice(slice, less)
|
||||||
|
}
|
||||||
|
func sortSliceStable(slice interface{}, less func(i, j int) bool) {
|
||||||
|
sort.SliceStable(slice, less)
|
||||||
|
}
|
182
vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
generated
vendored
Normal file
182
vendor/github.com/google/go-cmp/cmp/cmpopts/struct_filter.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package cmpopts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filterField returns a new Option where opt is only evaluated on paths that
|
||||||
|
// include a specific exported field on a single struct type.
|
||||||
|
// The struct type is specified by passing in a value of that type.
|
||||||
|
//
|
||||||
|
// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
|
||||||
|
// specific sub-field that is embedded or nested within the parent struct.
|
||||||
|
func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
|
||||||
|
// TODO: This is currently unexported over concerns of how helper filters
|
||||||
|
// can be composed together easily.
|
||||||
|
// TODO: Add tests for FilterField.
|
||||||
|
|
||||||
|
sf := newStructFilter(typ, name)
|
||||||
|
return cmp.FilterPath(sf.filter, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
type structFilter struct {
|
||||||
|
t reflect.Type // The root struct type to match on
|
||||||
|
ft fieldTree // Tree of fields to match on
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStructFilter(typ interface{}, names ...string) structFilter {
|
||||||
|
// TODO: Perhaps allow * as a special identifier to allow ignoring any
|
||||||
|
// number of path steps until the next field match?
|
||||||
|
// This could be useful when a concrete struct gets transformed into
|
||||||
|
// an anonymous struct where it is not possible to specify that by type,
|
||||||
|
// but the transformer happens to provide guarantees about the names of
|
||||||
|
// the transformed fields.
|
||||||
|
|
||||||
|
t := reflect.TypeOf(typ)
|
||||||
|
if t == nil || t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("%T must be a struct", typ))
|
||||||
|
}
|
||||||
|
var ft fieldTree
|
||||||
|
for _, name := range names {
|
||||||
|
cname, err := canonicalName(t, name)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
|
||||||
|
}
|
||||||
|
ft.insert(cname)
|
||||||
|
}
|
||||||
|
return structFilter{t, ft}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf structFilter) filter(p cmp.Path) bool {
|
||||||
|
for i, ps := range p {
|
||||||
|
if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldTree represents a set of dot-separated identifiers.
|
||||||
|
//
|
||||||
|
// For example, inserting the following selectors:
|
||||||
|
// Foo
|
||||||
|
// Foo.Bar.Baz
|
||||||
|
// Foo.Buzz
|
||||||
|
// Nuka.Cola.Quantum
|
||||||
|
//
|
||||||
|
// Results in a tree of the form:
|
||||||
|
// {sub: {
|
||||||
|
// "Foo": {ok: true, sub: {
|
||||||
|
// "Bar": {sub: {
|
||||||
|
// "Baz": {ok: true},
|
||||||
|
// }},
|
||||||
|
// "Buzz": {ok: true},
|
||||||
|
// }},
|
||||||
|
// "Nuka": {sub: {
|
||||||
|
// "Cola": {sub: {
|
||||||
|
// "Quantum": {ok: true},
|
||||||
|
// }},
|
||||||
|
// }},
|
||||||
|
// }}
|
||||||
|
type fieldTree struct {
|
||||||
|
ok bool // Whether this is a specified node
|
||||||
|
sub map[string]fieldTree // The sub-tree of fields under this node
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert inserts a sequence of field accesses into the tree.
|
||||||
|
func (ft *fieldTree) insert(cname []string) {
|
||||||
|
if ft.sub == nil {
|
||||||
|
ft.sub = make(map[string]fieldTree)
|
||||||
|
}
|
||||||
|
if len(cname) == 0 {
|
||||||
|
ft.ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sub := ft.sub[cname[0]]
|
||||||
|
sub.insert(cname[1:])
|
||||||
|
ft.sub[cname[0]] = sub
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchPrefix reports whether any selector in the fieldTree matches
|
||||||
|
// the start of path p.
|
||||||
|
func (ft fieldTree) matchPrefix(p cmp.Path) bool {
|
||||||
|
for _, ps := range p {
|
||||||
|
switch ps := ps.(type) {
|
||||||
|
case cmp.StructField:
|
||||||
|
ft = ft.sub[ps.Name()]
|
||||||
|
if ft.ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(ft.sub) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case cmp.Indirect:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// canonicalName returns a list of identifiers where any struct field access
|
||||||
|
// through an embedded field is expanded to include the names of the embedded
|
||||||
|
// types themselves.
|
||||||
|
//
|
||||||
|
// For example, suppose field "Foo" is not directly in the parent struct,
|
||||||
|
// but actually from an embedded struct of type "Bar". Then, the canonical name
|
||||||
|
// of "Foo" is actually "Bar.Foo".
|
||||||
|
//
|
||||||
|
// Suppose field "Foo" is not directly in the parent struct, but actually
|
||||||
|
// a field in two different embedded structs of types "Bar" and "Baz".
|
||||||
|
// Then the selector "Foo" causes a panic since it is ambiguous which one it
|
||||||
|
// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
|
||||||
|
func canonicalName(t reflect.Type, sel string) ([]string, error) {
|
||||||
|
var name string
|
||||||
|
sel = strings.TrimPrefix(sel, ".")
|
||||||
|
if sel == "" {
|
||||||
|
return nil, fmt.Errorf("name must not be empty")
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(sel, '.'); i < 0 {
|
||||||
|
name, sel = sel, ""
|
||||||
|
} else {
|
||||||
|
name, sel = sel[:i], sel[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type must be a struct or pointer to struct.
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("%v must be a struct", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the canonical name for this current field name.
|
||||||
|
// If the field exists in an embedded struct, then it will be expanded.
|
||||||
|
if !isExported(name) {
|
||||||
|
// Disallow unexported fields:
|
||||||
|
// * To discourage people from actually touching unexported fields
|
||||||
|
// * FieldByName is buggy (https://golang.org/issue/4876)
|
||||||
|
return []string{name}, fmt.Errorf("name must be exported")
|
||||||
|
}
|
||||||
|
sf, ok := t.FieldByName(name)
|
||||||
|
if !ok {
|
||||||
|
return []string{name}, fmt.Errorf("does not exist")
|
||||||
|
}
|
||||||
|
var ss []string
|
||||||
|
for i := range sf.Index {
|
||||||
|
ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
|
||||||
|
}
|
||||||
|
if sel == "" {
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
ssPost, err := canonicalName(sf.Type, sel)
|
||||||
|
return append(ss, ssPost...), err
|
||||||
|
}
|
Loading…
Reference in a new issue