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):
         """