Fixed #36141 -- Checked for applied replaced migrations recursively.

Regression in 64b1ac7292.
This commit is contained in:
Georgi Yanchev
2025-11-18 08:10:25 -05:00
committed by GitHub
parent 3c005b5f79
commit b07298a73a
3 changed files with 71 additions and 7 deletions

View File

@@ -304,8 +304,7 @@ class MigrationExecutor:
"""
applied = self.recorder.applied_migrations()
for key, migration in self.loader.replacements.items():
all_applied = all(m in applied for m in migration.replaces)
if all_applied and key not in applied:
if key not in applied and self.loader.all_replaced_applied(key, applied):
self.recorder.record_applied(*key)
def detect_soft_applied(self, project_state, migration):

View File

@@ -356,11 +356,8 @@ class MigrationLoader:
if parent not in applied:
# Skip unapplied squashed migrations that have all of their
# `replaces` applied.
if parent in self.replacements:
if all(
m in applied for m in self.replacements[parent].replaces
):
continue
if self.all_replaced_applied(parent.key, applied):
continue
raise InconsistentMigrationHistory(
"Migration {}.{} is applied before its dependency "
"{}.{} on database '{}'.".format(
@@ -372,6 +369,20 @@ class MigrationLoader:
)
)
def all_replaced_applied(self, migration_key, applied):
"""
Checks (recursively) whether all replaced migrations are applied.
"""
if migration_key in self.replacements:
for replaced_key in self.replacements[migration_key].replaces:
if replaced_key not in applied and not self.all_replaced_applied(
replaced_key, applied
):
return False
return True
return False
def detect_conflicts(self):
"""
Look through the loaded graph and detect any conflicts - apps

View File

@@ -3161,6 +3161,60 @@ class SquashMigrationsTests(MigrationTestBase):
]
self.assertNotIn("migrations", applied_app_labels)
def test_double_replaced_migrations_are_checked_correctly(self):
"""
If replaced migrations are already applied and replacing migrations
are not, then migrate should not fail with
InconsistentMigrationHistory.
"""
with self.temporary_migration_module():
call_command(
"makemigrations",
"migrations",
"--empty",
interactive=False,
verbosity=0,
)
call_command(
"makemigrations",
"migrations",
"--empty",
interactive=False,
verbosity=0,
)
call_command(
"makemigrations",
"migrations",
"--empty",
interactive=False,
verbosity=0,
)
call_command(
"makemigrations",
"migrations",
"--empty",
interactive=False,
verbosity=0,
)
call_command("migrate", "migrations", interactive=False, verbosity=0)
call_command(
"squashmigrations",
"migrations",
"0001",
"0002",
interactive=False,
verbosity=0,
)
call_command(
"squashmigrations",
"migrations",
"0001_initial_squashed",
"0003",
interactive=False,
verbosity=0,
)
call_command("migrate", "migrations", interactive=False, verbosity=0)
def test_squashmigrations_initial_attribute(self):
with self.temporary_migration_module(
module="migrations.test_migrations"