diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py index 4487543f16..fb9c7a93a4 100644 --- a/django/db/migrations/executor.py +++ b/django/db/migrations/executor.py @@ -116,8 +116,8 @@ class MigrationExecutor(object): if key in self.loader.graph.nodes } for migration, _ in full_plan: - if not migrations_to_run: - # We remove every migration that we applied from this set so + if not migrations_to_run and not applied_migrations: + # We remove every migration that we applied from these sets so # that we can bail out once the last migration has been applied # and don't always run until the very end of the migration # process. @@ -136,6 +136,7 @@ class MigrationExecutor(object): # to make sure the resulting state doesn't include changes # from unrelated migrations. migration.mutate_state(state, preserve=False) + applied_migrations.remove(migration) return state diff --git a/tests/migrations/migrations_test_apps/mutate_state_a/__init__.py b/tests/migrations/migrations_test_apps/mutate_state_a/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/mutate_state_a/migrations/0001_initial.py b/tests/migrations/migrations_test_apps/mutate_state_a/migrations/0001_initial.py new file mode 100644 index 0000000000..5fac74abf3 --- /dev/null +++ b/tests/migrations/migrations_test_apps/mutate_state_a/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mutate_state_b', '0001_initial'), + ] + + operations = [ + migrations.SeparateDatabaseAndState([], [ + migrations.CreateModel( + name='A', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + ), + ]) + ] diff --git a/tests/migrations/migrations_test_apps/mutate_state_a/migrations/__init__.py b/tests/migrations/migrations_test_apps/mutate_state_a/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/mutate_state_b/__init__.py b/tests/migrations/migrations_test_apps/mutate_state_b/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0001_initial.py b/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0001_initial.py new file mode 100644 index 0000000000..73f436b20d --- /dev/null +++ b/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0001_initial.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.SeparateDatabaseAndState([], [ + migrations.CreateModel( + name='B', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + ), + ]) + ] diff --git a/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0002_add_field.py b/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0002_add_field.py new file mode 100644 index 0000000000..45aca30740 --- /dev/null +++ b/tests/migrations/migrations_test_apps/mutate_state_b/migrations/0002_add_field.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mutate_state_b', '0001_initial'), + ] + + operations = [ + migrations.SeparateDatabaseAndState([], [ + migrations.AddField( + model_name='B', + name='added', + field=models.TextField(), + ), + ]) + ] diff --git a/tests/migrations/migrations_test_apps/mutate_state_b/migrations/__init__.py b/tests/migrations/migrations_test_apps/mutate_state_b/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index b51e7be90e..7c33c1be5c 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -477,6 +477,34 @@ class ExecutorTests(MigrationTestBase): self.assertTableNotExists("lookuperror_b_b1") self.assertTableNotExists("lookuperror_c_c1") + @override_settings( + INSTALLED_APPS=[ + 'migrations.migrations_test_apps.mutate_state_a', + 'migrations.migrations_test_apps.mutate_state_b', + ] + ) + def test_unrelated_applied_migrations_mutate_state(self): + """ + #26647 - Unrelated applied migrations should be part of the final + state in both directions. + """ + executor = MigrationExecutor(connection) + executor.migrate([ + ('mutate_state_b', '0002_add_field'), + ]) + # Migrate forward. + executor.loader.build_graph() + state = executor.migrate([ + ('mutate_state_a', '0001_initial'), + ]) + self.assertIn('added', dict(state.models['mutate_state_b', 'b'].fields)) + executor.loader.build_graph() + # Migrate backward. + state = executor.migrate([ + ('mutate_state_a', None), + ]) + self.assertIn('added', dict(state.models['mutate_state_b', 'b'].fields)) + @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"}) def test_process_callback(self): """