diff --git a/cmd/goose/main.go b/cmd/goose/main.go index 1b9e1fda5..04b07f3f1 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -10,13 +10,14 @@ import ( ) var ( - flags = flag.NewFlagSet("goose", flag.ExitOnError) - dir = flags.String("dir", ".", "directory with migration files") - table = flags.String("table", "goose_db_version", "migrations table name") - verbose = flags.Bool("v", false, "enable verbose mode") - help = flags.Bool("h", false, "print help") - version = flags.Bool("version", false, "print version") - certfile = flags.String("certfile", "", "file path to root CA's certificates in pem format (only support on mysql)") + flags = flag.NewFlagSet("goose", flag.ExitOnError) + dir = flags.String("dir", ".", "directory with migration files") + table = flags.String("table", "goose_db_version", "migrations table name") + verbose = flags.Bool("v", false, "enable verbose mode") + help = flags.Bool("h", false, "print help") + version = flags.Bool("version", false, "print version") + certfile = flags.String("certfile", "", "file path to root CA's certificates in pem format (only support on mysql)") + sequential = flags.Bool("s", false, "use sequential numbering for new migrations") ) func main() { @@ -30,6 +31,9 @@ func main() { if *verbose { goose.SetVerbose(true) } + if *sequential { + goose.SetSequential(true) + } goose.SetTableName(*table) args := flags.Args() diff --git a/create.go b/create.go index 55fe5409a..d361070f1 100644 --- a/create.go +++ b/create.go @@ -16,9 +16,38 @@ type tmplVars struct { CamelName string } +var ( + sequential = false +) + +// SetSequential set whether to use sequential versioning instead of timestamp based versioning +func SetSequential(s bool) { + sequential = s +} + // Create writes a new blank migration file. func CreateWithTemplate(db *sql.DB, dir string, tmpl *template.Template, name, migrationType string) error { - version := time.Now().Format(timestampFormat) + var version string + if sequential { + migrations, err := CollectMigrations(dir, minVersion, maxVersion) + if err != nil { + return err + } + + vMigrations, err := migrations.versioned() + if err != nil { + return err + } + + if last, err := vMigrations.Last(); err == nil { + version = fmt.Sprintf(seqVersionTemplate, last.Version+1) + } else { + version = fmt.Sprintf(seqVersionTemplate, int64(1)) + } + } else { + version = time.Now().Format(timestampFormat) + } + filename := fmt.Sprintf("%v_%v.%v", version, snakeCase(name), migrationType) if tmpl == nil { diff --git a/create_test.go b/create_test.go new file mode 100644 index 000000000..d243e211a --- /dev/null +++ b/create_test.go @@ -0,0 +1,55 @@ +package goose + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +func TestSequential(t *testing.T) { + t.Parallel() + + dir, err := ioutil.TempDir("", "tmptest") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(dir) // clean up + defer os.Remove("./bin/create-goose") // clean up + + commands := []string{ + "go build -o ./bin/create-goose ./cmd/goose", + fmt.Sprintf("./bin/create-goose -s -dir=%s create create_table", dir), + fmt.Sprintf("./bin/create-goose -s -dir=%s create add_users", dir), + fmt.Sprintf("./bin/create-goose -s -dir=%s create add_indices", dir), + fmt.Sprintf("./bin/create-goose -s -dir=%s create update_users", dir), + } + + for _, cmd := range commands { + args := strings.Split(cmd, " ") + time.Sleep(1 * time.Second) + cmd := exec.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%s:\n%v\n\n%s", err, cmd, out) + } + } + + files, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + // check that the files are in order + for i, f := range files { + expected := fmt.Sprintf("%05v", i+1) + if !strings.HasPrefix(f.Name(), expected) { + t.Errorf("failed to find %s prefix in %s", expected, f.Name()) + } + } +} diff --git a/fix.go b/fix.go index dcd801000..20eaeea20 100644 --- a/fix.go +++ b/fix.go @@ -7,6 +7,8 @@ import ( "strings" ) +const seqVersionTemplate = "%05v" + func Fix(dir string) error { migrations, err := CollectMigrations(dir, minVersion, maxVersion) if err != nil { @@ -32,7 +34,12 @@ func Fix(dir string) error { // fix filenames by replacing timestamps with sequential versions for _, tsm := range tsMigrations { oldPath := tsm.Source - newPath := strings.Replace(oldPath, fmt.Sprintf("%d", tsm.Version), fmt.Sprintf("%05v", version), 1) + newPath := strings.Replace( + oldPath, + fmt.Sprintf("%d", tsm.Version), + fmt.Sprintf(seqVersionTemplate, version), + 1, + ) if err := os.Rename(oldPath, newPath); err != nil { return err