mirror of
https://github.com/restic/restic.git
synced 2026-03-31 10:22:44 +00:00
Merge pull request #1876 from restic/forget-explain
forget: Add --explain
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/restic/restic/internal/cache"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -85,9 +86,17 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
tab := NewTable()
|
||||
tab.Header = fmt.Sprintf("%-14s %-16s %s", "Repository ID", "Last Used", "Old")
|
||||
tab.RowFormat = "%-14s %-16s %s"
|
||||
tab := table.New()
|
||||
|
||||
type data struct {
|
||||
ID string
|
||||
Last string
|
||||
Old string
|
||||
}
|
||||
|
||||
tab.AddColumn("Repo ID", "{{ .ID }}")
|
||||
tab.AddColumn("Last Used", "{{ .Last }}")
|
||||
tab.AddColumn("Old", "{{ .Old }}")
|
||||
|
||||
dirs, err := cache.All(cachedir)
|
||||
if err != nil {
|
||||
@@ -109,7 +118,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||
old = "yes"
|
||||
}
|
||||
|
||||
tab.Rows = append(tab.Rows, []interface{}{
|
||||
tab.AddRow(data{
|
||||
entry.Name()[:10],
|
||||
fmt.Sprintf("%d days ago", uint(time.Since(entry.ModTime()).Hours()/24)),
|
||||
old,
|
||||
|
||||
@@ -206,17 +206,17 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
Verbosef(":\n\n")
|
||||
|
||||
keep, remove := restic.ApplyPolicy(snapshotGroup, policy)
|
||||
keep, remove, reasons := restic.ApplyPolicy(snapshotGroup, policy)
|
||||
|
||||
if len(keep) != 0 && !gopts.Quiet {
|
||||
Printf("keep %d snapshots:\n", len(keep))
|
||||
PrintSnapshots(globalOptions.stdout, keep, opts.Compact)
|
||||
PrintSnapshots(globalOptions.stdout, keep, reasons, opts.Compact)
|
||||
Printf("\n")
|
||||
}
|
||||
|
||||
if len(remove) != 0 && !gopts.Quiet {
|
||||
Printf("remove %d snapshots:\n", len(remove))
|
||||
PrintSnapshots(globalOptions.stdout, remove, opts.Compact)
|
||||
PrintSnapshots(globalOptions.stdout, remove, nil, opts.Compact)
|
||||
Printf("\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -36,53 +36,18 @@ func init() {
|
||||
flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "the file from which to load a new password")
|
||||
}
|
||||
|
||||
type keyInfo struct {
|
||||
Current bool `json:"current"`
|
||||
ID string `json:"id"`
|
||||
UserName string `json:"userName"`
|
||||
HostName string `json:"hostName"`
|
||||
Created string `json:"created"`
|
||||
}
|
||||
|
||||
func (ki keyInfo) CurrentStr() string {
|
||||
if ki.Current {
|
||||
return "*"
|
||||
}
|
||||
return " "
|
||||
}
|
||||
|
||||
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error {
|
||||
var (
|
||||
appendKey func(keyInfo)
|
||||
printKeys func() error
|
||||
)
|
||||
|
||||
switch gopts.JSON {
|
||||
case true:
|
||||
var keys []keyInfo
|
||||
|
||||
appendKey = func(key keyInfo) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
printKeys = func() error {
|
||||
return json.NewEncoder(gopts.stdout).Encode(keys)
|
||||
}
|
||||
default:
|
||||
tab := NewTable()
|
||||
tab.Header = fmt.Sprintf(" %-10s %-10s %-10s %s", "ID", "User", "Host", "Created")
|
||||
tab.RowFormat = "%s%-10s %-10s %-10s %s"
|
||||
|
||||
appendKey = func(key keyInfo) {
|
||||
tab.Rows = append(tab.Rows, []interface{}{key.CurrentStr(), key.ID, key.UserName, key.HostName, key.Created})
|
||||
}
|
||||
|
||||
printKeys = func() error {
|
||||
return tab.Write(globalOptions.stdout)
|
||||
}
|
||||
type keyInfo struct {
|
||||
Current bool `json:"current"`
|
||||
ID string `json:"id"`
|
||||
UserName string `json:"userName"`
|
||||
HostName string `json:"hostName"`
|
||||
Created string `json:"created"`
|
||||
}
|
||||
|
||||
if err := s.List(ctx, restic.KeyFile, func(id restic.ID, size int64) error {
|
||||
var keys []keyInfo
|
||||
|
||||
err := s.List(ctx, restic.KeyFile, func(id restic.ID, size int64) error {
|
||||
k, err := repository.LoadKey(ctx, s, id.String())
|
||||
if err != nil {
|
||||
Warnf("LoadKey() failed: %v\n", err)
|
||||
@@ -97,14 +62,29 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||
Created: k.Created.Format(TimeFormat),
|
||||
}
|
||||
|
||||
appendKey(key)
|
||||
|
||||
keys = append(keys, key)
|
||||
return nil
|
||||
}); err != nil {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return printKeys()
|
||||
if gopts.JSON {
|
||||
return json.NewEncoder(globalOptions.stdout).Encode(keys)
|
||||
}
|
||||
|
||||
tab := table.New()
|
||||
tab.AddColumn(" ID", "{{if .Current}}*{{else}} {{end}}{{ .ID }}")
|
||||
tab.AddColumn("User", "{{ .UserName }}")
|
||||
tab.AddColumn("Host", "{{ .HostName }}")
|
||||
tab.AddColumn("Created", "{{ .Created }}")
|
||||
|
||||
for _, key := range keys {
|
||||
tab.AddRow(key)
|
||||
}
|
||||
|
||||
return tab.Write(globalOptions.stdout)
|
||||
}
|
||||
|
||||
// testKeyNewPassword is used to set a new password during integration testing.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -81,7 +82,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro
|
||||
}
|
||||
return nil
|
||||
}
|
||||
PrintSnapshots(gopts.stdout, list, opts.Compact)
|
||||
PrintSnapshots(gopts.stdout, list, nil, opts.Compact)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -123,7 +124,16 @@ func FilterLastSnapshots(list restic.Snapshots) restic.Snapshots {
|
||||
}
|
||||
|
||||
// PrintSnapshots prints a text table of the snapshots in list to stdout.
|
||||
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) {
|
||||
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.KeepReason, compact bool) {
|
||||
// keep the reasons a snasphot is being kept in a map, so that it doesn't
|
||||
// get lost when the list of snapshots is sorted
|
||||
keepReasons := make(map[restic.ID]restic.KeepReason, len(reasons))
|
||||
if len(reasons) > 0 {
|
||||
for i, sn := range list {
|
||||
id := sn.ID()
|
||||
keepReasons[*id] = reasons[i]
|
||||
}
|
||||
}
|
||||
|
||||
// always sort the snapshots so that the newer ones are listed last
|
||||
sort.SliceStable(list, func(i, j int) bool {
|
||||
@@ -143,71 +153,72 @@ func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) {
|
||||
}
|
||||
}
|
||||
|
||||
tab := NewTable()
|
||||
if !compact {
|
||||
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s %-3s %s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags", "", "Directory")
|
||||
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%%ds %%-3s %%s", -maxHost, -maxTag)
|
||||
tab := table.New()
|
||||
|
||||
if compact {
|
||||
tab.AddColumn("ID", "{{ .ID }}")
|
||||
tab.AddColumn("Time", "{{ .Timestamp }}")
|
||||
tab.AddColumn("Host", "{{ .Hostname }}")
|
||||
tab.AddColumn("Tags ", `{{ join .Tags "\n" }}`)
|
||||
} else {
|
||||
tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags")
|
||||
tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%s", -maxHost)
|
||||
tab.AddColumn("ID", "{{ .ID }}")
|
||||
tab.AddColumn("Time", "{{ .Timestamp }}")
|
||||
tab.AddColumn("Host ", "{{ .Hostname }}")
|
||||
tab.AddColumn("Tags ", `{{ join .Tags "," }}`)
|
||||
if len(reasons) > 0 {
|
||||
tab.AddColumn("Reasons", `{{ join .Reasons "\n" }}`)
|
||||
}
|
||||
tab.AddColumn("Paths", `{{ join .Paths "\n" }}`)
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
ID string
|
||||
Timestamp string
|
||||
Hostname string
|
||||
Tags []string
|
||||
Reasons []string
|
||||
Paths []string
|
||||
}
|
||||
|
||||
var multiline bool
|
||||
for _, sn := range list {
|
||||
if len(sn.Paths) == 0 {
|
||||
continue
|
||||
data := snapshot{
|
||||
ID: sn.ID().Str(),
|
||||
Timestamp: sn.Time.Format(TimeFormat),
|
||||
Hostname: sn.Hostname,
|
||||
Tags: sn.Tags,
|
||||
Paths: sn.Paths,
|
||||
}
|
||||
|
||||
firstTag := ""
|
||||
if len(sn.Tags) > 0 {
|
||||
firstTag = sn.Tags[0]
|
||||
if len(reasons) > 0 {
|
||||
id := sn.ID()
|
||||
data.Reasons = keepReasons[*id].Matches
|
||||
}
|
||||
|
||||
rows := len(sn.Paths)
|
||||
if rows < len(sn.Tags) {
|
||||
rows = len(sn.Tags)
|
||||
if len(sn.Paths) > 1 {
|
||||
multiline = true
|
||||
}
|
||||
|
||||
treeElement := " "
|
||||
if rows != 1 {
|
||||
treeElement = "┌──"
|
||||
}
|
||||
|
||||
if !compact {
|
||||
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, treeElement, sn.Paths[0]})
|
||||
} else {
|
||||
allTags := ""
|
||||
for _, tag := range sn.Tags {
|
||||
allTags += tag + " "
|
||||
}
|
||||
tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, allTags})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sn.Tags) > rows {
|
||||
rows = len(sn.Tags)
|
||||
}
|
||||
|
||||
for i := 1; i < rows; i++ {
|
||||
path := ""
|
||||
if len(sn.Paths) > i {
|
||||
path = sn.Paths[i]
|
||||
}
|
||||
|
||||
tag := ""
|
||||
if len(sn.Tags) > i {
|
||||
tag = sn.Tags[i]
|
||||
}
|
||||
|
||||
treeElement := "│"
|
||||
if i == (rows - 1) {
|
||||
treeElement = "└──"
|
||||
}
|
||||
|
||||
tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, treeElement, path})
|
||||
}
|
||||
tab.AddRow(data)
|
||||
}
|
||||
|
||||
tab.Footer = fmt.Sprintf("%d snapshots", len(list))
|
||||
tab.AddFooter(fmt.Sprintf("%d snapshots", len(list)))
|
||||
|
||||
if multiline {
|
||||
// print an additional blank line between snapshots
|
||||
|
||||
var last int
|
||||
tab.PrintData = func(w io.Writer, idx int, s string) error {
|
||||
var err error
|
||||
if idx == last {
|
||||
_, err = fmt.Fprintf(w, "%s\n", s)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "\n%s\n", s)
|
||||
}
|
||||
last = idx
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tab.Write(stdout)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ import (
|
||||
|
||||
var version = "0.9.2-dev (compiled manually)"
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// GlobalOptions hold all global options for restic.
|
||||
type GlobalOptions struct {
|
||||
Repo string
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Table contains data for a table to be printed.
|
||||
type Table struct {
|
||||
Header string
|
||||
Rows [][]interface{}
|
||||
Footer string
|
||||
|
||||
RowFormat string
|
||||
}
|
||||
|
||||
// NewTable initializes a new Table.
|
||||
func NewTable() Table {
|
||||
return Table{
|
||||
Rows: [][]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Table) printSeparationLine(w io.Writer) error {
|
||||
_, err := fmt.Fprintln(w, strings.Repeat("-", 70))
|
||||
return err
|
||||
}
|
||||
|
||||
// Write prints the table to w.
|
||||
func (t Table) Write(w io.Writer) error {
|
||||
_, err := fmt.Fprintln(w, t.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.printSeparationLine(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, row := range t.Rows {
|
||||
_, err = fmt.Fprintf(w, t.RowFormat+"\n", row...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = t.printSeparationLine(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(w, t.Footer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
Reference in New Issue
Block a user