2
2
mirror of https://github.com/octoleo/restic.git synced 2024-12-23 19:38:57 +00:00

Merge pull request #616 from restic/add-snapshot-tags

Add tags to snapshots
This commit is contained in:
Alexander Neumann 2016-09-14 20:58:12 +02:00
commit 931f5cdd33
45 changed files with 861 additions and 199 deletions

View File

@ -84,18 +84,24 @@ them, e.g. for the `backup` command:
The backup command creates a snapshot of a file or directory The backup command creates a snapshot of a file or directory
Application Options: Application Options:
-r, --repo= Repository directory to backup to/restore from -r, --repo= Repository directory to backup to/restore from (/tmp/repo)
--cache-dir= Directory to use as a local cache -p, --password-file= Read the repository password from a file
-q, --quiet Do not output comprehensive progress report (false) --cache-dir= Directory to use as a local cache
--no-lock Do not lock the repo, this allows some operations on read-only repos. (false) -q, --quiet Do not output comprehensive progress report (false)
--no-lock Do not lock the repo, this allows some operations on read-only repos. (false)
-o, --option= Specify options in the form 'foo.key=value'
Help Options: Help Options:
-h, --help Show this help message -h, --help Show this help message
[backup command options] [backup command options]
-p, --parent= use this parent snapshot (default: last snapshot in repo that has the same target) -p, --parent= use this parent snapshot (default: last snapshot in repo that has the same target)
-f, --force Force re-reading the target. Overrides the "parent" flag -f, --force Force re-reading the target. Overrides the "parent" flag
-e, --exclude= Exclude a pattern (can be specified multiple times) -e, --exclude= Exclude a pattern (can be specified multiple times)
--exclude-file= Read exclude-patterns from file
--stdin read backup data from stdin
--stdin-filename= file name to use when reading from stdin (stdin)
--tag= Add a tag (can be specified multiple times)
Subcommand that support showing progress information such as `backup`, `check` and `prune` will do so unless Subcommand that support showing progress information such as `backup`, `check` and `prune` will do so unless
the quiet flag `-q` or `--quiet` is set. When running from a non-interactive console progress reporting will the quiet flag `-q` or `--quiet` is set. When running from a non-interactive console progress reporting will
@ -216,37 +222,47 @@ with `--stdin-filename`, e.g. like this:
$ mysqldump [...] | restic -r /tmp/backup backup --stdin --stdin-filename production.sql $ mysqldump [...] | restic -r /tmp/backup backup --stdin --stdin-filename production.sql
## Tags
Snapshots can have one or more tags, short strings which add identifying
information. Just specify the tags for a snapshot with `--tag`:
$ restic -r /tmp/backup backup --tag projectX ~/shared/work/web
[...]
The tags can later be used to keep (or forget) snapshots.
# List all snapshots # List all snapshots
Now, you can list all the snapshots stored in the repository: Now, you can list all the snapshots stored in the repository:
$ restic -r /tmp/backup snapshots $ restic -r /tmp/backup snapshots
enter password for repository: enter password for repository:
ID Date Host Directory ID Date Host Tags Directory
---------------------------------------------------------------------- ----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work 40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work 79766175 2015-05-08 21:40:19 kasimir /home/user/work
bdbd3439 2015-05-08 21:45:17 luigi /home/art bdbd3439 2015-05-08 21:45:17 luigi /home/art
590c8fc8 2015-05-08 21:47:38 kazik /srv 590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv 9f0bc19e 2015-05-08 21:46:11 luigi /srv
You can filter the listing by directory path: You can filter the listing by directory path:
$ restic -r /tmp/backup snapshots --path="/srv" $ restic -r /tmp/backup snapshots --path="/srv"
enter password for repository: enter password for repository:
ID Date Host Directory ID Date Host Tags Directory
---------------------------------------------------------------------- ----------------------------------------------------------------------
590c8fc8 2015-05-08 21:47:38 kazik /srv 590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv 9f0bc19e 2015-05-08 21:46:11 luigi /srv
Or filter by host: Or filter by host:
$ restic -r /tmp/backup snapshots --host luigi $ restic -r /tmp/backup snapshots --host luigi
enter password for repository: enter password for repository:
ID Date Host Directory ID Date Host Tags Directory
---------------------------------------------------------------------- ----------------------------------------------------------------------
bdbd3439 2015-05-08 21:45:17 luigi /home/art bdbd3439 2015-05-08 21:45:17 luigi /home/art
9f0bc19e 2015-05-08 21:46:11 luigi /srv 9f0bc19e 2015-05-08 21:46:11 luigi /srv
Combining filters is also possible. Combining filters is also possible.
@ -412,13 +428,13 @@ The command `snapshots` can be used to list all snapshots in a repository like t
$ restic -r /tmp/backup snapshots $ restic -r /tmp/backup snapshots
enter password for repository: enter password for repository:
ID Date Host Directory ID Date Host Tags Directory
---------------------------------------------------------------------- ----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work 40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work 79766175 2015-05-08 21:40:19 kasimir /home/user/work
bdbd3439 2015-05-08 21:45:17 luigi /home/art bdbd3439 2015-05-08 21:45:17 luigi /home/art
590c8fc8 2015-05-08 21:47:38 kazik /srv 590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv 9f0bc19e 2015-05-08 21:46:11 luigi /srv
In order to remove the snapshot of `/home/art`, use the `forget` command and In order to remove the snapshot of `/home/art`, use the `forget` command and
specify the snapshot ID on the command line: specify the snapshot ID on the command line:
@ -431,12 +447,12 @@ Afterwards this snapshot is removed:
$ restic -r /tmp/backup snapshots $ restic -r /tmp/backup snapshots
enter password for repository: enter password for repository:
ID Date Host Directory ID Date Host Tags Directory
---------------------------------------------------------------------- ----------------------------------------------------------------------
40dc1520 2015-05-08 21:38:30 kasimir /home/user/work 40dc1520 2015-05-08 21:38:30 kasimir /home/user/work
79766175 2015-05-08 21:40:19 kasimir /home/user/work 79766175 2015-05-08 21:40:19 kasimir /home/user/work
590c8fc8 2015-05-08 21:47:38 kazik /srv 590c8fc8 2015-05-08 21:47:38 kazik /srv
9f0bc19e 2015-05-08 21:46:11 luigi /srv 9f0bc19e 2015-05-08 21:46:11 luigi /srv
But the data that was referenced by files in this snapshot is still stored in But the data that was referenced by files in this snapshot is still stored in
the repository. To cleanup unreferenced data, the `prune` command must be run: the repository. To cleanup unreferenced data, the `prune` command must be run:
@ -487,9 +503,13 @@ The `forget` command accepts the following parameters:
keep the last one for that month. keep the last one for that month.
* `--keep-yearly n` for the last `n` years which have one or more snapshots, only * `--keep-yearly n` for the last `n` years which have one or more snapshots, only
keep the last one for that year. keep the last one for that year.
* `--keep-tag` keep all snapshots which have all tags specified by this option
(can be specified multiple times).
Additionally, you can restrict removing snapshots to those which have a Additionally, you can restrict removing snapshots to those which have a
particular hostname with the `--hostname` parameter. particular hostname with the `--hostname` parameter, or tags with the `--tag`
option. When multiple tags are specified, only the snapshots which have all the
tags are considered.
All the `--keep-*` options above only count hours/days/weeks/months/years which All the `--keep-*` options above only count hours/days/weeks/months/years which
have a snapshot, so those without a snapshot are ignored. have a snapshot, so those without a snapshot are ignored.

View File

@ -25,6 +25,7 @@ type CmdBackup struct {
ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"` ExcludeFile string `long:"exclude-file" description:"Read exclude-patterns from file"`
Stdin bool `long:"stdin" description:"read backup data from stdin"` Stdin bool `long:"stdin" description:"read backup data from stdin"`
StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"` StdinFilename string `long:"stdin-filename" default:"stdin" description:"file name to use when reading from stdin"`
Tags []string `long:"tag" description:"Add a tag (can be specified multiple times)"`
global *GlobalOptions global *GlobalOptions
} }
@ -209,7 +210,7 @@ func (cmd CmdBackup) newArchiveStdinProgress() *restic.Progress {
} }
} }
PrintProgress("%s%s", status1) PrintProgress("%s", status1)
} }
archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) { archiveProgress.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
@ -259,7 +260,7 @@ func (cmd CmdBackup) readFromStdin(args []string) error {
return err return err
} }
_, id, err := archiver.ArchiveReader(repo, cmd.newArchiveStdinProgress(), os.Stdin, cmd.StdinFilename) _, id, err := archiver.ArchiveReader(repo, cmd.newArchiveStdinProgress(), os.Stdin, cmd.StdinFilename, cmd.Tags)
if err != nil { if err != nil {
return err return err
} }
@ -380,7 +381,7 @@ func (cmd CmdBackup) Execute(args []string) error {
return nil return nil
} }
_, id, err := arch.Snapshot(cmd.newArchiveProgress(stat), target, parentSnapshotID) _, id, err := arch.Snapshot(cmd.newArchiveProgress(stat), target, cmd.Tags, parentSnapshotID)
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,14 +9,17 @@ import (
// CmdForget implements the 'forget' command. // CmdForget implements the 'forget' command.
type CmdForget struct { type CmdForget struct {
Last int `short:"l" long:"keep-last" description:"keep the last n snapshots"` Last int `short:"l" long:"keep-last" description:"keep the last n snapshots"`
Hourly int `short:"H" long:"keep-hourly" description:"keep the last n hourly snapshots"` Hourly int `short:"H" long:"keep-hourly" description:"keep the last n hourly snapshots"`
Daily int `short:"d" long:"keep-daily" description:"keep the last n daily snapshots"` Daily int `short:"d" long:"keep-daily" description:"keep the last n daily snapshots"`
Weekly int `short:"w" long:"keep-weekly" description:"keep the last n weekly snapshots"` Weekly int `short:"w" long:"keep-weekly" description:"keep the last n weekly snapshots"`
Monthly int `short:"m" long:"keep-monthly" description:"keep the last n monthly snapshots"` Monthly int `short:"m" long:"keep-monthly"description:"keep the last n monthly snapshots"`
Yearly int `short:"y" long:"keep-yearly" description:"keep the last n yearly snapshots"` Yearly int `short:"y" long:"keep-yearly" description:"keep the last n yearly snapshots"`
Hostname string `long:"hostname" description:"only forget snapshots for the given hostname"` KeepTags []string `long:"keep-tag" description:"alwaps keep snapshots with this tag (can be specified multiple times)"`
Hostname string `long:"hostname" description:"only forget snapshots for the given hostname"`
Tags []string `long:"tag" description:"only forget snapshots with the tag (can be specified multiple times)"`
DryRun bool `short:"n" long:"dry-run" description:"do not delete anything, just print what would be done"` DryRun bool `short:"n" long:"dry-run" description:"do not delete anything, just print what would be done"`
@ -46,20 +49,38 @@ func (cmd CmdForget) Usage() string {
func printSnapshots(w io.Writer, snapshots restic.Snapshots) { func printSnapshots(w io.Writer, snapshots restic.Snapshots) {
tab := NewTable() tab := NewTable()
tab.Header = fmt.Sprintf("%-8s %-19s %-10s %s", "ID", "Date", "Host", "Directory") tab.Header = fmt.Sprintf("%-8s %-19s %-10s %-10s %s", "ID", "Date", "Host", "Tags", "Directory")
tab.RowFormat = "%-8s %-19s %-10s %s" tab.RowFormat = "%-8s %-19s %-10s %-10s %s"
for _, sn := range snapshots { for _, sn := range snapshots {
if len(sn.Paths) == 0 { if len(sn.Paths) == 0 {
continue continue
} }
id := sn.ID()
tab.Rows = append(tab.Rows, []interface{}{id.Str(), sn.Time.Format(TimeFormat), sn.Hostname, sn.Paths[0]})
if len(sn.Paths) > 1 { firstTag := ""
for _, path := range sn.Paths[1:] { if len(sn.Tags) > 0 {
tab.Rows = append(tab.Rows, []interface{}{"", "", "", path}) firstTag = sn.Tags[0]
}
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, sn.Paths[0]})
rows := len(sn.Paths)
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]
}
tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, path})
} }
} }
@ -110,6 +131,7 @@ func (cmd CmdForget) Execute(args []string) error {
Weekly: cmd.Weekly, Weekly: cmd.Weekly,
Monthly: cmd.Monthly, Monthly: cmd.Monthly,
Yearly: cmd.Yearly, Yearly: cmd.Yearly,
Tags: cmd.KeepTags,
} }
if policy.Empty() { if policy.Empty() {
@ -135,6 +157,10 @@ func (cmd CmdForget) Execute(args []string) error {
continue continue
} }
if !sn.HasTags(cmd.Tags) {
continue
}
k := key{Hostname: sn.Hostname, Dirs: strings.Join(sn.Paths, ":")} k := key{Hostname: sn.Hostname, Dirs: strings.Join(sn.Paths, ":")}
list := snapshotGroups[k] list := snapshotGroups[k]
list = append(list, sn) list = append(list, sn)

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"encoding/hex"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -85,8 +84,8 @@ func (cmd CmdSnapshots) Execute(args []string) error {
} }
tab := NewTable() tab := NewTable()
tab.Header = fmt.Sprintf("%-8s %-19s %-10s %s", "ID", "Date", "Host", "Directory") tab.Header = fmt.Sprintf("%-8s %-19s %-10s %-10s %s", "ID", "Date", "Host", "Tags", "Directory")
tab.RowFormat = "%-8s %-19s %-10s %s" tab.RowFormat = "%-8s %-19s %-10s %-10s %s"
done := make(chan struct{}) done := make(chan struct{})
defer close(done) defer close(done)
@ -115,22 +114,35 @@ func (cmd CmdSnapshots) Execute(args []string) error {
} }
plen, err := repo.PrefixLength(restic.SnapshotFile)
if err != nil {
return err
}
for _, sn := range list { for _, sn := range list {
if len(sn.Paths) == 0 { if len(sn.Paths) == 0 {
continue continue
} }
id := sn.ID()
tab.Rows = append(tab.Rows, []interface{}{hex.EncodeToString(id[:plen/2]), sn.Time.Format(TimeFormat), sn.Hostname, sn.Paths[0]})
if len(sn.Paths) > 1 { firstTag := ""
for _, path := range sn.Paths[1:] { if len(sn.Tags) > 0 {
tab.Rows = append(tab.Rows, []interface{}{"", "", "", path}) firstTag = sn.Tags[0]
}
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, sn.Paths[0]})
rows := len(sn.Paths)
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]
}
tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, path})
} }
} }

View File

@ -13,9 +13,9 @@ import (
// ArchiveReader reads from the reader and archives the data. Returned is the // ArchiveReader reads from the reader and archives the data. Returned is the
// resulting snapshot and its ID. // resulting snapshot and its ID.
func ArchiveReader(repo restic.Repository, p *restic.Progress, rd io.Reader, name string) (*restic.Snapshot, restic.ID, error) { func ArchiveReader(repo restic.Repository, p *restic.Progress, rd io.Reader, name string, tags []string) (*restic.Snapshot, restic.ID, error) {
debug.Log("ArchiveReader", "start archiving %s", name) debug.Log("ArchiveReader", "start archiving %s", name)
sn, err := restic.NewSnapshot([]string{name}) sn, err := restic.NewSnapshot([]string{name}, tags)
if err != nil { if err != nil {
return nil, restic.ID{}, err return nil, restic.ID{}, err
} }

View File

@ -77,7 +77,7 @@ func TestArchiveReader(t *testing.T) {
f := fakeFile(t, seed, size) f := fakeFile(t, seed, size)
sn, id, err := ArchiveReader(repo, nil, f, "fakefile") sn, id, err := ArchiveReader(repo, nil, f, "fakefile", []string{"test"})
if err != nil { if err != nil {
t.Fatalf("ArchiveReader() returned error %v", err) t.Fatalf("ArchiveReader() returned error %v", err)
} }
@ -107,7 +107,7 @@ func BenchmarkArchiveReader(t *testing.B) {
t.ResetTimer() t.ResetTimer()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
_, _, err := ArchiveReader(repo, nil, bytes.NewReader(buf), "fakefile") _, _, err := ArchiveReader(repo, nil, bytes.NewReader(buf), "fakefile", []string{"test"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -633,7 +633,7 @@ func (p baseNameSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Snapshot creates a snapshot of the given paths. If parentrestic.ID is set, this is // Snapshot creates a snapshot of the given paths. If parentrestic.ID is set, this is
// used to compare the files to the ones archived at the time this snapshot was // used to compare the files to the ones archived at the time this snapshot was
// taken. // taken.
func (arch *Archiver) Snapshot(p *restic.Progress, paths []string, parentID *restic.ID) (*restic.Snapshot, restic.ID, error) { func (arch *Archiver) Snapshot(p *restic.Progress, paths, tags []string, parentID *restic.ID) (*restic.Snapshot, restic.ID, error) {
paths = unique(paths) paths = unique(paths)
sort.Sort(baseNameSlice(paths)) sort.Sort(baseNameSlice(paths))
@ -649,7 +649,7 @@ func (arch *Archiver) Snapshot(p *restic.Progress, paths []string, parentID *res
defer p.Done() defer p.Done()
// create new snapshot // create new snapshot
sn, err := restic.NewSnapshot(paths) sn, err := restic.NewSnapshot(paths, tags)
if err != nil { if err != nil {
return nil, restic.ID{}, err return nil, restic.ID{}, err
} }

View File

@ -104,7 +104,7 @@ func archiveDirectory(b testing.TB) {
arch := archiver.New(repo) arch := archiver.New(repo)
_, id, err := arch.Snapshot(nil, []string{BenchArchiveDirectory}, nil) _, id, err := arch.Snapshot(nil, []string{BenchArchiveDirectory}, nil, nil)
OK(b, err) OK(b, err)
b.Logf("snapshot archived as %v", id) b.Logf("snapshot archived as %v", id)

View File

@ -8,7 +8,7 @@ import (
// TestSnapshot creates a new snapshot of path. // TestSnapshot creates a new snapshot of path.
func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot { func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot {
arch := New(repo) arch := New(repo)
sn, _, err := arch.Snapshot(nil, []string{path}, parent) sn, _, err := arch.Snapshot(nil, []string{path}, []string{"test"}, parent)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -247,7 +247,7 @@ func TestCheckerModifiedData(t *testing.T) {
test.OK(t, repo.Init(test.TestPassword)) test.OK(t, repo.Init(test.TestPassword))
arch := archiver.New(repo) arch := archiver.New(repo)
_, id, err := arch.Snapshot(nil, []string{"."}, nil) _, id, err := arch.Snapshot(nil, []string{"."}, nil, nil)
test.OK(t, err) test.OK(t, err)
t.Logf("archived as %v", id.Str()) t.Logf("archived as %v", id.Str())

View File

@ -21,13 +21,14 @@ type Snapshot struct {
UID uint32 `json:"uid,omitempty"` UID uint32 `json:"uid,omitempty"`
GID uint32 `json:"gid,omitempty"` GID uint32 `json:"gid,omitempty"`
Excludes []string `json:"excludes,omitempty"` Excludes []string `json:"excludes,omitempty"`
Tags []string `json:"tags,omitempty"`
id *ID // plaintext ID, used during restore id *ID // plaintext ID, used during restore
} }
// NewSnapshot returns an initialized snapshot struct for the current user and // NewSnapshot returns an initialized snapshot struct for the current user and
// time. // time.
func NewSnapshot(paths []string) (*Snapshot, error) { func NewSnapshot(paths []string, tags []string) (*Snapshot, error) {
for i, path := range paths { for i, path := range paths {
if p, err := filepath.Abs(path); err != nil { if p, err := filepath.Abs(path); err != nil {
paths[i] = p paths[i] = p
@ -37,6 +38,7 @@ func NewSnapshot(paths []string) (*Snapshot, error) {
sn := &Snapshot{ sn := &Snapshot{
Paths: paths, Paths: paths,
Time: time.Now(), Time: time.Now(),
Tags: tags,
} }
hn, err := os.Hostname() hn, err := os.Hostname()
@ -102,6 +104,22 @@ func (sn *Snapshot) fillUserInfo() error {
return err return err
} }
// HasTags returns true if the snapshot has all the tags.
func (sn *Snapshot) HasTags(tags []string) bool {
nextTag:
for _, tag := range tags {
for _, snTag := range sn.Tags {
if tag == snTag {
continue nextTag
}
}
return false
}
return true
}
// SamePaths compares the Snapshot's paths and provided paths are exactly the same // SamePaths compares the Snapshot's paths and provided paths are exactly the same
func SamePaths(expected, actual []string) bool { func SamePaths(expected, actual []string) bool {
if expected == nil || actual == nil { if expected == nil || actual == nil {

View File

@ -31,6 +31,7 @@ type SnapshotFilter struct {
Hostname string Hostname string
Username string Username string
Paths []string Paths []string
Tags []string
} }
// FilterSnapshots returns the snapshots from s which match the filter f. // FilterSnapshots returns the snapshots from s which match the filter f.
@ -48,6 +49,10 @@ func FilterSnapshots(s Snapshots, f SnapshotFilter) (result Snapshots) {
continue continue
} }
if !snap.HasTags(f.Tags) {
continue
}
result = append(result, snap) result = append(result, snap)
} }
@ -56,12 +61,13 @@ func FilterSnapshots(s Snapshots, f SnapshotFilter) (result Snapshots) {
// ExpirePolicy configures which snapshots should be automatically removed. // ExpirePolicy configures which snapshots should be automatically removed.
type ExpirePolicy struct { type ExpirePolicy struct {
Last int // keep the last n snapshots Last int // keep the last n snapshots
Hourly int // keep the last n hourly snapshots Hourly int // keep the last n hourly snapshots
Daily int // keep the last n daily snapshots Daily int // keep the last n daily snapshots
Weekly int // keep the last n weekly snapshots Weekly int // keep the last n weekly snapshots
Monthly int // keep the last n monthly snapshots Monthly int // keep the last n monthly snapshots
Yearly int // keep the last n yearly snapshots Yearly int // keep the last n yearly snapshots
Tags []string // keep all snapshots with these tags
} }
// Sum returns the maximum number of snapshots to be kept according to this // Sum returns the maximum number of snapshots to be kept according to this
@ -72,8 +78,12 @@ func (e ExpirePolicy) Sum() int {
// Empty returns true iff no policy has been configured (all values zero). // Empty returns true iff no policy has been configured (all values zero).
func (e ExpirePolicy) Empty() bool { func (e ExpirePolicy) Empty() bool {
if len(e.Tags) != 0 {
return false
}
empty := ExpirePolicy{} empty := ExpirePolicy{}
return e == empty return reflect.DeepEqual(e, empty)
} }
// filter is used to split a list of snapshots into those to keep and those to // filter is used to split a list of snapshots into those to keep and those to
@ -161,6 +171,23 @@ func (f *filter) apply(fn func(time.Time) int, max int) {
} }
} }
// keepTags marks the snapshots which have all tags as to be kept.
func (f *filter) keepTags(tags []string) {
if len(tags) == 0 {
return
}
unprocessed := f.Unprocessed[:0]
for _, sn := range f.Unprocessed {
if sn.HasTags(tags) {
f.Keep = append(f.Keep, sn)
continue
}
unprocessed = append(unprocessed, sn)
}
f.Unprocessed = unprocessed
}
// keepLast marks the last n snapshots as to be kept. // keepLast marks the last n snapshots as to be kept.
func (f *filter) keepLast(n int) { func (f *filter) keepLast(n int) {
if n > len(f.Unprocessed) { if n > len(f.Unprocessed) {
@ -195,6 +222,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
Keep: Snapshots{}, Keep: Snapshots{},
} }
f.keepTags(p.Tags)
f.keepLast(p.Last) f.keepLast(p.Last)
f.apply(ymdh, p.Hourly) f.apply(ymdh, p.Hourly)
f.apply(ymd, p.Daily) f.apply(ymd, p.Daily)

View File

@ -22,25 +22,25 @@ func parseTimeUTC(s string) time.Time {
} }
var testFilterSnapshots = restic.Snapshots{ var testFilterSnapshots = restic.Snapshots{
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-01 01:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-01 01:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "bar", Username: "testuser", Time: parseTimeUTC("2016-01-01 01:03:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "bar", Username: "testuser", Time: parseTimeUTC("2016-01-01 01:03:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-03 07:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-03 07:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "bar", Username: "testuser", Time: parseTimeUTC("2016-01-01 07:08:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "bar", Username: "testuser", Time: parseTimeUTC("2016-01-01 07:08:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 10:23:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 10:23:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 11:23:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 11:23:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:23:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:23:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:24:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:24:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"test"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:28:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:28:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"test"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:30:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 12:30:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"test", "foo", "bar"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 16:23:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-04 16:23:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"test", "test2"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-05 09:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-05 09:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-06 08:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-06 08:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"fox"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-07 10:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-07 10:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"fox"}},
{Hostname: "foo", Username: "root", Time: parseTimeUTC("2016-01-08 20:02:03"), Paths: []string{"/usr", "/sbin"}}, {Hostname: "foo", Username: "root", Time: parseTimeUTC("2016-01-08 20:02:03"), Paths: []string{"/usr", "/sbin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "root", Time: parseTimeUTC("2016-01-09 21:02:03"), Paths: []string{"/usr", "/sbin"}}, {Hostname: "foo", Username: "root", Time: parseTimeUTC("2016-01-09 21:02:03"), Paths: []string{"/usr", "/sbin"}, Tags: []string{"fox"}},
{Hostname: "bar", Username: "root", Time: parseTimeUTC("2016-01-12 21:02:03"), Paths: []string{"/usr", "/sbin"}}, {Hostname: "bar", Username: "root", Time: parseTimeUTC("2016-01-12 21:02:03"), Paths: []string{"/usr", "/sbin"}, Tags: []string{"foo"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-12 21:08:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-12 21:08:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"bar"}},
{Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-18 12:02:03"), Paths: []string{"/usr", "/bin"}}, {Hostname: "foo", Username: "testuser", Time: parseTimeUTC("2016-01-18 12:02:03"), Paths: []string{"/usr", "/bin"}, Tags: []string{"bar"}},
} }
var filterTests = []restic.SnapshotFilter{ var filterTests = []restic.SnapshotFilter{
@ -50,6 +50,10 @@ var filterTests = []restic.SnapshotFilter{
{Paths: []string{"/usr", "/bin"}}, {Paths: []string{"/usr", "/bin"}},
{Hostname: "bar", Paths: []string{"/usr", "/bin"}}, {Hostname: "bar", Paths: []string{"/usr", "/bin"}},
{Hostname: "foo", Username: "root", Paths: []string{"/usr", "/sbin"}}, {Hostname: "foo", Username: "root", Paths: []string{"/usr", "/sbin"}},
{Tags: []string{"foo"}},
{Tags: []string{"fox"}, Username: "root"},
{Tags: []string{"foo", "test"}},
{Tags: []string{"foo", "test2"}},
} }
func TestFilterSnapshots(t *testing.T) { func TestFilterSnapshots(t *testing.T) {
@ -108,20 +112,20 @@ var testExpireSnapshots = restic.Snapshots{
{Time: parseTimeUTC("2014-08-20 10:20:30")}, {Time: parseTimeUTC("2014-08-20 10:20:30")},
{Time: parseTimeUTC("2014-08-21 10:20:30")}, {Time: parseTimeUTC("2014-08-21 10:20:30")},
{Time: parseTimeUTC("2014-08-22 10:20:30")}, {Time: parseTimeUTC("2014-08-22 10:20:30")},
{Time: parseTimeUTC("2014-10-01 10:20:30")}, {Time: parseTimeUTC("2014-10-01 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-02 10:20:30")}, {Time: parseTimeUTC("2014-10-02 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-05 10:20:30")}, {Time: parseTimeUTC("2014-10-05 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-06 10:20:30")}, {Time: parseTimeUTC("2014-10-06 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-08 10:20:30")}, {Time: parseTimeUTC("2014-10-08 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-09 10:20:30")}, {Time: parseTimeUTC("2014-10-09 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-10 10:20:30")}, {Time: parseTimeUTC("2014-10-10 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-11 10:20:30")}, {Time: parseTimeUTC("2014-10-11 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-20 10:20:30")}, {Time: parseTimeUTC("2014-10-20 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-10-22 10:20:30")}, {Time: parseTimeUTC("2014-10-22 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-11-08 10:20:30")}, {Time: parseTimeUTC("2014-11-08 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-11-10 10:20:30")}, {Time: parseTimeUTC("2014-11-10 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-11-12 10:20:30")}, {Time: parseTimeUTC("2014-11-12 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-11-13 10:20:30")}, {Time: parseTimeUTC("2014-11-13 10:20:30"), Tags: []string{"foo"}},
{Time: parseTimeUTC("2014-11-13 10:20:30")}, {Time: parseTimeUTC("2014-11-13 10:20:30")},
{Time: parseTimeUTC("2014-11-15 10:20:30")}, {Time: parseTimeUTC("2014-11-15 10:20:30")},
{Time: parseTimeUTC("2014-11-18 10:20:30")}, {Time: parseTimeUTC("2014-11-18 10:20:30")},
@ -208,6 +212,7 @@ var expireTests = []restic.ExpirePolicy{
{Daily: 2, Weekly: 2, Monthly: 6}, {Daily: 2, Weekly: 2, Monthly: 6},
{Yearly: 10}, {Yearly: 10},
{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}, {Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10},
{Tags: []string{"foo"}},
} }
func TestApplyPolicy(t *testing.T) { func TestApplyPolicy(t *testing.T) {
@ -228,13 +233,13 @@ func TestApplyPolicy(t *testing.T) {
} }
for _, sn := range keep { for _, sn := range keep {
t.Logf("test %d: keep snapshot at %v\n", i, sn.Time) t.Logf("test %d: keep snapshot at %v %s\n", i, sn.Time, sn.Tags)
} }
for _, sn := range remove { for _, sn := range remove {
t.Logf("test %d: forget snapshot at %v\n", i, sn.Time) t.Logf("test %d: forget snapshot at %v %s\n", i, sn.Time, sn.Tags)
} }
goldenFilename := filepath.Join("testdata", fmt.Sprintf("expired_snapshots_%d", i)) goldenFilename := filepath.Join("testdata", fmt.Sprintf("policy_keep_snapshots_%d", i))
if *updateGoldenFiles { if *updateGoldenFiles {
buf, err := json.MarshalIndent(keep, "", " ") buf, err := json.MarshalIndent(keep, "", " ")

View File

@ -10,6 +10,6 @@ import (
func TestNewSnapshot(t *testing.T) { func TestNewSnapshot(t *testing.T) {
paths := []string{"/home/foobar"} paths := []string{"/home/foobar"}
_, err := restic.NewSnapshot(paths) _, err := restic.NewSnapshot(paths, nil)
OK(t, err) OK(t, err)
} }

View File

@ -7,7 +7,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"bar"
]
}, },
{ {
"time": "2016-01-12T21:08:03Z", "time": "2016-01-12T21:08:03Z",
@ -17,7 +20,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"bar"
]
}, },
{ {
"time": "2016-01-09T21:02:03Z", "time": "2016-01-09T21:02:03Z",
@ -27,7 +33,10 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-08T20:02:03Z", "time": "2016-01-08T20:02:03Z",
@ -37,7 +46,10 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-07T10:02:03Z", "time": "2016-01-07T10:02:03Z",
@ -47,7 +59,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-06T08:02:03Z", "time": "2016-01-06T08:02:03Z",
@ -57,7 +72,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-05T09:02:03Z", "time": "2016-01-05T09:02:03Z",
@ -67,7 +85,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T16:23:03Z", "time": "2016-01-04T16:23:03Z",
@ -77,7 +98,11 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test",
"test2"
]
}, },
{ {
"time": "2016-01-04T12:30:03Z", "time": "2016-01-04T12:30:03Z",
@ -87,7 +112,12 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test",
"foo",
"bar"
]
}, },
{ {
"time": "2016-01-04T12:28:03Z", "time": "2016-01-04T12:28:03Z",
@ -97,7 +127,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test"
]
}, },
{ {
"time": "2016-01-04T12:24:03Z", "time": "2016-01-04T12:24:03Z",
@ -107,7 +140,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test"
]
}, },
{ {
"time": "2016-01-04T12:23:03Z", "time": "2016-01-04T12:23:03Z",
@ -117,7 +153,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T11:23:03Z", "time": "2016-01-04T11:23:03Z",
@ -127,7 +166,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T10:23:03Z", "time": "2016-01-04T10:23:03Z",
@ -137,7 +179,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-03T07:02:03Z", "time": "2016-01-03T07:02:03Z",
@ -147,7 +192,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-01T01:02:03Z", "time": "2016-01-01T01:02:03Z",
@ -157,6 +205,9 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
} }
] ]

View File

@ -7,7 +7,10 @@
"/sbin" "/sbin"
], ],
"hostname": "bar", "hostname": "bar",
"username": "root" "username": "root",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-09T21:02:03Z", "time": "2016-01-09T21:02:03Z",
@ -17,7 +20,10 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-08T20:02:03Z", "time": "2016-01-08T20:02:03Z",
@ -27,6 +33,9 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"foo"
]
} }
] ]

View File

@ -7,7 +7,10 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-08T20:02:03Z", "time": "2016-01-08T20:02:03Z",
@ -17,6 +20,9 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"foo"
]
} }
] ]

View File

@ -7,7 +7,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"bar"
]
}, },
{ {
"time": "2016-01-12T21:08:03Z", "time": "2016-01-12T21:08:03Z",
@ -17,7 +20,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"bar"
]
}, },
{ {
"time": "2016-01-07T10:02:03Z", "time": "2016-01-07T10:02:03Z",
@ -27,7 +33,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-06T08:02:03Z", "time": "2016-01-06T08:02:03Z",
@ -37,7 +46,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-05T09:02:03Z", "time": "2016-01-05T09:02:03Z",
@ -47,7 +59,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T16:23:03Z", "time": "2016-01-04T16:23:03Z",
@ -57,7 +72,11 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test",
"test2"
]
}, },
{ {
"time": "2016-01-04T12:30:03Z", "time": "2016-01-04T12:30:03Z",
@ -67,7 +86,12 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test",
"foo",
"bar"
]
}, },
{ {
"time": "2016-01-04T12:28:03Z", "time": "2016-01-04T12:28:03Z",
@ -77,7 +101,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test"
]
}, },
{ {
"time": "2016-01-04T12:24:03Z", "time": "2016-01-04T12:24:03Z",
@ -87,7 +114,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"test"
]
}, },
{ {
"time": "2016-01-04T12:23:03Z", "time": "2016-01-04T12:23:03Z",
@ -97,7 +127,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T11:23:03Z", "time": "2016-01-04T11:23:03Z",
@ -107,7 +140,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-04T10:23:03Z", "time": "2016-01-04T10:23:03Z",
@ -117,7 +153,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-03T07:02:03Z", "time": "2016-01-03T07:02:03Z",
@ -127,7 +166,10 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-01T07:08:03Z", "time": "2016-01-01T07:08:03Z",
@ -137,7 +179,10 @@
"/bin" "/bin"
], ],
"hostname": "bar", "hostname": "bar",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-01T01:03:03Z", "time": "2016-01-01T01:03:03Z",
@ -147,7 +192,10 @@
"/bin" "/bin"
], ],
"hostname": "bar", "hostname": "bar",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-01T01:02:03Z", "time": "2016-01-01T01:02:03Z",
@ -157,6 +205,9 @@
"/bin" "/bin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
} }
] ]

View File

@ -7,7 +7,10 @@
"/bin" "/bin"
], ],
"hostname": "bar", "hostname": "bar",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
}, },
{ {
"time": "2016-01-01T01:03:03Z", "time": "2016-01-01T01:03:03Z",
@ -17,6 +20,9 @@
"/bin" "/bin"
], ],
"hostname": "bar", "hostname": "bar",
"username": "testuser" "username": "testuser",
"tags": [
"foo"
]
} }
] ]

View File

@ -7,7 +7,10 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"fox"
]
}, },
{ {
"time": "2016-01-08T20:02:03Z", "time": "2016-01-08T20:02:03Z",
@ -17,6 +20,9 @@
"/sbin" "/sbin"
], ],
"hostname": "foo", "hostname": "foo",
"username": "root" "username": "root",
"tags": [
"foo"
]
} }
] ]

147
src/restic/testdata/filter_snapshots_6 vendored Normal file
View File

@ -0,0 +1,147 @@
[
{
"time": "2016-01-12T21:02:03Z",
"tree": null,
"paths": [
"/usr",
"/sbin"
],
"hostname": "bar",
"username": "root",
"tags": [
"foo"
]
},
{
"time": "2016-01-08T20:02:03Z",
"tree": null,
"paths": [
"/usr",
"/sbin"
],
"hostname": "foo",
"username": "root",
"tags": [
"foo"
]
},
{
"time": "2016-01-05T09:02:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-04T12:30:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"test",
"foo",
"bar"
]
},
{
"time": "2016-01-04T12:23:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-04T11:23:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-04T10:23:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-03T07:02:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-01T07:08:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "bar",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-01T01:03:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "bar",
"username": "testuser",
"tags": [
"foo"
]
},
{
"time": "2016-01-01T01:02:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"foo"
]
}
]

15
src/restic/testdata/filter_snapshots_7 vendored Normal file
View File

@ -0,0 +1,15 @@
[
{
"time": "2016-01-09T21:02:03Z",
"tree": null,
"paths": [
"/usr",
"/sbin"
],
"hostname": "foo",
"username": "root",
"tags": [
"fox"
]
}
]

17
src/restic/testdata/filter_snapshots_8 vendored Normal file
View File

@ -0,0 +1,17 @@
[
{
"time": "2016-01-04T12:30:03Z",
"tree": null,
"paths": [
"/usr",
"/bin"
],
"hostname": "foo",
"username": "testuser",
"tags": [
"test",
"foo",
"bar"
]
}
]

View File

@ -0,0 +1 @@
null

View File

@ -327,72 +327,114 @@
{ {
"time": "2014-11-13T10:20:30Z", "time": "2014-11-13T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-12T10:20:30Z", "time": "2014-11-12T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-10T10:20:30Z", "time": "2014-11-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-08T10:20:30Z", "time": "2014-11-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-22T10:20:30Z", "time": "2014-10-22T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-20T10:20:30Z", "time": "2014-10-20T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-11T10:20:30Z", "time": "2014-10-11T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-10T10:20:30Z", "time": "2014-10-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-09T10:20:30Z", "time": "2014-10-09T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-08T10:20:30Z", "time": "2014-10-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-06T10:20:30Z", "time": "2014-10-06T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-05T10:20:30Z", "time": "2014-10-05T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-02T10:20:30Z", "time": "2014-10-02T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-01T10:20:30Z", "time": "2014-10-01T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-09-22T10:20:30Z", "time": "2014-09-22T10:20:30Z",

View File

@ -47,6 +47,9 @@
{ {
"time": "2014-10-22T10:20:30Z", "time": "2014-10-22T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
} }
] ]

View File

@ -0,0 +1,114 @@
[
{
"time": "2014-11-13T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-11-12T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-11-10T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-11-08T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-22T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-20T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-11T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-10T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-09T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-08T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-06T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-05T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-02T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
},
{
"time": "2014-10-01T10:20:30Z",
"tree": null,
"paths": null,
"tags": [
"foo"
]
}
]

View File

@ -327,72 +327,114 @@
{ {
"time": "2014-11-13T10:20:30Z", "time": "2014-11-13T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-12T10:20:30Z", "time": "2014-11-12T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-10T10:20:30Z", "time": "2014-11-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-08T10:20:30Z", "time": "2014-11-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-22T10:20:30Z", "time": "2014-10-22T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-20T10:20:30Z", "time": "2014-10-20T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-11T10:20:30Z", "time": "2014-10-11T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-10T10:20:30Z", "time": "2014-10-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-09T10:20:30Z", "time": "2014-10-09T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-08T10:20:30Z", "time": "2014-10-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-06T10:20:30Z", "time": "2014-10-06T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-05T10:20:30Z", "time": "2014-10-05T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-02T10:20:30Z", "time": "2014-10-02T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-01T10:20:30Z", "time": "2014-10-01T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-09-22T10:20:30Z", "time": "2014-09-22T10:20:30Z",

View File

@ -327,72 +327,114 @@
{ {
"time": "2014-11-13T10:20:30Z", "time": "2014-11-13T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-12T10:20:30Z", "time": "2014-11-12T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-10T10:20:30Z", "time": "2014-11-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-11-08T10:20:30Z", "time": "2014-11-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-22T10:20:30Z", "time": "2014-10-22T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-20T10:20:30Z", "time": "2014-10-20T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-11T10:20:30Z", "time": "2014-10-11T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-10T10:20:30Z", "time": "2014-10-10T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-09T10:20:30Z", "time": "2014-10-09T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-08T10:20:30Z", "time": "2014-10-08T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-06T10:20:30Z", "time": "2014-10-06T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-05T10:20:30Z", "time": "2014-10-05T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-02T10:20:30Z", "time": "2014-10-02T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-10-01T10:20:30Z", "time": "2014-10-01T10:20:30Z",
"tree": null, "tree": null,
"paths": null "paths": null,
"tags": [
"foo"
]
}, },
{ {
"time": "2014-09-22T10:20:30Z", "time": "2014-09-22T10:20:30Z",

View File

@ -154,7 +154,7 @@ func TestCreateSnapshot(t testing.TB, repo Repository, at time.Time, depth int,
t.Logf("create fake snapshot at %s with seed %d", at, seed) t.Logf("create fake snapshot at %s with seed %d", at, seed)
fakedir := fmt.Sprintf("fakedir-at-%v", at.Format("2006-01-02 15:04:05")) fakedir := fmt.Sprintf("fakedir-at-%v", at.Format("2006-01-02 15:04:05"))
snapshot, err := NewSnapshot([]string{fakedir}) snapshot, err := NewSnapshot([]string{fakedir}, []string{"test"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -24,7 +24,7 @@ func TestWalkTree(t *testing.T) {
// archive a few files // archive a few files
arch := archiver.New(repo) arch := archiver.New(repo)
sn, _, err := arch.Snapshot(nil, dirs, nil) sn, _, err := arch.Snapshot(nil, dirs, nil, nil)
OK(t, err) OK(t, err)
// flush repo, write all packs // flush repo, write all packs