diff --git a/src/cmds/restic/cmd_forget.go b/src/cmds/restic/cmd_forget.go new file mode 100644 index 000000000..df6d97062 --- /dev/null +++ b/src/cmds/restic/cmd_forget.go @@ -0,0 +1,153 @@ +package main + +import ( + "fmt" + "io" + "path" + "restic" + "restic/backend" +) + +// CmdForget implements the 'forget' command. +type CmdForget struct { + 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"` + 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"` + 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"` + + DryRun bool `short:"n" long:"dry-run" description:"do not delete anything, just print what would be done"` + + global *GlobalOptions +} + +func init() { + _, err := parser.AddCommand("forget", + "removes snapshots from a repository", + "The forget command removes snapshots according to a policy.", + &CmdForget{global: &globalOpts}) + if err != nil { + panic(err) + } +} + +// Usage returns usage information for 'forget'. +func (cmd CmdForget) Usage() string { + return "[snapshot ID] ..." +} + +func printSnapshots(w io.Writer, snapshots restic.Snapshots) { + tab := NewTable() + tab.Header = fmt.Sprintf("%-8s %-19s %-10s %s", "ID", "Date", "Host", "Directory") + tab.RowFormat = "%-8s %-19s %-10s %s" + + for _, sn := range snapshots { + if len(sn.Paths) == 0 { + 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 { + for _, path := range sn.Paths[1:] { + tab.Rows = append(tab.Rows, []interface{}{"", "", "", path}) + } + } + } + + tab.Write(w) +} + +// Execute runs the 'forget' command. +func (cmd CmdForget) Execute(args []string) error { + repo, err := cmd.global.OpenRepository() + if err != nil { + return err + } + + lock, err := lockRepoExclusive(repo) + defer unlockRepo(lock) + if err != nil { + return err + } + + err = repo.LoadIndex() + if err != nil { + return err + } + + // first, process all snapshot IDs given as arguments + for _, s := range args { + id, err := restic.FindSnapshot(repo, s) + if err != nil { + return err + } + + if !cmd.DryRun { + err = repo.Backend().Remove(backend.Snapshot, id.String()) + if err != nil { + return err + } + + cmd.global.Verbosef("removed snapshot %v\n", id.Str()) + } else { + cmd.global.Verbosef("would removed snapshot %v\n", id.Str()) + } + + } + + // then, load all remaining snapshots + snapshots, err := restic.LoadAllSnapshots(repo) + if err != nil { + return err + } + + // group by hostname and dirs + type key struct { + Hostname string + Dirs string + } + + snapshotGroups := make(map[key]restic.Snapshots) + + for _, sn := range snapshots { + k := key{Hostname: sn.Hostname, Dirs: path.Join(sn.Paths...)} + list := snapshotGroups[k] + list = append(list, sn) + snapshotGroups[k] = list + } + + policy := restic.ExpirePolicy{ + Last: cmd.Last, + Hourly: cmd.Hourly, + Daily: cmd.Daily, + Weekly: cmd.Weekly, + Monthly: cmd.Monthly, + Yearly: cmd.Yearly, + } + + for key, snapshotGroup := range snapshotGroups { + cmd.global.Printf("snapshots for host %v, directories %v:\n\n", key.Hostname, key.Dirs) + keep, remove := restic.ApplyPolicy(snapshotGroup, policy) + + cmd.global.Printf("keep %d snapshots:\n", len(keep)) + printSnapshots(cmd.global.stdout, keep) + cmd.global.Printf("\n") + + cmd.global.Printf("remove %d snapshots:\n", len(remove)) + printSnapshots(cmd.global.stdout, remove) + cmd.global.Printf("\n") + + if !cmd.DryRun { + for _, sn := range remove { + err = repo.Backend().Remove(backend.Snapshot, sn.ID().String()) + if err != nil { + return err + } + } + } + } + + return nil +}