Files
restic/cmd/restic/cmd_migrate.go
T
Michael Eischer d9d54a505e restic: move Printer interface from internal/ui/progress
Move Printer and NewNoopPrinter to internal/restic so repository does
not have to import the ui packages.
2026-06-20 17:49:20 +02:00

152 lines
3.8 KiB
Go

package main
import (
"context"
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/migrations"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func newMigrateCommand(globalOptions *global.Options) *cobra.Command {
var opts MigrateOptions
cmd := &cobra.Command{
Use: "migrate [flags] [migration name] [...]",
Short: "Apply migrations",
Long: `
The "migrate" command checks which migrations can be applied for a repository
and prints a list with available migration names. If one or more migration
names are specified, these migrations are applied.
EXIT STATUS
===========
Exit status is 0 if the command was successful.
Exit status is 1 if there was any error.
Exit status is 10 if the repository does not exist.
Exit status is 11 if the repository is already locked.
Exit status is 12 if the password is incorrect.
`,
DisableAutoGenTag: true,
GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error {
return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
},
}
opts.AddFlags(cmd.Flags())
return cmd
}
// MigrateOptions bundles all options for the 'check' command.
type MigrateOptions struct {
Force bool
}
func (opts *MigrateOptions) AddFlags(f *pflag.FlagSet) {
f.BoolVarP(&opts.Force, "force", "f", false, `apply a migration a second time`)
}
func checkMigrations(ctx context.Context, repo restic.Repository, printer restic.Printer) error {
printer.P("available migrations:\n")
found := false
for _, m := range migrations.All {
ok, _, err := m.Check(ctx, repo)
if err != nil {
return err
}
if ok {
printer.P(" %v\t%v\n", m.Name(), m.Desc())
found = true
}
}
if !found {
printer.P("no migrations found\n")
}
return nil
}
func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Options, repo restic.Repository, args []string, term ui.Terminal, printer restic.Printer) error {
var firsterr error
for _, name := range args {
found := false
for _, m := range migrations.All {
if m.Name() == name {
found = true
ok, reason, err := m.Check(ctx, repo)
if err != nil {
return err
}
if !ok {
if !opts.Force {
if reason == "" {
reason = "check failed"
}
printer.E("migration %v cannot be applied: %v\nIf you want to apply this migration anyway, re-run with option --force\n", m.Name(), reason)
continue
}
printer.E("check for migration %v failed, continuing anyway\n", m.Name())
}
if m.RepoCheck() {
printer.P("checking repository integrity...\n")
checkOptions := CheckOptions{}
checkGopts := gopts
// the repository is already locked
checkGopts.NoLock = true
_, err = runCheck(ctx, checkOptions, checkGopts, []string{}, term)
if err != nil {
return err
}
}
printer.P("applying migration %v...\n", m.Name())
if err = m.Apply(ctx, repo); err != nil {
printer.E("migration %v failed: %v\n", m.Name(), err)
if firsterr == nil {
firsterr = err
}
continue
}
printer.P("migration %v: success\n", m.Name())
}
}
if !found {
printer.E("unknown migration %v", name)
}
}
return firsterr
}
func runMigrate(ctx context.Context, opts MigrateOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
return err
}
defer unlock()
if len(args) == 0 {
return checkMigrations(ctx, repo, printer)
}
return applyMigrations(ctx, opts, gopts, repo, args, term, printer)
}