mirror of
https://github.com/restic/restic.git
synced 2025-12-13 22:32:01 +00:00
find: convert to termstatus
This commit is contained in:
@@ -3,6 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
"github.com/restic/restic/internal/ui/termstatus"
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +51,9 @@ Exit status is 12 if the password is incorrect.
|
|||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runFind(cmd.Context(), opts, globalOptions, args)
|
term, cancel := setupTermstatus()
|
||||||
|
defer cancel()
|
||||||
|
return runFind(cmd.Context(), opts, globalOptions, args, term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +129,12 @@ type statefulOutput struct {
|
|||||||
newsn *restic.Snapshot
|
newsn *restic.Snapshot
|
||||||
oldsn *restic.Snapshot
|
oldsn *restic.Snapshot
|
||||||
hits int
|
hits int
|
||||||
|
printer interface {
|
||||||
|
S(string, ...interface{})
|
||||||
|
P(string, ...interface{})
|
||||||
|
E(string, ...interface{})
|
||||||
|
}
|
||||||
|
stdout io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
||||||
@@ -148,37 +159,37 @@ func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
|||||||
findNode: (*findNode)(node),
|
findNode: (*findNode)(node),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("Marshall failed: %v\n", err)
|
s.printer.E("Marshall failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.inuse {
|
if !s.inuse {
|
||||||
Printf("[")
|
_, _ = s.stdout.Write([]byte("["))
|
||||||
s.inuse = true
|
s.inuse = true
|
||||||
}
|
}
|
||||||
if s.newsn != s.oldsn {
|
if s.newsn != s.oldsn {
|
||||||
if s.oldsn != nil {
|
if s.oldsn != nil {
|
||||||
Printf("],\"hits\":%d,\"snapshot\":%q},", s.hits, s.oldsn.ID())
|
_, _ = s.stdout.Write([]byte(fmt.Sprintf("],\"hits\":%d,\"snapshot\":%q},", s.hits, s.oldsn.ID())))
|
||||||
}
|
}
|
||||||
Printf(`{"matches":[`)
|
_, _ = s.stdout.Write([]byte(`{"matches":[`))
|
||||||
s.oldsn = s.newsn
|
s.oldsn = s.newsn
|
||||||
s.hits = 0
|
s.hits = 0
|
||||||
}
|
}
|
||||||
if s.hits > 0 {
|
if s.hits > 0 {
|
||||||
Printf(",")
|
_, _ = s.stdout.Write([]byte(","))
|
||||||
}
|
}
|
||||||
Print(string(b))
|
_, _ = s.stdout.Write(b)
|
||||||
s.hits++
|
s.hits++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
||||||
if s.newsn != s.oldsn {
|
if s.newsn != s.oldsn {
|
||||||
if s.oldsn != nil {
|
if s.oldsn != nil {
|
||||||
Verbosef("\n")
|
s.printer.P("")
|
||||||
}
|
}
|
||||||
s.oldsn = s.newsn
|
s.oldsn = s.newsn
|
||||||
Verbosef("Found matching entries in snapshot %s from %s\n", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
s.printer.P("Found matching entries in snapshot %s from %s", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
||||||
}
|
}
|
||||||
Println(formatNode(path, node, s.ListLong, s.HumanReadable))
|
s.printer.S(formatNode(path, node, s.ListLong, s.HumanReadable))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
||||||
@@ -207,29 +218,29 @@ func (s *statefulOutput) PrintObjectJSON(kind, id, nodepath, treeID string, sn *
|
|||||||
Time: sn.Time,
|
Time: sn.Time,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("Marshall failed: %v\n", err)
|
s.printer.E("Marshall failed: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.inuse {
|
if !s.inuse {
|
||||||
Printf("[")
|
_, _ = s.stdout.Write([]byte("["))
|
||||||
s.inuse = true
|
s.inuse = true
|
||||||
}
|
}
|
||||||
if s.hits > 0 {
|
if s.hits > 0 {
|
||||||
Printf(",")
|
_, _ = s.stdout.Write([]byte(","))
|
||||||
}
|
}
|
||||||
Print(string(b))
|
_, _ = s.stdout.Write(b)
|
||||||
s.hits++
|
s.hits++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
||||||
Printf("Found %s %s\n", kind, id)
|
s.printer.S("Found %s %s", kind, id)
|
||||||
if kind == "blob" {
|
if kind == "blob" {
|
||||||
Printf(" ... in file %s\n", nodepath)
|
s.printer.S(" ... in file %s", nodepath)
|
||||||
Printf(" (tree %s)\n", treeID)
|
s.printer.S(" (tree %s)", treeID)
|
||||||
} else {
|
} else {
|
||||||
Printf(" ... path %s\n", nodepath)
|
s.printer.S(" ... path %s", nodepath)
|
||||||
}
|
}
|
||||||
Printf(" ... in snapshot %s (%s)\n", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
s.printer.S(" ... in snapshot %s (%s)", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
||||||
@@ -244,12 +255,12 @@ func (s *statefulOutput) Finish() {
|
|||||||
if s.JSON {
|
if s.JSON {
|
||||||
// do some finishing up
|
// do some finishing up
|
||||||
if s.oldsn != nil {
|
if s.oldsn != nil {
|
||||||
Printf("],\"hits\":%d,\"snapshot\":%q}", s.hits, s.oldsn.ID())
|
_, _ = s.stdout.Write([]byte(fmt.Sprintf("],\"hits\":%d,\"snapshot\":%q}", s.hits, s.oldsn.ID())))
|
||||||
}
|
}
|
||||||
if s.inuse {
|
if s.inuse {
|
||||||
Printf("]\n")
|
_, _ = s.stdout.Write([]byte("]\n"))
|
||||||
} else {
|
} else {
|
||||||
Printf("[]\n")
|
_, _ = s.stdout.Write([]byte("[]\n"))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -263,6 +274,11 @@ type Finder struct {
|
|||||||
blobIDs map[string]struct{}
|
blobIDs map[string]struct{}
|
||||||
treeIDs map[string]struct{}
|
treeIDs map[string]struct{}
|
||||||
itemsFound int
|
itemsFound int
|
||||||
|
printer interface {
|
||||||
|
S(string, ...interface{})
|
||||||
|
P(string, ...interface{})
|
||||||
|
E(string, ...interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error {
|
func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error {
|
||||||
@@ -277,7 +293,8 @@ func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
||||||
|
|
||||||
Printf("Unable to load tree %s\n ... which belongs to snapshot %s\n", parentTreeID, sn.ID())
|
f.printer.S("Unable to load tree %s", parentTreeID)
|
||||||
|
f.printer.S(" ... which belongs to snapshot %s", sn.ID())
|
||||||
|
|
||||||
return walker.ErrSkipNode
|
return walker.ErrSkipNode
|
||||||
}
|
}
|
||||||
@@ -375,7 +392,8 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
||||||
|
|
||||||
Printf("Unable to load tree %s\n ... which belongs to snapshot %s\n", parentTreeID, sn.ID())
|
f.printer.S("Unable to load tree %s", parentTreeID)
|
||||||
|
f.printer.S(" ... which belongs to snapshot %s", sn.ID())
|
||||||
|
|
||||||
return walker.ErrSkipNode
|
return walker.ErrSkipNode
|
||||||
}
|
}
|
||||||
@@ -524,7 +542,7 @@ func (f *Finder) indexPacksToBlobs(ctx context.Context, packIDs map[string]struc
|
|||||||
for h := range indexPackIDs {
|
for h := range indexPackIDs {
|
||||||
list = append(list, h)
|
list = append(list, h)
|
||||||
}
|
}
|
||||||
Warnf("some pack files are missing from the repository, getting their blobs from the repository index: %v\n\n", list)
|
f.printer.E("some pack files are missing from the repository, getting their blobs from the repository index: %v\n\n", list)
|
||||||
}
|
}
|
||||||
return packIDs, nil
|
return packIDs, nil
|
||||||
}
|
}
|
||||||
@@ -532,19 +550,20 @@ func (f *Finder) indexPacksToBlobs(ctx context.Context, packIDs map[string]struc
|
|||||||
func (f *Finder) findObjectPack(id string, t restic.BlobType) {
|
func (f *Finder) findObjectPack(id string, t restic.BlobType) {
|
||||||
rid, err := restic.ParseID(id)
|
rid, err := restic.ParseID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Printf("Note: cannot find pack for object '%s', unable to parse ID: %v\n", id, err)
|
f.printer.S("Note: cannot find pack for object '%s', unable to parse ID: %v", id, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
blobs := f.repo.LookupBlob(t, rid)
|
blobs := f.repo.LookupBlob(t, rid)
|
||||||
if len(blobs) == 0 {
|
if len(blobs) == 0 {
|
||||||
Printf("Object %s not found in the index\n", rid.Str())
|
f.printer.S("Object %s not found in the index", rid.Str())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range blobs {
|
for _, b := range blobs {
|
||||||
if b.ID.Equal(rid) {
|
if b.ID.Equal(rid) {
|
||||||
Printf("Object belongs to pack %s\n ... Pack %s: %s\n", b.PackID, b.PackID.Str(), b.String())
|
f.printer.S("Object belongs to pack %s", b.PackID)
|
||||||
|
f.printer.S(" ... Pack %s: %s", b.PackID.Str(), b.String())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,11 +579,13 @@ func (f *Finder) findObjectsPacks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []string) error {
|
func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.Fatal("wrong number of arguments")
|
return errors.Fatal("wrong number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
pat := findPattern{pattern: args}
|
pat := findPattern{pattern: args}
|
||||||
if opts.CaseInsensitive {
|
if opts.CaseInsensitive {
|
||||||
@@ -604,15 +625,16 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bar := newIndexProgress(gopts.Quiet, gopts.JSON)
|
bar := newIndexTerminalProgress(printer)
|
||||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &Finder{
|
f := &Finder{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
pat: pat,
|
pat: pat,
|
||||||
out: statefulOutput{ListLong: opts.ListLong, HumanReadable: opts.HumanReadable, JSON: gopts.JSON},
|
out: statefulOutput{ListLong: opts.ListLong, HumanReadable: opts.HumanReadable, JSON: gopts.JSON, printer: printer, stdout: term.OutputRaw()},
|
||||||
|
printer: printer,
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.BlobID {
|
if opts.BlobID {
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
"github.com/restic/restic/internal/ui/termstatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte {
|
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte {
|
||||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||||
gopts.JSON = wantJSON
|
gopts.JSON = wantJSON
|
||||||
|
|
||||||
return runFind(context.TODO(), opts, gopts, []string{pattern})
|
return withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
|
||||||
|
return runFind(ctx, opts, gopts, []string{pattern}, term)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
@@ -95,7 +98,7 @@ func TestFindSorting(t *testing.T) {
|
|||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
datafile := testSetupBackupData(t, env)
|
testSetupBackupData(t, env)
|
||||||
opts := BackupOptions{}
|
opts := BackupOptions{}
|
||||||
|
|
||||||
// first backup
|
// first backup
|
||||||
@@ -114,14 +117,14 @@ func TestFindSorting(t *testing.T) {
|
|||||||
// first restic find - with default FindOptions{}
|
// first restic find - with default FindOptions{}
|
||||||
results := testRunFind(t, true, FindOptions{}, env.gopts, "testfile")
|
results := testRunFind(t, true, FindOptions{}, env.gopts, "testfile")
|
||||||
lines := strings.Split(string(results), "\n")
|
lines := strings.Split(string(results), "\n")
|
||||||
rtest.Assert(t, len(lines) == 2, "expected two files found in repo (%v), found %d", datafile, len(lines))
|
rtest.Assert(t, len(lines) == 2, "expected two lines of output, found %d", len(lines))
|
||||||
matches := []testMatches{}
|
matches := []testMatches{}
|
||||||
rtest.OK(t, json.Unmarshal(results, &matches))
|
rtest.OK(t, json.Unmarshal(results, &matches))
|
||||||
|
|
||||||
// run second restic find with --reverse, sort oldest to newest
|
// run second restic find with --reverse, sort oldest to newest
|
||||||
resultsReverse := testRunFind(t, true, FindOptions{Reverse: true}, env.gopts, "testfile")
|
resultsReverse := testRunFind(t, true, FindOptions{Reverse: true}, env.gopts, "testfile")
|
||||||
lines = strings.Split(string(resultsReverse), "\n")
|
lines = strings.Split(string(resultsReverse), "\n")
|
||||||
rtest.Assert(t, len(lines) == 2, "expected two files found in repo (%v), found %d", datafile, len(lines))
|
rtest.Assert(t, len(lines) == 2, "expected two lines of output, found %d", len(lines))
|
||||||
matchesReverse := []testMatches{}
|
matchesReverse := []testMatches{}
|
||||||
rtest.OK(t, json.Unmarshal(resultsReverse, &matchesReverse))
|
rtest.OK(t, json.Unmarshal(resultsReverse, &matchesReverse))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user