mirror of
https://github.com/kopia/kopia.git
synced 2026-01-20 12:27:53 -05:00
135 lines
2.9 KiB
Go
135 lines
2.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/kopia/kopia/internal/units"
|
|
"github.com/kopia/kopia/snapshot"
|
|
|
|
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
|
)
|
|
|
|
var (
|
|
backupsCommand = app.Command("backups", "List history of file or directory backups.")
|
|
backupsPath = backupsCommand.Arg("source", "File or directory to show history of.").String()
|
|
maxResultsPerPath = backupsCommand.Flag("maxresults", "Maximum number of results.").Default("100").Int()
|
|
)
|
|
|
|
func findBackups(mgr *snapshot.Manager, sourceInfo snapshot.SourceInfo) (manifestIDs []string, relPath string, err error) {
|
|
for len(sourceInfo.Path) > 0 {
|
|
list, err := mgr.ListSnapshotManifests(&sourceInfo, -1)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if len(list) > 0 {
|
|
return list, relPath, nil
|
|
}
|
|
|
|
if len(relPath) > 0 {
|
|
relPath = filepath.Base(sourceInfo.Path) + "/" + relPath
|
|
} else {
|
|
relPath = filepath.Base(sourceInfo.Path)
|
|
}
|
|
|
|
log.Printf("No backups of %v@%v:%v", sourceInfo.UserName, sourceInfo.Host, sourceInfo.Path)
|
|
|
|
parentPath := filepath.Dir(sourceInfo.Path)
|
|
if parentPath == sourceInfo.Path {
|
|
break
|
|
}
|
|
sourceInfo.Path = parentPath
|
|
}
|
|
|
|
return nil, "", nil
|
|
}
|
|
|
|
func runBackupsCommand(context *kingpin.ParseContext) error {
|
|
conn := mustOpenConnection()
|
|
defer conn.Close()
|
|
|
|
mgr := snapshot.NewManager(conn)
|
|
|
|
var previous []string
|
|
var relPath string
|
|
var err error
|
|
|
|
if *backupsPath != "" {
|
|
si, err := snapshot.ParseSourceInfo(*backupsPath, getHostName(), getUserName())
|
|
if err != nil {
|
|
return fmt.Errorf("invalid directory: '%s': %s", *backupsPath, err)
|
|
}
|
|
|
|
previous, relPath, err = findBackups(mgr, si)
|
|
if relPath != "" {
|
|
relPath = "/" + relPath
|
|
}
|
|
} else {
|
|
previous, err = mgr.ListSnapshotManifests(nil, -1)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("cannot list backups: %v", err)
|
|
}
|
|
|
|
var lastSource snapshot.SourceInfo
|
|
var count int
|
|
|
|
manifests, err := mgr.LoadSnapshots(previous)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sort.Sort(manifestSorter(manifests))
|
|
|
|
for _, m := range manifests {
|
|
if m.Source != lastSource {
|
|
fmt.Printf("\n%v\n", m.Source)
|
|
lastSource = m.Source
|
|
count = 0
|
|
}
|
|
|
|
if count < *maxResultsPerPath {
|
|
fmt.Printf(
|
|
" %v%v %v %10v %v\n",
|
|
m.RootObjectID,
|
|
relPath,
|
|
m.StartTime.Format("2006-01-02 15:04:05 MST"),
|
|
units.BytesString(m.Stats.TotalFileSize),
|
|
deltaBytes(m.Stats.Repository.WrittenBytes),
|
|
)
|
|
count++
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type manifestSorter []*snapshot.Manifest
|
|
|
|
func (b manifestSorter) Len() int { return len(b) }
|
|
func (b manifestSorter) Less(i, j int) bool {
|
|
if c := strings.Compare(b[i].Source.String(), b[j].Source.String()); c != 0 {
|
|
return c < 0
|
|
}
|
|
|
|
return b[i].StartTime.UnixNano() < b[j].StartTime.UnixNano()
|
|
}
|
|
|
|
func (b manifestSorter) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
|
|
func deltaBytes(b int64) string {
|
|
if b > 0 {
|
|
return "(+" + units.BytesString(b) + ")"
|
|
}
|
|
|
|
return "(no change)"
|
|
}
|
|
|
|
func init() {
|
|
backupsCommand.Action(runBackupsCommand)
|
|
}
|