github.com/mckael/restic@v0.8.3/cmd/restic/cmd_snapshots.go (about) 1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "sort" 9 "strings" 10 11 "github.com/restic/restic/internal/restic" 12 "github.com/spf13/cobra" 13 ) 14 15 var cmdSnapshots = &cobra.Command{ 16 Use: "snapshots [snapshotID ...]", 17 Short: "List all snapshots", 18 Long: ` 19 The "snapshots" command lists all snapshots stored in the repository. 20 `, 21 DisableAutoGenTag: true, 22 RunE: func(cmd *cobra.Command, args []string) error { 23 return runSnapshots(snapshotOptions, globalOptions, args) 24 }, 25 } 26 27 // SnapshotOptions bundles all options for the snapshots command. 28 type SnapshotOptions struct { 29 Host string 30 Tags restic.TagLists 31 Paths []string 32 Compact bool 33 Last bool 34 } 35 36 var snapshotOptions SnapshotOptions 37 38 func init() { 39 cmdRoot.AddCommand(cmdSnapshots) 40 41 f := cmdSnapshots.Flags() 42 f.StringVarP(&snapshotOptions.Host, "host", "H", "", "only consider snapshots for this `host`") 43 f.Var(&snapshotOptions.Tags, "tag", "only consider snapshots which include this `taglist` (can be specified multiple times)") 44 f.StringArrayVar(&snapshotOptions.Paths, "path", nil, "only consider snapshots for this `path` (can be specified multiple times)") 45 f.BoolVarP(&snapshotOptions.Compact, "compact", "c", false, "use compact format") 46 f.BoolVar(&snapshotOptions.Last, "last", false, "only show the last snapshot for each host and path") 47 } 48 49 func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) error { 50 repo, err := OpenRepository(gopts) 51 if err != nil { 52 return err 53 } 54 55 if !gopts.NoLock { 56 lock, err := lockRepo(repo) 57 defer unlockRepo(lock) 58 if err != nil { 59 return err 60 } 61 } 62 63 ctx, cancel := context.WithCancel(gopts.ctx) 64 defer cancel() 65 66 var list restic.Snapshots 67 for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args) { 68 list = append(list, sn) 69 } 70 71 if opts.Last { 72 list = FilterLastSnapshots(list) 73 } 74 75 sort.Sort(sort.Reverse(list)) 76 77 if gopts.JSON { 78 err := printSnapshotsJSON(gopts.stdout, list) 79 if err != nil { 80 Warnf("error printing snapshot: %v\n", err) 81 } 82 return nil 83 } 84 PrintSnapshots(gopts.stdout, list, opts.Compact) 85 86 return nil 87 } 88 89 // filterLastSnapshotsKey is used by FilterLastSnapshots. 90 type filterLastSnapshotsKey struct { 91 Hostname string 92 JoinedPaths string 93 } 94 95 // newFilterLastSnapshotsKey initializes a filterLastSnapshotsKey from a Snapshot 96 func newFilterLastSnapshotsKey(sn *restic.Snapshot) filterLastSnapshotsKey { 97 // Shallow slice copy 98 var paths = make([]string, len(sn.Paths)) 99 copy(paths, sn.Paths) 100 sort.Strings(paths) 101 return filterLastSnapshotsKey{sn.Hostname, strings.Join(paths, "|")} 102 } 103 104 // FilterLastSnapshots filters a list of snapshots to only return the last 105 // entry for each hostname and path. If the snapshot contains multiple paths, 106 // they will be joined and treated as one item. 107 func FilterLastSnapshots(list restic.Snapshots) restic.Snapshots { 108 // Sort the snapshots so that the newer ones are listed first 109 sort.SliceStable(list, func(i, j int) bool { 110 return list[i].Time.After(list[j].Time) 111 }) 112 113 var results restic.Snapshots 114 seen := make(map[filterLastSnapshotsKey]bool) 115 for _, sn := range list { 116 key := newFilterLastSnapshotsKey(sn) 117 if !seen[key] { 118 seen[key] = true 119 results = append(results, sn) 120 } 121 } 122 return results 123 } 124 125 // PrintSnapshots prints a text table of the snapshots in list to stdout. 126 func PrintSnapshots(stdout io.Writer, list restic.Snapshots, compact bool) { 127 128 // always sort the snapshots so that the newer ones are listed last 129 sort.SliceStable(list, func(i, j int) bool { 130 return list[i].Time.Before(list[j].Time) 131 }) 132 133 // Determine the max widths for host and tag. 134 maxHost, maxTag := 10, 6 135 for _, sn := range list { 136 if len(sn.Hostname) > maxHost { 137 maxHost = len(sn.Hostname) 138 } 139 for _, tag := range sn.Tags { 140 if len(tag) > maxTag { 141 maxTag = len(tag) 142 } 143 } 144 } 145 146 tab := NewTable() 147 if !compact { 148 tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s %-3s %s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags", "", "Directory") 149 tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%%ds %%-3s %%s", -maxHost, -maxTag) 150 } else { 151 tab.Header = fmt.Sprintf("%-8s %-19s %-*s %-*s", "ID", "Date", -maxHost, "Host", -maxTag, "Tags") 152 tab.RowFormat = fmt.Sprintf("%%-8s %%-19s %%%ds %%s", -maxHost) 153 } 154 155 for _, sn := range list { 156 if len(sn.Paths) == 0 { 157 continue 158 } 159 160 firstTag := "" 161 if len(sn.Tags) > 0 { 162 firstTag = sn.Tags[0] 163 } 164 165 rows := len(sn.Paths) 166 if rows < len(sn.Tags) { 167 rows = len(sn.Tags) 168 } 169 170 treeElement := " " 171 if rows != 1 { 172 treeElement = "┌──" 173 } 174 175 if !compact { 176 tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, firstTag, treeElement, sn.Paths[0]}) 177 } else { 178 allTags := "" 179 for _, tag := range sn.Tags { 180 allTags += tag + " " 181 } 182 tab.Rows = append(tab.Rows, []interface{}{sn.ID().Str(), sn.Time.Format(TimeFormat), sn.Hostname, allTags}) 183 continue 184 } 185 186 if len(sn.Tags) > rows { 187 rows = len(sn.Tags) 188 } 189 190 for i := 1; i < rows; i++ { 191 path := "" 192 if len(sn.Paths) > i { 193 path = sn.Paths[i] 194 } 195 196 tag := "" 197 if len(sn.Tags) > i { 198 tag = sn.Tags[i] 199 } 200 201 treeElement := "│" 202 if i == (rows - 1) { 203 treeElement = "└──" 204 } 205 206 tab.Rows = append(tab.Rows, []interface{}{"", "", "", tag, treeElement, path}) 207 } 208 } 209 210 tab.Footer = fmt.Sprintf("%d snapshots", len(list)) 211 212 tab.Write(stdout) 213 } 214 215 // Snapshot helps to print Snaphots as JSON with their ID included. 216 type Snapshot struct { 217 *restic.Snapshot 218 219 ID *restic.ID `json:"id"` 220 ShortID string `json:"short_id"` 221 } 222 223 // printSnapshotsJSON writes the JSON representation of list to stdout. 224 func printSnapshotsJSON(stdout io.Writer, list restic.Snapshots) error { 225 226 var snapshots []Snapshot 227 228 for _, sn := range list { 229 230 k := Snapshot{ 231 Snapshot: sn, 232 ID: sn.ID(), 233 ShortID: sn.ID().Str(), 234 } 235 snapshots = append(snapshots, k) 236 } 237 238 return json.NewEncoder(stdout).Encode(snapshots) 239 }