mirror of
https://github.com/octoleo/restic.git
synced 2024-11-29 08:14:03 +00:00
Merge pull request #1090 from middelink/fix-1081
Update HasTags() and HasPaths() to follow #1081 feature request
This commit is contained in:
commit
16340ce811
@ -48,6 +48,11 @@ Small changes
|
|||||||
https://github.com/restic/restic/pull/1080
|
https://github.com/restic/restic/pull/1080
|
||||||
https://github.com/restic/restic/pull/1112
|
https://github.com/restic/restic/pull/1112
|
||||||
|
|
||||||
|
* The semantic for the `--tags` option to `forget` and `snapshots` was
|
||||||
|
clarified:
|
||||||
|
https://github.com/restic/restic/issues/1081
|
||||||
|
https://github.com/restic/restic/pull/1090
|
||||||
|
|
||||||
Important Changes in 0.7.0
|
Important Changes in 0.7.0
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
@ -526,18 +526,20 @@ specified 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
|
Tags for backup
|
||||||
~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Snapshots can have one or more tags, short strings which add identifying
|
Snapshots can have one or more tags, short strings which add identifying
|
||||||
information. Just specify the tags for a snapshot with ``--tag``:
|
information. Just specify the tags for a snapshot one by one with ``--tag``:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ restic -r /tmp/backup backup --tag projectX ~/shared/work/web
|
$ restic -r /tmp/backup backup --tag projectX -tag foo --tag bar ~/shared/work/web
|
||||||
[...]
|
[...]
|
||||||
|
|
||||||
The tags can later be used to keep (or forget) snapshots.
|
The tags can later be used to keep (or forget) snapshots with the ``forget``
|
||||||
|
command. The command ``tag`` can be used to modify tags on an existing
|
||||||
|
snapshot.
|
||||||
|
|
||||||
List all snapshots
|
List all snapshots
|
||||||
------------------
|
------------------
|
||||||
@ -644,7 +646,7 @@ command does that:
|
|||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ restic -r /tmp/backup tag --set NL,CH 590c8fc8
|
$ restic -r /tmp/backup tag --set NL --set CH 590c8fc8
|
||||||
Create exclusive lock for repository
|
Create exclusive lock for repository
|
||||||
Modified tags on 1 snapshots
|
Modified tags on 1 snapshots
|
||||||
|
|
||||||
@ -872,7 +874,26 @@ The ``forget`` command accepts the following parameters:
|
|||||||
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, or tags with the
|
particular hostname with the ``--hostname`` parameter, or tags with the
|
||||||
``--tag`` option. When multiple tags are specified, only the snapshots
|
``--tag`` option. When multiple tags are specified, only the snapshots
|
||||||
which have all the tags are considered.
|
which have all the tags are considered. For example, the following command
|
||||||
|
removes all but the latest snapshot of all snapshots that have the tag ``foo``:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ restic forget --tag foo --keep-last 1
|
||||||
|
|
||||||
|
This command removes all but the last snapshot of all snapshots that have
|
||||||
|
either the ``foo`` or ``bar`` tag set:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ restic forget --tag foo --tag bar --keep-last 1
|
||||||
|
|
||||||
|
To only keep the last snapshot of all snapshots with both the tag ``foo`` and
|
||||||
|
``bar`` set use:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ restic forget --tag foo,tag bar --keep-last 1
|
||||||
|
|
||||||
All the ``--keep-*`` options above only count
|
All the ``--keep-*`` options above only count
|
||||||
hours/days/weeks/months/years which have a snapshot, so those without a
|
hours/days/weeks/months/years which have a snapshot, so those without a
|
||||||
|
@ -68,12 +68,12 @@ func init() {
|
|||||||
f := cmdBackup.Flags()
|
f := cmdBackup.Flags()
|
||||||
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)")
|
f.StringVar(&backupOptions.Parent, "parent", "", "use this parent snapshot (default: last snapshot in the repo that has the same target files/directories)")
|
||||||
f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories (overrides the "parent" flag)`)
|
f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the target files/directories (overrides the "parent" flag)`)
|
||||||
f.StringSliceVarP(&backupOptions.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
|
f.StringArrayVarP(&backupOptions.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
|
||||||
f.StringSliceVar(&backupOptions.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)")
|
f.StringArrayVar(&backupOptions.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)")
|
||||||
f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems")
|
f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems")
|
||||||
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
|
f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin")
|
||||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "file name to use when reading from stdin")
|
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "file name to use when reading from stdin")
|
||||||
f.StringSliceVar(&backupOptions.Tags, "tag", nil, "add a `tag` for the new snapshot (can be specified multiple times)")
|
f.StringArrayVar(&backupOptions.Tags, "tag", nil, "add a `tag` for the new snapshot (can be specified multiple times)")
|
||||||
f.StringVar(&backupOptions.Hostname, "hostname", hostname, "set the `hostname` for the snapshot manually")
|
f.StringVar(&backupOptions.Hostname, "hostname", hostname, "set the `hostname` for the snapshot manually")
|
||||||
f.StringVar(&backupOptions.FilesFrom, "files-from", "", "read the files to backup from file (can be combined with file args)")
|
f.StringVar(&backupOptions.FilesFrom, "files-from", "", "read the files to backup from file (can be combined with file args)")
|
||||||
}
|
}
|
||||||
@ -392,7 +392,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, args []string) error {
|
|||||||
|
|
||||||
// Find last snapshot to set it as parent, if not already set
|
// Find last snapshot to set it as parent, if not already set
|
||||||
if !opts.Force && parentSnapshotID == nil {
|
if !opts.Force && parentSnapshotID == nil {
|
||||||
id, err := restic.FindLatestSnapshot(context.TODO(), repo, target, opts.Tags, opts.Hostname)
|
id, err := restic.FindLatestSnapshot(context.TODO(), repo, target, []restic.TagList{opts.Tags}, opts.Hostname)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
parentSnapshotID = &id
|
parentSnapshotID = &id
|
||||||
} else if err != restic.ErrNoSnapshotFound {
|
} else if err != restic.ErrNoSnapshotFound {
|
||||||
|
@ -34,7 +34,7 @@ type FindOptions struct {
|
|||||||
ListLong bool
|
ListLong bool
|
||||||
Host string
|
Host string
|
||||||
Paths []string
|
Paths []string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
}
|
}
|
||||||
|
|
||||||
var findOptions FindOptions
|
var findOptions FindOptions
|
||||||
@ -45,13 +45,13 @@ func init() {
|
|||||||
f := cmdFind.Flags()
|
f := cmdFind.Flags()
|
||||||
f.StringVarP(&findOptions.Oldest, "oldest", "O", "", "oldest modification date/time")
|
f.StringVarP(&findOptions.Oldest, "oldest", "O", "", "oldest modification date/time")
|
||||||
f.StringVarP(&findOptions.Newest, "newest", "N", "", "newest modification date/time")
|
f.StringVarP(&findOptions.Newest, "newest", "N", "", "newest modification date/time")
|
||||||
f.StringSliceVarP(&findOptions.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)")
|
f.StringArrayVarP(&findOptions.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)")
|
||||||
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
|
f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern")
|
||||||
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||||
|
|
||||||
f.StringVarP(&findOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
f.StringVarP(&findOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||||
f.StringSliceVar(&findOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot-ID is given")
|
f.Var(&findOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
|
||||||
f.StringSliceVar(&findOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
f.StringArrayVar(&findOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
||||||
}
|
}
|
||||||
|
|
||||||
type findPattern struct {
|
type findPattern struct {
|
||||||
|
@ -31,10 +31,10 @@ type ForgetOptions struct {
|
|||||||
Weekly int
|
Weekly int
|
||||||
Monthly int
|
Monthly int
|
||||||
Yearly int
|
Yearly int
|
||||||
KeepTags []string
|
KeepTags restic.TagLists
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
Paths []string
|
Paths []string
|
||||||
|
|
||||||
GroupByTags bool
|
GroupByTags bool
|
||||||
@ -55,14 +55,14 @@ func init() {
|
|||||||
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
|
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
|
||||||
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
|
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
|
||||||
|
|
||||||
f.StringSliceVar(&forgetOptions.KeepTags, "keep-tag", []string{}, "keep snapshots with this `tag` (can be specified multiple times)")
|
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
|
||||||
f.BoolVarP(&forgetOptions.GroupByTags, "group-by-tags", "G", false, "Group by host,paths,tags instead of just host,paths")
|
f.BoolVarP(&forgetOptions.GroupByTags, "group-by-tags", "G", false, "Group by host,paths,tags instead of just host,paths")
|
||||||
// Sadly the commonly used shortcut `H` is already used.
|
// Sadly the commonly used shortcut `H` is already used.
|
||||||
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
|
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
|
||||||
// Deprecated since 2017-03-07.
|
// Deprecated since 2017-03-07.
|
||||||
f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)")
|
f.StringVar(&forgetOptions.Host, "hostname", "", "only consider snapshots with the given `hostname` (deprecated)")
|
||||||
f.StringSliceVar(&forgetOptions.Tags, "tag", nil, "only consider snapshots which include this `tag` (can be specified multiple times)")
|
f.Var(&forgetOptions.Tags, "tag", "only consider snapshots which include this `taglist` in the format `tag[,tag,...]` (can be specified multiple times)")
|
||||||
f.StringSliceVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)")
|
f.StringArrayVar(&forgetOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` (can be specified multiple times)")
|
||||||
|
|
||||||
f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
|
f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
|
||||||
f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
|
f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
|
||||||
|
@ -28,7 +28,7 @@ The special snapshot-ID "latest" can be used to list files and directories of th
|
|||||||
type LsOptions struct {
|
type LsOptions struct {
|
||||||
ListLong bool
|
ListLong bool
|
||||||
Host string
|
Host string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
Paths []string
|
Paths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,8 +41,8 @@ func init() {
|
|||||||
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode")
|
||||||
|
|
||||||
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
flags.StringVarP(&lsOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||||
flags.StringSliceVar(&lsOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot ID is given")
|
flags.Var(&lsOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot ID is given")
|
||||||
flags.StringSliceVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
|
flags.StringArrayVar(&lsOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot ID is given")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTree(repo *repository.Repository, id *restic.ID, prefix string) error {
|
func printTree(repo *repository.Repository, id *restic.ID, prefix string) error {
|
||||||
|
@ -6,6 +6,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
"restic"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ type MountOptions struct {
|
|||||||
AllowRoot bool
|
AllowRoot bool
|
||||||
AllowOther bool
|
AllowOther bool
|
||||||
Host string
|
Host string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
Paths []string
|
Paths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +53,8 @@ func init() {
|
|||||||
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
|
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
|
||||||
|
|
||||||
mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
|
mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
|
||||||
mountFlags.StringSliceVar(&mountOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`")
|
mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`")
|
||||||
mountFlags.StringSliceVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
|
mountFlags.StringArrayVar(&mountOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
||||||
|
@ -31,7 +31,7 @@ type RestoreOptions struct {
|
|||||||
Target string
|
Target string
|
||||||
Host string
|
Host string
|
||||||
Paths []string
|
Paths []string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
}
|
}
|
||||||
|
|
||||||
var restoreOptions RestoreOptions
|
var restoreOptions RestoreOptions
|
||||||
@ -40,13 +40,13 @@ func init() {
|
|||||||
cmdRoot.AddCommand(cmdRestore)
|
cmdRoot.AddCommand(cmdRestore)
|
||||||
|
|
||||||
flags := cmdRestore.Flags()
|
flags := cmdRestore.Flags()
|
||||||
flags.StringSliceVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
|
flags.StringArrayVarP(&restoreOptions.Exclude, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)")
|
||||||
flags.StringSliceVarP(&restoreOptions.Include, "include", "i", nil, "include a `pattern`, exclude everything else (can be specified multiple times)")
|
flags.StringArrayVarP(&restoreOptions.Include, "include", "i", nil, "include a `pattern`, exclude everything else (can be specified multiple times)")
|
||||||
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
|
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
|
||||||
|
|
||||||
flags.StringVarP(&restoreOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
|
flags.StringVarP(&restoreOptions.Host, "host", "H", "", `only consider snapshots for this host when the snapshot ID is "latest"`)
|
||||||
flags.StringSliceVar(&restoreOptions.Tags, "tag", nil, "only consider snapshots which include this `tag` for snapshot ID \"latest\"")
|
flags.Var(&restoreOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
|
||||||
flags.StringSliceVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
flags.StringArrayVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
||||||
|
@ -26,7 +26,7 @@ The "snapshots" command lists all snapshots stored in the repository.
|
|||||||
// SnapshotOptions bundles all options for the snapshots command.
|
// SnapshotOptions bundles all options for the snapshots command.
|
||||||
type SnapshotOptions struct {
|
type SnapshotOptions struct {
|
||||||
Host string
|
Host string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
Paths []string
|
Paths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,8 +37,8 @@ func init() {
|
|||||||
|
|
||||||
f := cmdSnapshots.Flags()
|
f := cmdSnapshots.Flags()
|
||||||
f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`")
|
f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`")
|
||||||
f.StringSliceVar(&snapshotOptions.Tags, "tag", nil, "only consider snapshots which include this `tag` (can be specified multiple times)")
|
f.Var(&snapshotOptions.Tags, "tag", "only consider snapshots which include this `taglist` (can be specified multiple times)")
|
||||||
f.StringSliceVar(&snapshotOptions.Paths, "path", nil, "only consider snapshots for this `path` (can be specified multiple times)")
|
f.StringArrayVar(&snapshotOptions.Paths, "path", nil, "only consider snapshots for this `path` (can be specified multiple times)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error {
|
func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error {
|
||||||
|
@ -31,7 +31,7 @@ When no snapshot-ID is given, all snapshots matching the host, tag and path filt
|
|||||||
type TagOptions struct {
|
type TagOptions struct {
|
||||||
Host string
|
Host string
|
||||||
Paths []string
|
Paths []string
|
||||||
Tags []string
|
Tags restic.TagLists
|
||||||
SetTags []string
|
SetTags []string
|
||||||
AddTags []string
|
AddTags []string
|
||||||
RemoveTags []string
|
RemoveTags []string
|
||||||
@ -48,8 +48,8 @@ func init() {
|
|||||||
tagFlags.StringSliceVar(&tagOptions.RemoveTags, "remove", nil, "`tag` which will be removed from the existing tags (can be given multiple times)")
|
tagFlags.StringSliceVar(&tagOptions.RemoveTags, "remove", nil, "`tag` which will be removed from the existing tags (can be given multiple times)")
|
||||||
|
|
||||||
tagFlags.StringVarP(&tagOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
tagFlags.StringVarP(&tagOptions.Host, "host", "H", "", "only consider snapshots for this `host`, when no snapshot ID is given")
|
||||||
tagFlags.StringSliceVar(&tagOptions.Tags, "tag", nil, "only consider snapshots which include this `tag`, when no snapshot-ID is given")
|
tagFlags.Var(&tagOptions.Tags, "tag", "only consider snapshots which include this `taglist`, when no snapshot-ID is given")
|
||||||
tagFlags.StringSliceVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
tagFlags.StringArrayVar(&tagOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path`, when no snapshot-ID is given")
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeTags(repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string) (bool, error) {
|
func changeTags(repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string) (bool, error) {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||||
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, host string, tags []string, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
|
func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, host string, tags []restic.TagList, paths []string, snapshotIDs []string) <-chan *restic.Snapshot {
|
||||||
out := make(chan *restic.Snapshot)
|
out := make(chan *restic.Snapshot)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
OwnerIsRoot bool
|
OwnerIsRoot bool
|
||||||
Host string
|
Host string
|
||||||
Tags []string
|
Tags []restic.TagList
|
||||||
Paths []string
|
Paths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"restic/debug"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -130,46 +131,65 @@ func (sn *Snapshot) RemoveTags(removeTags []string) (changed bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasTags returns true if the snapshot has at least all of tags.
|
func (sn *Snapshot) hasTag(tag string) bool {
|
||||||
func (sn *Snapshot) HasTags(tags []string) bool {
|
|
||||||
nextTag:
|
|
||||||
for _, tag := range tags {
|
|
||||||
for _, snTag := range sn.Tags {
|
for _, snTag := range sn.Tags {
|
||||||
if tag == snTag {
|
if tag == snTag {
|
||||||
continue nextTag
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTags returns true if the snapshot has all the tags in l.
|
||||||
|
func (sn *Snapshot) HasTags(l []string) bool {
|
||||||
|
for _, tag := range l {
|
||||||
|
if !sn.hasTag(tag) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTagList returns true if the snapshot satisfies at least one TagList,
|
||||||
|
// so there is a TagList in l for which all tags are included in sn.
|
||||||
|
func (sn *Snapshot) HasTagList(l []TagList) bool {
|
||||||
|
debug.Log("testing snapshot with tags %v against list: %v", sn.Tags, l)
|
||||||
|
|
||||||
|
if len(l) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tags := range l {
|
||||||
|
if sn.HasTags(tags) {
|
||||||
|
debug.Log(" snapshot satisfies %v", tags, l)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
func (sn *Snapshot) hasPath(path string) bool {
|
||||||
}
|
|
||||||
|
|
||||||
// HasPaths returns true if the snapshot has at least all of paths.
|
|
||||||
func (sn *Snapshot) HasPaths(paths []string) bool {
|
|
||||||
nextPath:
|
|
||||||
for _, path := range paths {
|
|
||||||
for _, snPath := range sn.Paths {
|
for _, snPath := range sn.Paths {
|
||||||
if path == snPath {
|
if path == snPath {
|
||||||
continue nextPath
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPaths returns true if the snapshot has all of the paths.
|
||||||
|
func (sn *Snapshot) HasPaths(paths []string) bool {
|
||||||
|
for _, path := range paths {
|
||||||
|
if !sn.hasPath(path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SamePaths returns true if the snapshot matches the entire paths set
|
|
||||||
func (sn *Snapshot) SamePaths(paths []string) bool {
|
|
||||||
if len(sn.Paths) != len(paths) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return sn.HasPaths(paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshots is a list of snapshots.
|
// Snapshots is a list of snapshots.
|
||||||
type Snapshots []*Snapshot
|
type Snapshots []*Snapshot
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
||||||
|
|
||||||
// FindLatestSnapshot finds latest snapshot with optional target/directory, tags and hostname filters.
|
// FindLatestSnapshot finds latest snapshot with optional target/directory, tags and hostname filters.
|
||||||
func FindLatestSnapshot(ctx context.Context, repo Repository, targets []string, tags []string, hostname string) (ID, error) {
|
func FindLatestSnapshot(ctx context.Context, repo Repository, targets []string, tagLists []TagList, hostname string) (ID, error) {
|
||||||
var (
|
var (
|
||||||
latest time.Time
|
latest time.Time
|
||||||
latestID ID
|
latestID ID
|
||||||
@ -24,12 +24,22 @@ func FindLatestSnapshot(ctx context.Context, repo Repository, targets []string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ID{}, errors.Errorf("Error listing snapshot: %v", err)
|
return ID{}, errors.Errorf("Error listing snapshot: %v", err)
|
||||||
}
|
}
|
||||||
if snapshot.Time.After(latest) && (hostname == "" || hostname == snapshot.Hostname) && snapshot.HasTags(tags) && snapshot.HasPaths(targets) {
|
if snapshot.Time.Before(latest) || (hostname != "" && hostname != snapshot.Hostname) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !snapshot.HasTagList(tagLists) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !snapshot.HasPaths(targets) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
latest = snapshot.Time
|
latest = snapshot.Time
|
||||||
latestID = snapshotID
|
latestID = snapshotID
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return ID{}, ErrNoSnapshotFound
|
return ID{}, ErrNoSnapshotFound
|
||||||
@ -53,7 +63,7 @@ func FindSnapshot(repo Repository, s string) (ID, error) {
|
|||||||
|
|
||||||
// FindFilteredSnapshots yields Snapshots filtered from the list of all
|
// FindFilteredSnapshots yields Snapshots filtered from the list of all
|
||||||
// snapshots.
|
// snapshots.
|
||||||
func FindFilteredSnapshots(ctx context.Context, repo Repository, host string, tags []string, paths []string) Snapshots {
|
func FindFilteredSnapshots(ctx context.Context, repo Repository, host string, tags []TagList, paths []string) Snapshots {
|
||||||
results := make(Snapshots, 0, 20)
|
results := make(Snapshots, 0, 20)
|
||||||
|
|
||||||
for id := range repo.List(ctx, SnapshotFile) {
|
for id := range repo.List(ctx, SnapshotFile) {
|
||||||
@ -62,7 +72,7 @@ func FindFilteredSnapshots(ctx context.Context, repo Repository, host string, ta
|
|||||||
fmt.Fprintf(os.Stderr, "could not load snapshot %v: %v\n", id.Str(), err)
|
fmt.Fprintf(os.Stderr, "could not load snapshot %v: %v\n", id.Str(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (host != "" && host != sn.Hostname) || !sn.HasTags(tags) || !sn.HasPaths(paths) {
|
if (host != "" && host != sn.Hostname) || !sn.HasTagList(tags) || !sn.HasPaths(paths) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ type ExpirePolicy struct {
|
|||||||
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
|
Tags []TagList // keep all snapshots that include at least one of the tag lists.
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -94,11 +94,12 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots) {
|
|||||||
var keepSnap bool
|
var keepSnap bool
|
||||||
|
|
||||||
// Tags are handled specially as they are not counted.
|
// Tags are handled specially as they are not counted.
|
||||||
if len(p.Tags) > 0 {
|
for _, l := range p.Tags {
|
||||||
if cur.HasTags(p.Tags) {
|
if cur.HasTags(l) {
|
||||||
keepSnap = true
|
keepSnap = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now update the other buckets and see if they have some counts left.
|
// Now update the other buckets and see if they have some counts left.
|
||||||
for i, b := range buckets {
|
for i, b := range buckets {
|
||||||
if b.Count > 0 {
|
if b.Count > 0 {
|
||||||
|
@ -27,7 +27,7 @@ func TestExpireSnapshotOps(t *testing.T) {
|
|||||||
p *restic.ExpirePolicy
|
p *restic.ExpirePolicy
|
||||||
}{
|
}{
|
||||||
{true, 0, &restic.ExpirePolicy{}},
|
{true, 0, &restic.ExpirePolicy{}},
|
||||||
{true, 0, &restic.ExpirePolicy{Tags: []string{}}},
|
{true, 0, &restic.ExpirePolicy{Tags: []restic.TagList{}}},
|
||||||
{false, 22, &restic.ExpirePolicy{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}},
|
{false, 22, &restic.ExpirePolicy{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}},
|
||||||
}
|
}
|
||||||
for i, d := range data {
|
for i, d := range data {
|
||||||
@ -163,8 +163,9 @@ 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"}},
|
{Tags: []restic.TagList{{"foo"}}},
|
||||||
{Tags: []string{"foo", "bar"}},
|
{Tags: []restic.TagList{{"foo", "bar"}}},
|
||||||
|
{Tags: []restic.TagList{{"foo"}, {"bar"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyPolicy(t *testing.T) {
|
func TestApplyPolicy(t *testing.T) {
|
62
src/restic/tag_list.go
Normal file
62
src/restic/tag_list.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package restic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TagList is a list of tags.
|
||||||
|
type TagList []string
|
||||||
|
|
||||||
|
// splitTagList splits a string into a list of tags. The tags in the string
|
||||||
|
// need to be separated by commas. Whitespace is stripped around the individual
|
||||||
|
// tags.
|
||||||
|
func splitTagList(s string) (l TagList) {
|
||||||
|
for _, t := range strings.Split(s, ",") {
|
||||||
|
l = append(l, strings.TrimSpace(t))
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l TagList) String() string {
|
||||||
|
return "[" + strings.Join(l, ", ") + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set updates the TagList's value.
|
||||||
|
func (l *TagList) Set(s string) error {
|
||||||
|
*l = splitTagList(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a description of the type.
|
||||||
|
func (TagList) Type() string {
|
||||||
|
return "TagList"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagLists consists of several TagList.
|
||||||
|
type TagLists []TagList
|
||||||
|
|
||||||
|
// splitTagLists splits a slice of strings into a slice of TagLists using
|
||||||
|
// SplitTagList.
|
||||||
|
func splitTagLists(s []string) (l TagLists) {
|
||||||
|
l = make([]TagList, 0, len(s))
|
||||||
|
for _, t := range s {
|
||||||
|
l = append(l, splitTagList(t))
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l TagLists) String() string {
|
||||||
|
return fmt.Sprintf("%v", []TagList(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set updates the TagList's value.
|
||||||
|
func (l *TagLists) Set(s string) error {
|
||||||
|
*l = append(*l, splitTagList(s))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a description of the type.
|
||||||
|
func (TagLists) Type() string {
|
||||||
|
return "TagLists"
|
||||||
|
}
|
131
src/restic/testdata/policy_keep_snapshots_20
vendored
Normal file
131
src/restic/testdata/policy_keep_snapshots_20
vendored
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"time": "2014-11-15T10:20:30Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time": "2014-11-13T10:20:30.1Z",
|
||||||
|
"tree": null,
|
||||||
|
"paths": null,
|
||||||
|
"tags": [
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user