migrate --stop-before an already-applied migration is now an error

The CLI validated --stop-before names against both pending and applied
migrations, but Migrations.apply() only looked for the stop name among
pending ones - naming an applied migration passed validation and then
silently applied every migration after it, the exact outcome the option
exists to prevent. apply() now raises ValueError before applying
anything if a stop_before name matches an applied migration in its set;
names not in the set are still ignored, since unqualified CLI values are
offered to every set. The migrate command reports it as a clean error.

Refs https://github.com/simonw/sqlite-utils/issues/769#issuecomment-4900034150

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Simon Willison 2026-07-06 21:50:05 -07:00
commit b3aa3f47b7
6 changed files with 79 additions and 3 deletions

View file

@ -214,3 +214,33 @@ def test_duplicate_migration_name_errors():
pass
assert "m001" in str(ex.value)
def test_stop_before_applied_migration_errors(migrations):
# Stopping before a migration that has already been applied is
# impossible to honor - previously the stop name was only checked
# against pending migrations, so everything after it was applied
db = sqlite_utils.Database(memory=True)
migrations.apply(db, stop_before="m002") # applies m001 only
with pytest.raises(ValueError) as ex:
migrations.apply(db, stop_before="m001")
assert "m001" in str(ex.value)
assert "already been applied" in str(ex.value)
# Nothing else was applied
assert not db["cats"].exists()
def test_stop_before_applied_migration_errors_before_any_apply(migrations):
# The error fires before any pending migration runs, even those that
# come before the already-applied stop target in registration order
db = sqlite_utils.Database(memory=True)
only_second = Migrations("test")
@only_second()
def m002(db):
db["cats"].create({"name": str})
only_second.apply(db) # m002 applied, m001 still pending
with pytest.raises(ValueError):
migrations.apply(db, stop_before="m002")
assert not db["dogs"].exists()