From 3c6c17abcd5a950bfcb80fb9c2163299ebfbffca Mon Sep 17 00:00:00 2001 From: Pauline Middelink Date: Wed, 8 Mar 2017 20:24:58 +0100 Subject: [PATCH] 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. --- src/cmds/restic/cmd_forget.go | 110 ++++++++++++------------------- src/cmds/restic/cmd_snapshots.go | 53 +++++---------- 2 files changed, 57 insertions(+), 106 deletions(-) diff --git a/src/cmds/restic/cmd_forget.go b/src/cmds/restic/cmd_forget.go index 7c762728d..bc372fc56 100644 --- a/src/cmds/restic/cmd_forget.go +++ b/src/cmds/restic/cmd_forget.go @@ -1,14 +1,12 @@ package main import ( - "encoding/hex" + "context" "encoding/json" "restic" "sort" "strings" - "restic/errors" - "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. type ForgetOptions struct { - Last int - Hourly int - Daily int - Weekly int - Monthly int - Yearly int - + Last int + Hourly int + Daily int + 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) - if err != nil { + 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", id.Str()) + Verbosef("removed snapshot %v\n", sn.ID().Str()) } 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 } @@ -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 { diff --git a/src/cmds/restic/cmd_snapshots.go b/src/cmds/restic/cmd_snapshots.go index 7ff52831d..7a3fa9879 100644 --- a/src/cmds/restic/cmd_snapshots.go +++ b/src/cmds/restic/cmd_snapshots.go @@ -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) - - 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) - } - } + ctx, cancel := context.WithCancel(gopts.ctx) + defer cancel() + 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) - }