package main import ( "context" "github.com/restic/restic/internal/migrations" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/termstatus" "github.com/spf13/cobra" "github.com/spf13/pflag" ) func newMigrateCommand() *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 { term, cancel := setupTermstatus() defer cancel() return runMigrate(cmd.Context(), opts, globalOptions, args, 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 progress.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 GlobalOptions, repo restic.Repository, args []string, term *termstatus.Terminal, printer progress.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 GlobalOptions, args []string, term *termstatus.Terminal) error { printer := newTerminalProgressPrinter(gopts.verbosity, term) ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false) 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) }