Squashing migrations
Collapse a plugin's long migration history into one optimized file with squashmigrations, non-destructively — the originals stay on disk and live databases are never rebuilt.
Over a project's life a plugin accumulates migrations: a table here, a column there, a choices tweak later. A fresh database has to replay all of them in order — dozens of CREATE/ALTER steps to reach a schema you could build with a handful of CREATE TABLEs. squashmigrations collapses that history into one optimized migration, without throwing the old files away.
Purpose
umbral squashmigrations <plugin> reads a plugin's entire migration history and writes a single squash file whose operations are the diff from an empty schema to the final snapshot — one CreateTable per model, with every intermediate AlterColumn already folded into the final column set. It is non-destructive: the original migration files stay on disk so older deploys still migrate, and the runner treats the squash and its originals as mutually exclusive. A live database that already ran the originals is never rebuilt — the squash records itself without re-running any DDL.
One example
# Collapse the blog plugin's 0001..0012 into one file.cargo run -- squashmigrations blogSquashed 12 migrations for `blog` into 0001_squashed_0012 wrote migrations/blog/0001_squashed_0012.json replaces: 0001_initial, 0002_add_slug, ... , 0012_add_pinned The originals are kept on disk (non-destructive). `migrate` now applies thesquash on a fresh database and record-only on databases that already ran theoriginals. Once EVERY deploy has migrated past this squash, delete the 12original file(s) it replaces.What happens on the next migrate:
Fresh database
Already-migrated database
Cleanup, later
When it refuses
squashmigrations fails closed rather than produce a squash that could lose work:
- Fewer than two migrations — nothing to collapse.
- A history that already contains a squash — nested squashing isn't supported yet; delete the redundant originals first, then squash again.
- A
RunSqldata migration in the history — a snapshot diff can't see hand-written data steps, so squashing across one would silently drop it. Leave that history intact, or squash only the schema-only migrations around it.
Link to the design
Squash semantics — the replaces field, the fresh / record-only / partial-transition decision, and why the originals are kept — live in arch.md and the migration-engine spec under docs/specs/. The behaviour is gaps2 #100.