mirror of https://github.com/restic/restic.git
Refactor `forget` and `snapshots` command
Implement filtering by using `FindFilteredSnapshots()` to iterate over the snapshots Refactor cmd_snapshots' `PrintSnapshots()` so its pretty printing can be used from both `forget` and `snapshots`. Use contexts.
This commit is contained in:
parent
11d237c252
commit
3c6c17abcd
|
@ -1,14 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"restic"
|
"restic"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"restic/errors"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,13 +25,12 @@ data after 'forget' was run successfully, see the 'prune' command. `,
|
||||||
|
|
||||||
// ForgetOptions collects all options for the forget command.
|
// ForgetOptions collects all options for the forget command.
|
||||||
type ForgetOptions struct {
|
type ForgetOptions struct {
|
||||||
Last int
|
Last int
|
||||||
Hourly int
|
Hourly int
|
||||||
Daily int
|
Daily int
|
||||||
Weekly int
|
Weekly int
|
||||||
Monthly int
|
Monthly int
|
||||||
Yearly int
|
Yearly int
|
||||||
|
|
||||||
KeepTags []string
|
KeepTags []string
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
|
@ -83,32 +80,43 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all snapshot IDs given as arguments.
|
// group by hostname and dirs
|
||||||
if len(args) != 0 {
|
type key struct {
|
||||||
for _, s := range args {
|
Hostname string
|
||||||
// Parse argument as hex string.
|
Paths []string
|
||||||
if _, err := hex.DecodeString(s); err != nil {
|
Tags []string
|
||||||
Warnf("argument %q is not a snapshot ID, ignoring\n", s)
|
}
|
||||||
continue
|
snapshotGroups := make(map[string]restic.Snapshots)
|
||||||
}
|
|
||||||
id, err := restic.FindSnapshot(repo, s)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("could not find a snapshot for ID %q, ignoring\n", s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
|
||||||
|
if len(args) > 0 {
|
||||||
|
// When explicit snapshots args are given, remove them immediately.
|
||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
h := restic.Handle{Type: restic.SnapshotFile, Name: id.String()}
|
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
|
||||||
err = repo.Backend().Remove(h)
|
if err = repo.Backend().Remove(h); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Verbosef("removed snapshot %v\n", sn.ID().Str())
|
||||||
Verbosef("removed snapshot %v\n", id.Str())
|
|
||||||
} else {
|
} else {
|
||||||
Verbosef("would remove snapshot %v\n", id.Str())
|
Verbosef("would have removed snapshot %v\n", sn.ID().Str())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
var tags []string
|
||||||
|
if opts.GroupByTags {
|
||||||
|
tags = sn.Tags
|
||||||
|
sort.StringSlice(tags).Sort()
|
||||||
|
}
|
||||||
|
sort.StringSlice(sn.Paths).Sort()
|
||||||
|
k, err := json.Marshal(key{Hostname: sn.Hostname, Tags: tags, Paths: sn.Paths})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,53 +130,17 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||||
Tags: opts.KeepTags,
|
Tags: opts.KeepTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshots, err := restic.LoadAllSnapshots(repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group snapshots by hostname and dirs.
|
|
||||||
type key struct {
|
|
||||||
Hostname string
|
|
||||||
Paths []string
|
|
||||||
Tags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotGroups := make(map[string]restic.Snapshots)
|
|
||||||
|
|
||||||
for _, sn := range snapshots {
|
|
||||||
if opts.Host != "" && sn.Hostname != opts.Host {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sn.HasTags(opts.Tags) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sn.HasPaths(opts.Paths) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var tags []string
|
|
||||||
if opts.GroupByTags {
|
|
||||||
sort.StringSlice(sn.Tags).Sort()
|
|
||||||
tags = sn.Tags
|
|
||||||
}
|
|
||||||
sort.StringSlice(sn.Paths).Sort()
|
|
||||||
k, _ := json.Marshal(key{Hostname: sn.Hostname, Tags: tags, Paths: sn.Paths})
|
|
||||||
snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
|
|
||||||
}
|
|
||||||
if len(snapshotGroups) == 0 {
|
|
||||||
return errors.Fatal("no snapshots remained after filtering")
|
|
||||||
}
|
|
||||||
if policy.Empty() {
|
if policy.Empty() {
|
||||||
Verbosef("no policy was specified, no snapshots will be removed\n")
|
Verbosef("no policy was specified, no snapshots will be removed\n")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSnapshots := 0
|
removeSnapshots := 0
|
||||||
for k, snapshotGroup := range snapshotGroups {
|
for k, snapshotGroup := range snapshotGroups {
|
||||||
var key key
|
var key key
|
||||||
json.Unmarshal([]byte(k), &key)
|
if json.Unmarshal([]byte(k), &key) != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if opts.GroupByTags {
|
if opts.GroupByTags {
|
||||||
Printf("snapshots for host %v, tags [%v], paths: [%v]:\n\n", key.Hostname, strings.Join(key.Tags, ", "), strings.Join(key.Paths, ", "))
|
Printf("snapshots for host %v, tags [%v], paths: [%v]:\n\n", key.Hostname, strings.Join(key.Tags, ", "), strings.Join(key.Paths, ", "))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"restic/errors"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
"restic"
|
"restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdSnapshots = &cobra.Command{
|
var cmdSnapshots = &cobra.Command{
|
||||||
Use: "snapshots",
|
Use: "snapshots [snapshotID ...]",
|
||||||
Short: "list all snapshots",
|
Short: "list all snapshots",
|
||||||
Long: `
|
Long: `
|
||||||
The "snapshots" command lists all snapshots stored in the repository.
|
The "snapshots" command lists all snapshots stored in the repository.
|
||||||
|
@ -26,6 +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
|
||||||
Paths []string
|
Paths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,15 +36,12 @@ func init() {
|
||||||
cmdRoot.AddCommand(cmdSnapshots)
|
cmdRoot.AddCommand(cmdSnapshots)
|
||||||
|
|
||||||
f := cmdSnapshots.Flags()
|
f := cmdSnapshots.Flags()
|
||||||
f.StringVar(&snapshotOptions.Host, "host", "", "only print snapshots for this host")
|
f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`")
|
||||||
f.StringSliceVar(&snapshotOptions.Paths, "path", []string{}, "only print snapshots for this `path` (can be specified multiple times)")
|
f.StringSliceVar(&snapshotOptions.Tags, "tag", nil, "only consider snapshots which include this `tag` (can be specified multiple times)")
|
||||||
|
f.StringSliceVar(&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 {
|
||||||
if len(args) != 0 {
|
|
||||||
return errors.Fatal("wrong number of arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := OpenRepository(gopts)
|
repo, err := OpenRepository(gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -57,32 +55,14 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
ctx, cancel := context.WithCancel(gopts.ctx)
|
||||||
defer close(done)
|
defer cancel()
|
||||||
|
|
||||||
list := []*restic.Snapshot{}
|
|
||||||
for id := range repo.List(restic.SnapshotFile, done) {
|
|
||||||
sn, err := restic.LoadSnapshot(repo, id)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("error loading snapshot %s: %v\n", id, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.Host == "" || opts.Host == sn.Hostname) && sn.HasPaths(opts.Paths) {
|
|
||||||
pos := sort.Search(len(list), func(i int) bool {
|
|
||||||
return list[i].Time.After(sn.Time)
|
|
||||||
})
|
|
||||||
|
|
||||||
if pos < len(list) {
|
|
||||||
list = append(list, nil)
|
|
||||||
copy(list[pos+1:], list[pos:])
|
|
||||||
list[pos] = sn
|
|
||||||
} else {
|
|
||||||
list = append(list, sn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var list restic.Snapshots
|
||||||
|
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) {
|
||||||
|
list = append(list, sn)
|
||||||
}
|
}
|
||||||
|
sort.Sort(sort.Reverse(list))
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
err := printSnapshotsJSON(gopts.stdout, list)
|
err := printSnapshotsJSON(gopts.stdout, list)
|
||||||
|
@ -97,7 +77,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.Snapshot) {
|
func PrintSnapshots(stdout io.Writer, list restic.Snapshots) {
|
||||||
|
|
||||||
// Determine the max widths for host and tag.
|
// Determine the max widths for host and tag.
|
||||||
maxHost, maxTag := 10, 6
|
maxHost, maxTag := 10, 6
|
||||||
|
@ -165,7 +145,7 @@ func PrintSnapshots(stdout io.Writer, list []*restic.Snapshot) {
|
||||||
tab.Write(stdout)
|
tab.Write(stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot helps to print Snaphots as JSON
|
// Snapshot helps to print Snaphots as JSON with their ID included.
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
*restic.Snapshot
|
*restic.Snapshot
|
||||||
|
|
||||||
|
@ -173,7 +153,7 @@ type Snapshot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// printSnapshotsJSON writes the JSON representation of list to stdout.
|
// printSnapshotsJSON writes the JSON representation of list to stdout.
|
||||||
func printSnapshotsJSON(stdout io.Writer, list []*restic.Snapshot) error {
|
func printSnapshotsJSON(stdout io.Writer, list restic.Snapshots) error {
|
||||||
|
|
||||||
var snapshots []Snapshot
|
var snapshots []Snapshot
|
||||||
|
|
||||||
|
@ -187,5 +167,4 @@ func printSnapshotsJSON(stdout io.Writer, list []*restic.Snapshot) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.NewEncoder(stdout).Encode(snapshots)
|
return json.NewEncoder(stdout).Encode(snapshots)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue