From 36d36818a30025034cad6f1ee59b2a960a6582ec Mon Sep 17 00:00:00 2001 From: Simon Charette <charette.s@gmail.com> Date: Sun, 22 May 2016 20:35:10 -0400 Subject: [PATCH] Fixed #26647 -- Included the state of all applied migrations when migrating forward. Thanks Jasper Maes for the detailed report. --- django/db/migrations/executor.py | 5 ++-- .../mutate_state_a/__init__.py | 0 .../mutate_state_a/migrations/0001_initial.py | 22 +++++++++++++++ .../mutate_state_a/migrations/__init__.py | 0 .../mutate_state_b/__init__.py | 0 .../mutate_state_b/migrations/0001_initial.py | 21 ++++++++++++++ .../migrations/0002_add_field.py | 21 ++++++++++++++ .../mutate_state_b/migrations/__init__.py | 0 tests/migrations/test_executor.py | 28 +++++++++++++++++++ 9 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/migrations/migrations_test_apps/mutate_state_a/__init__.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_a/migrations/0001_initial.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_a/migrations/__init__.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_b/__init__.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_b/migrations/0001_initial.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_b/migrations/0002_add_field.py create mode 100644 tests/migrations/migrations_test_apps/mutate_state_b/migrations/__init__.py 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): """