107 lines
3.5 KiB
Go
107 lines
3.5 KiB
Go
package commit
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/jackc/pgconn"
|
|
"github.com/jackc/pgx/v4"
|
|
"github.com/spf13/afero"
|
|
"github.com/supabase/cli/internal/db/diff"
|
|
"github.com/supabase/cli/internal/db/dump"
|
|
"github.com/supabase/cli/internal/migration/list"
|
|
"github.com/supabase/cli/internal/migration/repair"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/pkg/migration"
|
|
)
|
|
|
|
func Run(ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error {
|
|
if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error {
|
|
return run(p, ctx, schema, config, fsys)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("Finished " + utils.Aqua("supabase db remote commit") + ".")
|
|
return nil
|
|
}
|
|
|
|
func run(p utils.Program, ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error {
|
|
// 1. Assert `supabase/migrations` and `schema_migrations` are in sync.
|
|
w := utils.StatusWriter{Program: p}
|
|
conn, err := utils.ConnectByConfigStream(ctx, config, w)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer conn.Close(context.Background())
|
|
if err := assertRemoteInSync(ctx, conn, fsys); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 2. Fetch remote schema changes
|
|
if len(schema) == 0 {
|
|
schema, err = migration.ListUserSchemas(ctx, conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
timestamp := utils.GetCurrentTimestamp()
|
|
if err := fetchRemote(p, ctx, schema, timestamp, config, fsys); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 3. Insert a row to `schema_migrations`
|
|
return repair.UpdateMigrationTable(ctx, conn, []string{timestamp}, repair.Applied, false, fsys)
|
|
}
|
|
|
|
func fetchRemote(p utils.Program, ctx context.Context, schema []string, timestamp string, config pgconn.Config, fsys afero.Fs) error {
|
|
path := filepath.Join(utils.MigrationsDir, timestamp+"_remote_commit.sql")
|
|
// Special case if this is the first migration
|
|
if migrations, err := migration.ListLocalMigrations(utils.MigrationsDir, afero.NewIOFS(fsys)); err != nil {
|
|
return err
|
|
} else if len(migrations) == 0 {
|
|
p.Send(utils.StatusMsg("Committing initial migration on remote database..."))
|
|
return dump.Run(ctx, path, config, nil, nil, false, false, false, false, false, fsys)
|
|
}
|
|
|
|
w := utils.StatusWriter{Program: p}
|
|
// Diff remote db (source) & shadow db (target) and write it as a new migration.
|
|
output, err := diff.DiffDatabase(ctx, schema, config, w, fsys, diff.DiffSchemaMigra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(output) == 0 {
|
|
return errors.New("No schema changes found")
|
|
}
|
|
return utils.WriteFile(path, []byte(output), fsys)
|
|
}
|
|
|
|
func assertRemoteInSync(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error {
|
|
remoteMigrations, err := migration.ListRemoteMigrations(ctx, conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
localMigrations, err := list.LoadLocalVersions(fsys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conflictErr := errors.New("The remote database's migration history is not in sync with the contents of " + utils.Bold(utils.MigrationsDir) + `. Resolve this by:
|
|
- Updating the project from version control to get the latest ` + utils.Bold(utils.MigrationsDir) + `,
|
|
- Pushing unapplied migrations with ` + utils.Aqua("supabase db push") + `,
|
|
- Or failing that, manually editing supabase_migrations.schema_migrations table with ` + utils.Aqua("supabase migration repair") + ".")
|
|
if len(remoteMigrations) != len(localMigrations) {
|
|
return conflictErr
|
|
}
|
|
|
|
for i, remoteTimestamp := range remoteMigrations {
|
|
if localMigrations[i] != remoteTimestamp {
|
|
return conflictErr
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|