Skip to content

Commit d7db2c2

Browse files
authored
Dedupe migrations, add check to prevent low entropy names (#3366)
1 parent 42b8785 commit d7db2c2

3 files changed

Lines changed: 34 additions & 12 deletions

File tree

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: Out of Order Migrations
22

3-
on: [workflow_call]
3+
on:
4+
workflow_call:
5+
merge_group:
46

57
jobs:
68
check:
@@ -15,24 +17,44 @@ jobs:
1517

1618
- name: Compare migrations
1719
run: |
18-
# Get all migration versions from main
19-
git ls-tree -r origin/main --name-only | grep '^packages/db/migrations/' | grep -oE '[0-9]{14}' | sort -n > main_versions.txt
20-
21-
# Find the highest version number from main
22-
HIGHEST_MAIN=$(tail -n1 main_versions.txt)
20+
# Fail if any two migrations share the same 14-digit version. We check the
21+
# final directory state (not new-vs-main), which is both simpler and correct:
22+
# a duplicate is a duplicate regardless of which commit introduced it. On a
23+
# `merge_group` event the checkout contains every queued PR, so this catches
24+
# collisions between commits that would each pass against main on their own.
25+
DUPES=$(ls packages/db/migrations/ | grep -oE '^[0-9]{14}' | sort | uniq -d)
26+
if [ -n "$DUPES" ]; then
27+
echo "❌ Duplicate migration version(s):"
28+
echo "$DUPES"
29+
echo "Two migrations share the same timestamp. Regenerate one with 'date +%Y%m%d%H%M%S'."
30+
exit 1
31+
fi
32+
33+
# Find the highest migration version on main. "Out of order" is inherently
34+
# relative to what's already applied, so this check still needs main.
35+
HIGHEST_MAIN=$(git ls-tree -r origin/main --name-only | grep '^packages/db/migrations/' | grep -oE '[0-9]{14}' | sort -n | tail -n1)
2336
echo "Highest main migration version: $HIGHEST_MAIN"
24-
25-
# Find newly added migration files in this PR
37+
38+
# Newly added migrations must be newer than everything on main and must not
39+
# use placeholder timestamps.
2640
NEW_FILES=$(git diff --name-status origin/main -- packages/db/migrations/ | grep '^A' | awk '{print $2}')
27-
2841
for file in $NEW_FILES; do
2942
version=$(basename "$file" | grep -oE '^[0-9]{14}')
3043
echo "Checking new migration version: $version"
3144
if [ "$version" -le "$HIGHEST_MAIN" ]; then
3245
echo "❌ Migration $file is out of order! ($version <= $HIGHEST_MAIN)"
3346
exit 1
3447
fi
48+
49+
# Reject placeholder timestamps (last 6 digits = HHMMSS) that cause
50+
# same-day version collisions. Use a real timestamp instead:
51+
# date +%Y%m%d%H%M%S
52+
time_part="${version: -6}"
53+
if [ "$time_part" = "120000" ] || [ "$time_part" = "000000" ]; then
54+
echo "❌ Migration $file uses a placeholder timestamp ($time_part)! Generate a real timestamp with 'date +%Y%m%d%H%M%S'."
55+
exit 1
56+
fi
3557
done
36-
37-
echo "✅ All new migrations are in correct order."
58+
59+
echo "✅ All new migrations are in correct order and free of collisions."
3860

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ go test -race -v -run TestCreateSandbox ./internal/handlers
216216

217217
### Database Migrations
218218
- Migrations: `packages/db/migrations/`
219-
- Create: Add new `XXXXXX_name.sql` file
219+
- Create: `cd packages/db && make create-migration NAME=your-migration-name` — this generates the file with a correct `YYYYMMDDHHMMSS` timestamp. Do NOT hand-create migration files; placeholder timestamps like `120000`/`000000` cause same-day version collisions and are rejected by the out-of-order-migrations CI check.
220220
- Apply: `make migrate` (requires POSTGRES_CONNECTION_STRING)
221221
- Code generation: `make generate/db` (regenerates sqlc code)
222222

packages/db/migrations/20260723120000_rename_default_team_names_to_project.sql renamed to packages/db/migrations/20260723120001_rename_default_team_names_to_project.sql

File renamed without changes.

0 commit comments

Comments
 (0)