Skip to content

Commit 3b052ca

Browse files
committed
feat: try to detect changes in migrations (not just snapshot)
1 parent ecb4822 commit 3b052ca

2 files changed

Lines changed: 112 additions & 0 deletions

File tree

regresql/regresql.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ func Test(root, runFilter, formatName, outputPath string, commit bool) {
161161
os.Exit(1)
162162
}
163163

164+
// Validate migrations haven't changed since last snapshot build
165+
if err := ValidateMigrationsHash(root); err != nil {
166+
fmt.Printf("Error: %s\n", err)
167+
os.Exit(1)
168+
}
169+
164170
if err := TestConnectionString(config.PgUri); err != nil {
165171
fmt.Print(err.Error())
166172
os.Exit(2)

regresql/snapshot.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,109 @@ Run 'regresql snapshot build --schema=%s' to rebuild the snapshot`,
539539

540540
return nil
541541
}
542+
543+
func ValidateMigrationsHash(root string) error {
544+
snapshotsDir := GetSnapshotsDir(root)
545+
546+
metadata, err := ReadSnapshotMetadata(snapshotsDir)
547+
if err != nil {
548+
return nil // No metadata - already warned by ValidateSchemaHash
549+
}
550+
551+
info := metadata.Current
552+
if info == nil || info.MigrationsDir == "" {
553+
return nil
554+
}
555+
556+
if _, err := os.Stat(info.MigrationsDir); os.IsNotExist(err) {
557+
return nil // Stale metadata - directory no longer exists
558+
}
559+
560+
currentFiles, err := discoverMigrations(info.MigrationsDir)
561+
if err != nil {
562+
return fmt.Errorf("failed to discover migrations in %s: %w", info.MigrationsDir, err)
563+
}
564+
565+
if len(currentFiles) == 0 && info.MigrationsHash == "" {
566+
return nil // No migrations before, none now
567+
}
568+
569+
if len(currentFiles) == 0 {
570+
return migrationChangeError(info, "", nil, info.MigrationsApplied)
571+
}
572+
573+
currentHash, err := computeMigrationsHash(currentFiles)
574+
if err != nil {
575+
return fmt.Errorf("failed to hash migrations: %w", err)
576+
}
577+
578+
if currentHash == info.MigrationsHash {
579+
return nil
580+
}
581+
582+
// Detect what changed
583+
currentNames := make([]string, len(currentFiles))
584+
for i, f := range currentFiles {
585+
currentNames[i] = filepath.Base(f)
586+
}
587+
588+
return migrationChangeError(info, currentHash, currentNames, info.MigrationsApplied)
589+
}
590+
591+
func migrationChangeError(info *SnapshotInfo, currentHash string, current, stored []string) error {
592+
currentSet := make(map[string]bool)
593+
for _, name := range current {
594+
currentSet[name] = true
595+
}
596+
storedSet := make(map[string]bool)
597+
for _, name := range stored {
598+
storedSet[name] = true
599+
}
600+
601+
var added, removed []string
602+
for _, name := range current {
603+
if !storedSet[name] {
604+
added = append(added, name)
605+
}
606+
}
607+
for _, name := range stored {
608+
if !currentSet[name] {
609+
removed = append(removed, name)
610+
}
611+
}
612+
613+
var changes strings.Builder
614+
changes.WriteString("\n Changes detected:")
615+
if len(added) == 0 && len(removed) == 0 {
616+
changes.WriteString("\n ~ content modified")
617+
}
618+
for _, name := range added {
619+
changes.WriteString("\n + ")
620+
changes.WriteString(name)
621+
}
622+
for _, name := range removed {
623+
changes.WriteString("\n - ")
624+
changes.WriteString(name)
625+
}
626+
627+
expectedHash := info.MigrationsHash
628+
if expectedHash != "" {
629+
expectedHash = expectedHash[:20] + "..."
630+
} else {
631+
expectedHash = "(none)"
632+
}
633+
if currentHash != "" {
634+
currentHash = currentHash[:20] + "..."
635+
} else {
636+
currentHash = "(empty)"
637+
}
638+
639+
return fmt.Errorf(`migrations have changed since last snapshot build
640+
641+
Migrations dir: %s
642+
Expected hash: %s
643+
Current hash: %s%s
644+
645+
Run 'regresql snapshot build --migrations=%s' to rebuild the snapshot`,
646+
info.MigrationsDir, expectedHash, currentHash, changes.String(), info.MigrationsDir)
647+
}

0 commit comments

Comments
 (0)