mirror of
https://github.com/restic/restic.git
synced 2025-12-13 14:22:26 +00:00
repair snapshots: convert to termstatus
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -49,7 +50,9 @@ Exit status is 12 if the password is incorrect.
|
|||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runRepairSnapshots(cmd.Context(), globalOptions, opts, args)
|
term, cancel := setupTermstatus()
|
||||||
|
defer cancel()
|
||||||
|
return runRepairSnapshots(cmd.Context(), globalOptions, opts, args, term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +75,9 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
|||||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string) error {
|
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string, term *termstatus.Terminal) error {
|
||||||
|
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -84,7 +89,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
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
|
||||||
}
|
}
|
||||||
@@ -96,7 +101,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
||||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
||||||
if node.Type == restic.NodeTypeIrregular || node.Type == restic.NodeTypeInvalid {
|
if node.Type == restic.NodeTypeIrregular || node.Type == restic.NodeTypeInvalid {
|
||||||
Verbosef(" file %q: removed node with invalid type %q\n", path, node.Type)
|
printer.P(" file %q: removed node with invalid type %q", path, node.Type)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if node.Type != restic.NodeTypeFile {
|
if node.Type != restic.NodeTypeFile {
|
||||||
@@ -116,9 +121,9 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
Verbosef(" file %q: removed missing content\n", path)
|
printer.P(" file %q: removed missing content", path)
|
||||||
} else if newSize != node.Size {
|
} else if newSize != node.Size {
|
||||||
Verbosef(" file %q: fixed incorrect size\n", path)
|
printer.P(" file %q: fixed incorrect size", path)
|
||||||
}
|
}
|
||||||
// no-ops if already correct
|
// no-ops if already correct
|
||||||
node.Content = newContent
|
node.Content = newContent
|
||||||
@@ -127,12 +132,12 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
},
|
},
|
||||||
RewriteFailedTree: func(_ restic.ID, path string, _ error) (restic.ID, error) {
|
RewriteFailedTree: func(_ restic.ID, path string, _ error) (restic.ID, error) {
|
||||||
if path == "/" {
|
if path == "/" {
|
||||||
Verbosef(" dir %q: not readable\n", path)
|
printer.P(" dir %q: not readable", path)
|
||||||
// remove snapshots with invalid root node
|
// remove snapshots with invalid root node
|
||||||
return restic.ID{}, nil
|
return restic.ID{}, nil
|
||||||
}
|
}
|
||||||
// If a subtree fails to load, remove it
|
// If a subtree fails to load, remove it
|
||||||
Verbosef(" dir %q: replaced with empty directory\n", path)
|
printer.P(" dir %q: replaced with empty directory", path)
|
||||||
emptyID, err := restic.SaveTree(ctx, repo, &restic.Tree{})
|
emptyID, err := restic.SaveTree(ctx, repo, &restic.Tree{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return restic.ID{}, err
|
return restic.ID{}, err
|
||||||
@@ -144,7 +149,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
|
|
||||||
changedCount := 0
|
changedCount := 0
|
||||||
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args) {
|
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args) {
|
||||||
Verbosef("\n%v\n", sn)
|
printer.P("\n%v", sn)
|
||||||
changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
|
changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
|
||||||
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) {
|
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) {
|
||||||
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
||||||
@@ -161,18 +166,18 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
Verbosef("\n")
|
printer.P("")
|
||||||
if changedCount == 0 {
|
if changedCount == 0 {
|
||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
Verbosef("no snapshots were modified\n")
|
printer.P("no snapshots were modified")
|
||||||
} else {
|
} else {
|
||||||
Verbosef("no snapshots would be modified\n")
|
printer.P("no snapshots would be modified")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
Verbosef("modified %v snapshots\n", changedCount)
|
printer.P("modified %v snapshots", changedCount)
|
||||||
} else {
|
} else {
|
||||||
Verbosef("would modify %v snapshots\n", changedCount)
|
printer.P("would modify %v snapshots", changedCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
"github.com/restic/restic/internal/ui/termstatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
||||||
@@ -19,7 +20,9 @@ func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
|||||||
Forget: forget,
|
Forget: forget,
|
||||||
}
|
}
|
||||||
|
|
||||||
rtest.OK(t, runRepairSnapshots(context.TODO(), gopts, opts, nil))
|
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
|
||||||
|
return runRepairSnapshots(context.TODO(), gopts, opts, nil, term)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRandomFile(t testing.TB, env *testEnvironment, path string, size int) {
|
func createRandomFile(t testing.TB, env *testEnvironment, path string, size int) {
|
||||||
|
|||||||
Reference in New Issue
Block a user