Skip to content

Commit a383837

Browse files
authored
sg: Add sg migration revert <commit> (sourcegraph#30772)
1 parent 5f47bee commit a383837

File tree

7 files changed

+236
-90
lines changed

7 files changed

+236
-90
lines changed

dev/sg/internal/db/db.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ var (
6565
DefaultDatabase = databases[0]
6666
)
6767

68+
func Databases() []Database {
69+
c := make([]Database, len(databases))
70+
copy(c, databases)
71+
return c
72+
}
73+
6874
func DatabaseNames() []string {
6975
databaseNames := make([]string, 0, len(databases))
7076
for _, database := range databases {

dev/sg/internal/migration/add.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import (
77
"time"
88

99
"github.com/sourcegraph/sourcegraph/dev/sg/internal/db"
10+
"github.com/sourcegraph/sourcegraph/dev/sg/internal/stdout"
11+
"github.com/sourcegraph/sourcegraph/lib/output"
1012
)
1113

12-
const metadataFileTemplate = `name: %s
14+
const newMetadataFileTemplate = `name: %s
1315
parents: [%s]
1416
`
1517

16-
const upMigrationFileTemplate = `BEGIN;
18+
const newUpMigrationFileTemplate = `BEGIN;
1719
1820
-- Perform migration here.
1921
--
@@ -29,7 +31,7 @@ const upMigrationFileTemplate = `BEGIN;
2931
COMMIT;
3032
`
3133

32-
const downMigrationFileTemplate = `BEGIN;
34+
const newDownMigrationFileTemplate = `BEGIN;
3335
3436
-- Undo the changes made in the up migration
3537
@@ -38,10 +40,14 @@ COMMIT;
3840

3941
// Add creates a new directory with stub migration files in the given schema and returns the
4042
// names of the newly created files. If there was an error, the filesystem is rolled-back.
41-
func Add(database db.Database, migrationName string) (up, down, metadata string, _ error) {
43+
func Add(database db.Database, migrationName string) error {
44+
return add(database, migrationName, newUpMigrationFileTemplate, newDownMigrationFileTemplate)
45+
}
46+
47+
func add(database db.Database, migrationName, upMigrationFileTemplate, downMigrationFileTemplate string) error {
4248
definitions, err := readDefinitions(database)
4349
if err != nil {
44-
return "", "", "", err
50+
return err
4551
}
4652

4753
leaves := definitions.Leaves()
@@ -50,23 +56,27 @@ func Add(database db.Database, migrationName string) (up, down, metadata string,
5056
parents = append(parents, leaf.ID)
5157
}
5258

53-
id := int(time.Now().UTC().Unix())
54-
55-
upPath, downPath, metadataPath, err := makeMigrationFilenames(database, id)
59+
files, err := makeMigrationFilenames(database, int(time.Now().UTC().Unix()))
5660
if err != nil {
57-
return "", "", "", err
61+
return err
5862
}
5963

6064
contents := map[string]string{
61-
upPath: upMigrationFileTemplate,
62-
downPath: downMigrationFileTemplate,
63-
metadataPath: fmt.Sprintf(metadataFileTemplate, migrationName, strings.Join(intsToStrings(parents), ", ")),
65+
files.UpFile: upMigrationFileTemplate,
66+
files.DownFile: downMigrationFileTemplate,
67+
files.MetadataFile: fmt.Sprintf(newMetadataFileTemplate, migrationName, strings.Join(intsToStrings(parents), ", ")),
6468
}
6569
if err := writeMigrationFiles(contents); err != nil {
66-
return "", "", "", err
70+
return err
6771
}
6872

69-
return upPath, downPath, metadataPath, nil
73+
block := stdout.Out.Block(output.Linef("", output.StyleBold, "Migration files created"))
74+
block.Writef("Up query file: %s", files.UpFile)
75+
block.Writef("Down query file: %s", files.DownFile)
76+
block.Writef("Metadata file: %s", files.MetadataFile)
77+
block.Close()
78+
79+
return nil
7080
}
7181

7282
func intsToStrings(ints []int) []string {

dev/sg/internal/migration/revert.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package migration
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/sourcegraph/sourcegraph/dev/sg/internal/db"
10+
"github.com/sourcegraph/sourcegraph/dev/sg/internal/run"
11+
"github.com/sourcegraph/sourcegraph/dev/sg/internal/stdout"
12+
"github.com/sourcegraph/sourcegraph/internal/database/migration/definition"
13+
"github.com/sourcegraph/sourcegraph/lib/errors"
14+
"github.com/sourcegraph/sourcegraph/lib/output"
15+
)
16+
17+
// Revert creates a new migration that reverts the set of migrations from a target commit.
18+
func Revert(databases []db.Database, commit string) error {
19+
versionsByDatabase := make(map[string][]int, len(databases))
20+
for _, database := range databases {
21+
definitions, err := readDefinitions(database)
22+
if err != nil {
23+
return err
24+
}
25+
26+
versions, err := selectMigrationsDefinedInCommit(database, definitions, commit)
27+
if err != nil {
28+
return err
29+
}
30+
31+
versionsByDatabase[database.Name] = versions
32+
}
33+
34+
redacted := false
35+
for name, versions := range versionsByDatabase {
36+
if len(versions) == 0 {
37+
continue
38+
}
39+
redacted = true
40+
41+
var (
42+
database, _ = db.DatabaseByName(name)
43+
upPaths = make([]string, 0, len(versions))
44+
downQueries = make([]string, 0, len(versions))
45+
)
46+
47+
for _, version := range versions {
48+
files, err := makeMigrationFilenames(database, version)
49+
if err != nil {
50+
return err
51+
}
52+
53+
downQuery, err := os.ReadFile(files.DownFile)
54+
if err != nil {
55+
return err
56+
}
57+
upPaths = append(upPaths, files.UpFile)
58+
downQueries = append(downQueries, string(downQuery))
59+
60+
contents := map[string]string{
61+
files.UpFile: "-- REDACTED\n",
62+
}
63+
if err := writeMigrationFiles(contents); err != nil {
64+
return err
65+
}
66+
}
67+
68+
block := stdout.Out.Block(output.Linef("", output.StyleBold, "Migration files redacted"))
69+
for _, path := range upPaths {
70+
block.Writef("Up query file: %s", path)
71+
}
72+
block.Close()
73+
74+
if err := add(database, fmt.Sprintf("revert %s", commit), strings.Join(downQueries, "\n\n"), "-- No-op\n"); err != nil {
75+
return err
76+
}
77+
}
78+
if !redacted {
79+
return errors.Newf("No migrations defined on commit %q", commit)
80+
}
81+
82+
return nil
83+
}
84+
85+
// selectMigrationsDefinedInCommit returns the identifiers of migrations defined in the given
86+
// commit for the given schema.a
87+
func selectMigrationsDefinedInCommit(database db.Database, ds *definition.Definitions, commit string) ([]int, error) {
88+
migrationsDir := filepath.Join("migrations", database.Name)
89+
90+
output, err := run.GitCmd("diff", "--name-only", commit+".."+commit+"~1", migrationsDir)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
versions := parseVersions(strings.Split(output, "\n"), migrationsDir)
96+
return versions, nil
97+
}

dev/sg/internal/migration/squash.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,24 @@ func Squash(database db.Database, commit string) error {
5454
block := stdout.Out.Block(output.Linef("", output.StyleBold, "Updated filesystem"))
5555
defer block.Close()
5656

57-
upPath, downPath, metadataPath, err := makeMigrationFilenames(database, newRoot)
57+
files, err := makeMigrationFilenames(database, newRoot)
5858
if err != nil {
5959
return err
6060
}
6161

6262
contents := map[string]string{
63-
upPath: squashedUpMigration,
64-
downPath: squashedDownMigration,
65-
metadataPath: "name: 'squashed migrations'\n",
63+
files.UpFile: squashedUpMigration,
64+
files.DownFile: squashedDownMigration,
65+
files.MetadataFile: "name: 'squashed migrations'\n",
6666
}
6767

6868
if err := writeMigrationFiles(contents); err != nil {
6969
return err
7070
}
7171

72-
block.Writef("Created: %s", upPath)
73-
block.Writef("Created: %s", downPath)
74-
block.Writef("Created: %s", metadataPath)
72+
block.Writef("Created: %s", files.UpFile)
73+
block.Writef("Created: %s", files.DownFile)
74+
block.Writef("Created: %s", files.MetadataFile)
7575

7676
// Remove the migration file pairs that were just squashed
7777
filenames, err := removeAncestorsOf(database, definitions, newRoot)
@@ -97,16 +97,8 @@ func selectNewRootMigration(database db.Database, ds *definition.Definitions, co
9797
if err != nil {
9898
return 0, false, err
9999
}
100-
lines := strings.Split(output, "\n")
101100

102-
versions := make([]int, 0, len(lines))
103-
for _, filename := range lines {
104-
if version, err := strconv.Atoi(strings.Split(filename, "_")[0]); err == nil {
105-
versions = append(versions, version)
106-
}
107-
}
108-
109-
ds, err = ds.Filter(versions)
101+
ds, err = ds.Filter(parseVersions(strings.Split(output, "\n"), migrationsDir))
110102
if err != nil {
111103
return 0, false, err
112104
}

dev/sg/internal/migration/util.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"sort"
8+
"strconv"
9+
"strings"
710

811
"github.com/sourcegraph/sourcegraph/dev/sg/internal/db"
912
"github.com/sourcegraph/sourcegraph/dev/sg/root"
@@ -20,17 +23,23 @@ func readDefinitions(database db.Database) (*definition.Definitions, error) {
2023
return definition.ReadDefinitions(fs)
2124
}
2225

26+
type MigrationFiles struct {
27+
UpFile string
28+
DownFile string
29+
MetadataFile string
30+
}
31+
2332
// makeMigrationFilenames makes a pair of (absolute) paths to migration files with the given migration index.
24-
func makeMigrationFilenames(database db.Database, migrationIndex int) (up, down, metadata string, _ error) {
33+
func makeMigrationFilenames(database db.Database, migrationIndex int) (MigrationFiles, error) {
2534
baseDir, err := migrationDirectoryForDatabase(database)
2635
if err != nil {
27-
return "", "", "", err
36+
return MigrationFiles{}, err
2837
}
2938

3039
upPath := filepath.Join(baseDir, fmt.Sprintf("%d/up.sql", migrationIndex))
3140
downPath := filepath.Join(baseDir, fmt.Sprintf("%d/down.sql", migrationIndex))
3241
metadataPath := filepath.Join(baseDir, fmt.Sprintf("%d/metadata.yaml", migrationIndex))
33-
return upPath, downPath, metadataPath, nil
42+
return MigrationFiles{upPath, downPath, metadataPath}, nil
3443
}
3544

3645
// migrationDirectoryForDatabase returns the directory where migration files are stored for the
@@ -67,3 +76,39 @@ func writeMigrationFiles(contents map[string]string) (err error) {
6776

6877
return nil
6978
}
79+
80+
// parseVersions takes a list of filepaths (the output of some git command) and a base
81+
// migrations directory and returns the versions of migrations present in the list.
82+
func parseVersions(lines []string, migrationsDir string) []int {
83+
var (
84+
pathSeparator = string(os.PathSeparator)
85+
prefixesToTrim = []string{migrationsDir, pathSeparator}
86+
separatorsToSplitBy = []string{pathSeparator, "_"}
87+
)
88+
89+
versionMap := make(map[int]struct{}, len(lines))
90+
for _, rawVersion := range lines {
91+
// Remove leading migration directory if it exists
92+
for _, prefix := range prefixesToTrim {
93+
rawVersion = strings.TrimPrefix(rawVersion, prefix)
94+
}
95+
96+
// Remove trailing filepath (if dir) or name prefix (if old migration)
97+
for _, separator := range separatorsToSplitBy {
98+
rawVersion = strings.Split(rawVersion, separator)[0]
99+
}
100+
101+
// Should be left with only a version number
102+
if version, err := strconv.Atoi(rawVersion); err == nil {
103+
versionMap[version] = struct{}{}
104+
}
105+
}
106+
107+
versions := make([]int, 0, len(versionMap))
108+
for version := range versionMap {
109+
versions = append(versions, version)
110+
}
111+
sort.Ints(versions)
112+
113+
return versions
114+
}

0 commit comments

Comments
 (0)