Repository: ariga/atlas
Pull request #2645 (2024-03-26 13:51:44)

sql/migrate: fix incorrect line number in checksum error
The first line is the global hash, first file is one line 2, not 1.


Patch:
diff --git a/cmd/atlas/internal/cmdapi/migrate.go b/cmd/atlas/internal/cmdapi/migrate.go
index 24a180cb..269fac17 100644
--- a/cmd/atlas/internal/cmdapi/migrate.go
+++ b/cmd/atlas/internal/cmdapi/migrate.go
@@ -1615,7 +1615,7 @@ func printChecksumError(cmd *cobra.Command, err error) {
 	out := cmd.OutOrStderr()
 	fmt.Fprintln(out, "You have a checksum error in your migration directory.")
 	if csErr := (&migrate.ChecksumError{}); errors.As(err, &csErr) {
-		fmt.Fprintf(out, "\n\tL %d: %s was %s\n\n", csErr.Line, csErr.File, csErr.Reason)
+		fmt.Fprintf(out, "\n\tL%d: %s was %s\n\n", csErr.Line, csErr.File, csErr.Reason)
 	}
 	fmt.Fprintf(
 		out,
diff --git a/sql/migrate/dir.go b/sql/migrate/dir.go
index 74b30ebb..7ad9ba70 100644
--- a/sql/migrate/dir.go
+++ b/sql/migrate/dir.go
@@ -654,10 +654,10 @@ type (
 )
 
 // Error implements the error interface.
-func (err ChecksumError) Error() string { return ErrChecksumMismatch.Error() }
+func (err *ChecksumError) Error() string { return ErrChecksumMismatch.Error() }
 
 // Is exists for backwards compatability reasons.
-func (err ChecksumError) Is(target error) bool {
+func (err *ChecksumError) Is(target error) bool {
 	return errors.Is(ErrChecksumMismatch, target)
 }
 
@@ -707,7 +707,7 @@ func Validate(dir Dir) error {
 				continue
 			}
 			// Index is now pointing at the file with the mismatch.
-			err.Line = i + 1
+			err.Line = i + 2 // first line is global hash
 			err.Pos = pos
 			err.File = h.N
 			switch idx := slices.IndexFunc(ex, func(e struct{ N, H string }) bool { return e.N == h.N }); {
@@ -725,7 +725,7 @@ func Validate(dir Dir) error {
 		}
 		// If we land here, all migrations in the sum file are present unchanged in the computed sum.
 		// But there is a mismatch, meaning the next file in the computed sum was added.
-		err.Line = err.Total + 1
+		err.Line = err.Total + 2 // first line is global hash
 		err.File = ex[err.Total].N
 		err.Pos = pos
 		err.Reason = ReasonAdded


Test patch:
diff --git a/cmd/atlas/internal/cmdapi/migrate_test.go b/cmd/atlas/internal/cmdapi/migrate_test.go
index ebaabfbb..4c49dce4 100644
--- a/cmd/atlas/internal/cmdapi/migrate_test.go
+++ b/cmd/atlas/internal/cmdapi/migrate_test.go
@@ -1470,11 +1470,11 @@ func TestMigrate_Validate(t *testing.T) {
 	s, err = runCmd(migrateValidateCmd(), "--dir", "file://"+p)
 	csErr := &migrate.ChecksumError{}
 	require.ErrorAs(t, err, &csErr)
-	require.Equal(t, 2, csErr.Line)
+	require.Equal(t, 3, csErr.Line)
 	require.Equal(t, "2_second.sql", csErr.File)
 	require.Equal(t, migrate.ReasonAdded, csErr.Reason)
 	require.Contains(t, s, "You have a checksum error")
-	require.Contains(t, s, "L 2: 2_second.sql was added")
+	require.Contains(t, s, "L3: 2_second.sql was added")
 }
 
 func TestMigrate_Hash(t *testing.T) {
diff --git a/cmd/atlas/internal/migratelint/run_test.go b/cmd/atlas/internal/migratelint/run_test.go
index 98bfd52a..1b9061bf 100644
--- a/cmd/atlas/internal/migratelint/run_test.go
+++ b/cmd/atlas/internal/migratelint/run_test.go
@@ -44,7 +44,7 @@ func TestRunner_Run(t *testing.T) {
 		require.ErrorAs(t, r.Run(ctx), &err)
 		require.Regexp(t, `Analyzing changes \(1 migration in total\):
 
-  Error: checksum mismatch \(atlas.sum\): L1: 1\.sql was added
+  Error: checksum mismatch \(atlas.sum\): L2: 1\.sql was added
 
   -------------------------
   -- .*
@@ -57,7 +57,7 @@ func TestRunner_Run(t *testing.T) {
 		require.ErrorAs(t, r.Run(ctx), &err)
 		require.Regexp(t, `Analyzing changes \(1 migration in total\):
 
-  Error: checksum mismatch \(atlas.sum\): L1: 1\.sql was edited
+  Error: checksum mismatch \(atlas.sum\): L2: 1\.sql was edited
 
   -------------------------
   -- .*
@@ -71,7 +71,7 @@ func TestRunner_Run(t *testing.T) {
 		require.ErrorAs(t, r.Run(ctx), &err)
 		require.Regexp(t, `Analyzing changes \(1 migration in total\):
 
-  Error: checksum mismatch \(atlas.sum\): L1: 1\.sql was removed
+  Error: checksum mismatch \(atlas.sum\): L2: 1\.sql was removed
 
   -------------------------
   -- .*
@@ -86,7 +86,7 @@ func TestRunner_Run(t *testing.T) {
 		require.ErrorAs(t, r.Run(ctx), &err)
 		require.Regexp(t, `Analyzing changes \(1 migration in total\):
 
-  Error: checksum mismatch \(atlas.sum\): L2: 2\.sql was added
+  Error: checksum mismatch \(atlas.sum\): L3: 2\.sql was added
 
   -------------------------
   -- .*
diff --git a/sql/migrate/dir_test.go b/sql/migrate/dir_test.go
index 4f5098d7..d9914a52 100644
--- a/sql/migrate/dir_test.go
+++ b/sql/migrate/dir_test.go
@@ -91,7 +91,7 @@ func TestValidate(t *testing.T) {
 	d, err := migrate.NewLocalDir(p)
 	require.NoError(t, err)
 	require.NoError(t, d.WriteFile("atlas.sum", hash))
-	require.Equal(t, removed(1, 2, 48, "1_initial.down.sql"), migrate.Validate(d))
+	require.Equal(t, removed(2, 2, 48, "1_initial.down.sql"), migrate.Validate(d))
 
 	td := "testdata/migrate"
 	d, err = migrate.NewLocalDir(td)
@@ -128,13 +128,13 @@ func TestValidate(t *testing.T) {
 	t.Cleanup(func() {
 		require.NoError(t, os.WriteFile(filepath.Join(td, "1_initial.up.sql"), initialUp, 0644))
 	})
-	require.Equal(t, edited(2, 2, 115, "1_initial.up.sql"), migrate.Validate(d))
+	require.Equal(t, edited(3, 2, 115, "1_initial.up.sql"), migrate.Validate(d))
 	require.NoError(t, os.WriteFile(filepath.Join(td, "1_initial.up.sql"), initialUp, 0644))
 
 	// Adding a file at the end.
 	require.NoError(t, os.WriteFile(filepath.Join(td, "2_second.sql"), []byte("stmt"), os.ModePerm))
 	t.Cleanup(func() { os.Remove(filepath.Join(td, "2_second.sql")) })
-	require.Equal(t, added(3, 2, 180, "2_second.sql"), migrate.Validate(d))
+	require.Equal(t, added(4, 2, 180, "2_second.sql"), migrate.Validate(d))
 	require.NoError(t, os.Remove(filepath.Join(td, "2_second.sql")))
 
 	// Changing the filename should raise validation error.
@@ -142,14 +142,14 @@ func TestValidate(t *testing.T) {
 	t.Cleanup(func() {
 		require.NoError(t, os.Rename(filepath.Join(td, "1_first.up.sql"), filepath.Join(td, "1_initial.up.sql")))
 	})
-	require.Equal(t, added(1, 2, 48, "1_first.up.sql"), migrate.Validate(d))
+	require.Equal(t, added(2, 2, 48, "1_first.up.sql"), migrate.Validate(d))
 
 	// Removing it as well (move it out of the dir).
 	require.NoError(t, os.Rename(filepath.Join(td, "1_first.up.sql"), filepath.Join(td, "..", "bak")))
 	t.Cleanup(func() {
 		require.NoError(t, os.Rename(filepath.Join(td, "..", "bak"), filepath.Join(td, "1_first.up.sql")))
 	})
-	require.Equal(t, removed(2, 2, 115, "1_initial.up.sql"), migrate.Validate(d))
+	require.Equal(t, removed(3, 2, 115, "1_initial.up.sql"), migrate.Validate(d))
 }
 
 func TestHash_MarshalText(t *testing.T) {