migrate --stop-before: validate names and fix legacy compatibility

An unknown --stop-before value previously matched nothing and every
migration was silently applied, including the one the user meant to
stop before. The CLI now errors unless each value matches a known
migration.

The CLI also always passed stop_before as a list, but older
duck-typed sqlite-migrate Migrations objects compare it against a
single string name - so the flag was silently ignored for exactly
the migration files the compatibility shim exists to support. Legacy
sets now receive a single string, with an error if multiple values
target one legacy set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd
This commit is contained in:
Claude 2026-07-04 19:08:53 +00:00
commit 0bad21280f
No known key found for this signature in database
3 changed files with 158 additions and 4 deletions

View file

@ -3400,11 +3400,38 @@ def migrate(db_path, migrations, stop_before, list_, verbose):
click.echo("\nSchema before:\n")
click.echo(textwrap.indent(prev_schema, " ") or " (empty)")
click.echo()
if stop_before:
# Every --stop-before value must match at least one known migration
known_names = set()
for migration_set in migration_sets:
names = {m.name for m in migration_set.pending(db)}
names.update(m.name for m in migration_set.applied(db))
known_names.update(names)
known_names.update(
"{}:{}".format(migration_set.name, name) for name in names
)
unknown = [value for value in stop_before if value not in known_names]
if unknown:
raise click.ClickException(
"--stop-before did not match any migrations: {}".format(
", ".join(unknown)
)
)
for migration_set in migration_sets:
migration_set.apply(
db,
stop_before=_stop_before_for_migration_set(stop_before, migration_set.name),
)
matches = _stop_before_for_migration_set(stop_before, migration_set.name)
if isinstance(migration_set, sqlite_utils.Migrations):
migration_set.apply(db, stop_before=matches)
else:
# Legacy sqlite-migrate Migrations objects take a single string
# for stop_before, not a list
distinct = list(dict.fromkeys(matches))
if len(distinct) > 1:
raise click.ClickException(
"Migration set '{}' uses the older sqlite-migrate class, "
"which only supports a single --stop-before value - "
"got: {}".format(migration_set.name, ", ".join(distinct))
)
migration_set.apply(db, stop_before=distinct[0] if distinct else None)
if verbose:
click.echo("Schema after:\n")
post_schema = db.schema