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:
Pauline Middelink 2017-03-08 20:24:58 +01:00
parent 11d237c252
commit 3c6c17abcd
2 changed files with 57 additions and 106 deletions

View File

@ -1,14 +1,12 @@
package main
import (
"encoding/hex"
"context"
"encoding/json"
"restic"
"sort"
"strings"
"restic/errors"
"github.com/spf13/cobra"
)
@ -33,7 +31,6 @@ type ForgetOptions struct {
Weekly int
Monthly int
Yearly int
KeepTags []string
Host string
@ -83,32 +80,43 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
return err
}
// Process all snapshot IDs given as arguments.
if len(args) != 0 {
for _, s := range args {
// Parse argument as hex string.
if _, err := hex.DecodeString(s); err != nil {
Warnf("argument %q is not a snapshot ID, ignoring\n", s)
continue
}
id, err := restic.FindSnapshot(repo, s)
if err != nil {
Warnf("could not find a snapshot for ID %q, ignoring\n", s)
continue
// group by hostname and dirs
type key struct {
Hostname string
Paths []string
Tags []string
}
snapshotGroups := make(map[string]restic.Snapshots)
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 {
h := restic.Handle{Type: restic.SnapshotFile, Name: id.String()}
err = repo.Backend().Remove(h)
h := restic.Handle{Type: restic.SnapshotFile, Name: sn.ID().String()}
if err = repo.Backend().Remove(h); err != nil {
return err
}
Verbosef("removed snapshot %v\n", sn.ID().Str())
} else {
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
}
Verbosef("removed snapshot %v\n", id.Str())
} else {
Verbosef("would remove snapshot %v\n", id.Str())
snapshotGroups[string(k)] = append(snapshotGroups[string(k)], sn)
}
}
if len(args) > 0 {
return nil
}
@ -122,53 +130,17 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
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() {
Verbosef("no policy was specified, no snapshots will be removed\n")
return nil
}
removeSnapshots := 0
for k, snapshotGroup := range snapshotGroups {
var key key
json.Unmarshal([]byte(k), &key)
if json.Unmarshal([]byte(k), &key) != nil {
return err
}
if opts.GroupByTags {
Printf("snapshots for host %v, tags [%v], paths: [%v]:\n\n", key.Hostname, strings.Join(key.Tags, ", "), strings.Join(key.Paths, ", "))
} else {

View File

@ -1,19 +1,19 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"restic/errors"
"sort"
"github.com/spf13/cobra"
"encoding/json"
"restic"
)
var cmdSnapshots = &cobra.Command{
Use: "snapshots",
Use: "snapshots [snapshotID ...]",
Short: "list all snapshots",
Long: `
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.
type SnapshotOptions struct {
Host string
Tags []string
Paths []string
}
@ -35,15 +36,12 @@ func init() {
cmdRoot.AddCommand(cmdSnapshots)
f := cmdSnapshots.Flags()
f.StringVar(&snapshotOptions.Host, "host", "", "only print snapshots for this host")
f.StringSliceVar(&snapshotOptions.Paths, "path", []string{}, "only print snapshots for this `path` (can be specified multiple times)")
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.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 {
if len(args) != 0 {
return errors.Fatal("wrong number of arguments")
}
repo, err := OpenRepository(gopts)
if err != nil {
return err
@ -57,32 +55,14 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
}
}
done := make(chan struct{})
defer close(done)
ctx, cancel := context.WithCancel(gopts.ctx)
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 {
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 {
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.
func PrintSnapshots(stdout io.Writer, list []*restic.Snapshot) {
func PrintSnapshots(stdout io.Writer, list restic.Snapshots) {
// Determine the max widths for host and tag.
maxHost, maxTag := 10, 6
@ -165,7 +145,7 @@ func PrintSnapshots(stdout io.Writer, list []*restic.Snapshot) {
tab.Write(stdout)
}
// Snapshot helps to print Snaphots as JSON
// Snapshot helps to print Snaphots as JSON with their ID included.
type Snapshot struct {
*restic.Snapshot
@ -173,7 +153,7 @@ type Snapshot struct {
}
// 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
@ -187,5 +167,4 @@ func printSnapshotsJSON(stdout io.Writer, list []*restic.Snapshot) error {
}
return json.NewEncoder(stdout).Encode(snapshots)
}