rewrite: convert to termstatus

This commit is contained in:
Michael Eischer
2025-09-13 23:45:09 +02:00
parent a304826b98
commit 0dcd9bee88
3 changed files with 43 additions and 30 deletions

View File

@@ -154,7 +154,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
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)
return id, nil, err return id, nil, err
}, opts.DryRun, opts.Forget, nil, "repaired") }, opts.DryRun, opts.Forget, nil, "repaired", printer)
if err != nil { if err != nil {
return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err) return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err)
} }

View File

@@ -13,6 +13,8 @@ import (
"github.com/restic/restic/internal/filter" "github.com/restic/restic/internal/filter"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/restic/restic/internal/walker" "github.com/restic/restic/internal/walker"
) )
@@ -58,7 +60,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 runRewrite(cmd.Context(), opts, globalOptions, args) term, cancel := setupTermstatus()
defer cancel()
return runRewrite(cmd.Context(), opts, globalOptions, args, term)
}, },
} }
@@ -122,12 +126,12 @@ func (opts *RewriteOptions) AddFlags(f *pflag.FlagSet) {
// be updated accordingly. // be updated accordingly.
type rewriteFilterFunc func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) type rewriteFilterFunc func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error)
func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, opts RewriteOptions) (bool, error) { func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, opts RewriteOptions, printer progress.Printer) (bool, error) {
if sn.Tree == nil { if sn.Tree == nil {
return false, errors.Errorf("snapshot %v has nil tree", sn.ID().Str()) return false, errors.Errorf("snapshot %v has nil tree", sn.ID().Str())
} }
rejectByNameFuncs, err := opts.ExcludePatternOptions.CollectPatterns(Warnf) rejectByNameFuncs, err := opts.ExcludePatternOptions.CollectPatterns(printer.E)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -154,7 +158,7 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
if selectByName(path) { if selectByName(path) {
return node return node
} }
Verbosef("excluding %s\n", path) printer.P("excluding %s", path)
return nil return nil
} }
@@ -182,11 +186,11 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
} }
return filterAndReplaceSnapshot(ctx, repo, sn, return filterAndReplaceSnapshot(ctx, repo, sn,
filter, opts.DryRun, opts.Forget, metadata, "rewrite") filter, opts.DryRun, opts.Forget, metadata, "rewrite", printer)
} }
func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *restic.Snapshot, func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *restic.Snapshot,
filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string) (bool, error) { filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string, printer progress.Printer) (bool, error) {
wg, wgCtx := errgroup.WithContext(ctx) wg, wgCtx := errgroup.WithContext(ctx)
repo.StartPackUploader(wgCtx, wg) repo.StartPackUploader(wgCtx, wg)
@@ -209,13 +213,13 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
if filteredTree.IsNull() { if filteredTree.IsNull() {
if dryRun { if dryRun {
Verbosef("would delete empty snapshot\n") printer.P("would delete empty snapshot")
} else { } else {
if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil { if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil {
return false, err return false, err
} }
debug.Log("removed empty snapshot %v", sn.ID()) debug.Log("removed empty snapshot %v", sn.ID())
Verbosef("removed empty snapshot %v\n", sn.ID().Str()) printer.P("removed empty snapshot %v", sn.ID().Str())
} }
return true, nil return true, nil
} }
@@ -232,18 +236,18 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
debug.Log("Snapshot %v modified", sn) debug.Log("Snapshot %v modified", sn)
if dryRun { if dryRun {
Verbosef("would save new snapshot\n") printer.P("would save new snapshot")
if forget { if forget {
Verbosef("would remove old snapshot\n") printer.P("would remove old snapshot")
} }
if newMetadata != nil && newMetadata.Time != nil { if newMetadata != nil && newMetadata.Time != nil {
Verbosef("would set time to %s\n", newMetadata.Time) printer.P("would set time to %s", newMetadata.Time)
} }
if newMetadata != nil && newMetadata.Hostname != "" { if newMetadata != nil && newMetadata.Hostname != "" {
Verbosef("would set hostname to %s\n", newMetadata.Hostname) printer.P("would set hostname to %s", newMetadata.Hostname)
} }
return true, nil return true, nil
@@ -261,12 +265,12 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
} }
if newMetadata != nil && newMetadata.Time != nil { if newMetadata != nil && newMetadata.Time != nil {
Verbosef("setting time to %s\n", *newMetadata.Time) printer.P("setting time to %s", *newMetadata.Time)
sn.Time = *newMetadata.Time sn.Time = *newMetadata.Time
} }
if newMetadata != nil && newMetadata.Hostname != "" { if newMetadata != nil && newMetadata.Hostname != "" {
Verbosef("setting host to %s\n", newMetadata.Hostname) printer.P("setting host to %s", newMetadata.Hostname)
sn.Hostname = newMetadata.Hostname sn.Hostname = newMetadata.Hostname
} }
@@ -275,23 +279,25 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
if err != nil { if err != nil {
return false, err return false, err
} }
Verbosef("saved new snapshot %v\n", id.Str()) printer.P("saved new snapshot %v", id.Str())
if forget { if forget {
if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil { if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil {
return false, err return false, err
} }
debug.Log("removed old snapshot %v", sn.ID()) debug.Log("removed old snapshot %v", sn.ID())
Verbosef("removed old snapshot %v\n", sn.ID().Str()) printer.P("removed old snapshot %v", sn.ID().Str())
} }
return true, nil return true, nil
} }
func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, args []string) error { func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, args []string, term *termstatus.Terminal) error {
if !opts.SnapshotSummary && opts.ExcludePatternOptions.Empty() && opts.Metadata.empty() { if !opts.SnapshotSummary && opts.ExcludePatternOptions.Empty() && opts.Metadata.empty() {
return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided") return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
var ( var (
repo *repository.Repository repo *repository.Repository
unlock func() unlock func()
@@ -299,7 +305,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
) )
if opts.Forget { if opts.Forget {
Verbosef("create exclusive lock for repository\n") printer.P("create exclusive lock for repository")
ctx, repo, unlock, err = openWithExclusiveLock(ctx, gopts, opts.DryRun) ctx, repo, unlock, err = openWithExclusiveLock(ctx, gopts, opts.DryRun)
} else { } else {
ctx, repo, unlock, err = openWithAppendLock(ctx, gopts, opts.DryRun) ctx, repo, unlock, err = openWithAppendLock(ctx, gopts, opts.DryRun)
@@ -314,15 +320,15 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
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
} }
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 := rewriteSnapshot(ctx, repo, sn, opts) changed, err := rewriteSnapshot(ctx, repo, sn, opts, printer)
if err != nil { if err != nil {
return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err) return errors.Fatalf("unable to rewrite snapshot ID %q: %v", sn.ID().Str(), err)
} }
@@ -334,18 +340,18 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
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)
} }
} }

View File

@@ -9,6 +9,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" "github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/termstatus"
) )
func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string, forget bool, metadata snapshotMetadataArgs) { func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string, forget bool, metadata snapshotMetadataArgs) {
@@ -20,7 +21,9 @@ func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string,
Metadata: metadata, Metadata: metadata,
} }
rtest.OK(t, runRewrite(context.TODO(), opts, gopts, nil)) rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term *termstatus.Terminal) error {
return runRewrite(context.TODO(), opts, gopts, nil, term)
}))
} }
func createBasicRewriteRepo(t testing.TB, env *testEnvironment) restic.ID { func createBasicRewriteRepo(t testing.TB, env *testEnvironment) restic.ID {
@@ -145,7 +148,9 @@ func TestRewriteSnaphotSummary(t *testing.T) {
defer cleanup() defer cleanup()
createBasicRewriteRepo(t, env) createBasicRewriteRepo(t, env)
rtest.OK(t, runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{})) rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term *termstatus.Terminal) error {
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term)
}))
// no new snapshot should be created as the snapshot already has a summary // no new snapshot should be created as the snapshot already has a summary
snapshots := testListSnapshots(t, env.gopts, 1) snapshots := testListSnapshots(t, env.gopts, 1)
@@ -162,7 +167,9 @@ func TestRewriteSnaphotSummary(t *testing.T) {
unlock() unlock()
// rewrite snapshot and lookup ID of new snapshot // rewrite snapshot and lookup ID of new snapshot
rtest.OK(t, runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{})) rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term *termstatus.Terminal) error {
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term)
}))
newSnapshots := testListSnapshots(t, env.gopts, 2) newSnapshots := testListSnapshots(t, env.gopts, 2)
newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0] newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0]