From 0271a11ba5df58b449a3f968ec57fb1a090c5726 Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Sun, 9 Aug 2015 23:53:09 +1000 Subject: [PATCH] Fixed #24755 -- Hid operations from dependency apps when merging migrations Thanks Carl Meyer for the report and Tim Graham for the review. --- .../management/commands/makemigrations.py | 6 +++- .../__init__.py | 0 .../migrations/0001_initial.py | 27 +++++++++++++++ .../migrations/0002_conflicting_second.py | 20 +++++++++++ .../migrations/0002_second.py | 24 ++++++++++++++ .../migrations/__init__.py | 0 tests/migrations/test_commands.py | 33 +++++++++++++++++++ 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/__init__.py create mode 100644 tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0001_initial.py create mode 100644 tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_conflicting_second.py create mode 100644 tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_second.py create mode 100644 tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/__init__.py diff --git a/django/core/management/commands/makemigrations.py b/django/core/management/commands/makemigrations.py index 4cd36f3c8a3..f218c417c0c 100644 --- a/django/core/management/commands/makemigrations.py +++ b/django/core/management/commands/makemigrations.py @@ -194,13 +194,17 @@ class Command(BaseCommand): questioner = InteractiveMigrationQuestioner() else: questioner = MigrationQuestioner(defaults={'ask_merge': True}) + for app_label, migration_names in conflicts.items(): # Grab out the migrations in question, and work out their # common ancestor. merge_migrations = [] for migration_name in migration_names: migration = loader.get_migration(app_label, migration_name) - migration.ancestry = loader.graph.forwards_plan((app_label, migration_name)) + migration.ancestry = [ + mig for mig in loader.graph.forwards_plan((app_label, migration_name)) + if mig[0] == migration.app_label + ] merge_migrations.append(migration) all_items_equal = lambda seq: all(item == seq[0] for item in seq[1:]) merge_migrations_generations = zip(*[m.ancestry for m in merge_migrations]) diff --git a/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/__init__.py b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0001_initial.py b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0001_initial.py new file mode 100644 index 00000000000..146d6cee3e7 --- /dev/null +++ b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + operations = [ + migrations.CreateModel( + "Author", + [ + ("id", models.AutoField(primary_key=True)), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(null=True)), + ("age", models.IntegerField(default=0)), + ("silly_field", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + "Tribble", + [ + ("id", models.AutoField(primary_key=True)), + ("fluffy", models.BooleanField(default=True)), + ], + ) + ] diff --git a/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_conflicting_second.py b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_conflicting_second.py new file mode 100644 index 00000000000..cbe5bfb13e6 --- /dev/null +++ b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_conflicting_second.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conflicting_app_with_dependencies", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + "Something", + [ + ("id", models.AutoField(primary_key=True)), + ], + ) + ] diff --git a/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_second.py b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_second.py new file mode 100644 index 00000000000..16d42f8736d --- /dev/null +++ b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/0002_second.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("conflicting_app_with_dependencies", "0001_initial"), + ("migrated_app", "0001_initial"), + ] + + operations = [ + migrations.DeleteModel("Tribble"), + migrations.RemoveField("Author", "silly_field"), + migrations.AddField("Author", "rating", models.IntegerField(default=0)), + migrations.CreateModel( + "Book", + [ + ("id", models.AutoField(primary_key=True)), + ], + ) + ] diff --git a/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/__init__.py b/tests/migrations/migrations_test_apps/conflicting_app_with_dependencies/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 8ca47aec2d3..67fc57faa18 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -920,6 +920,39 @@ class MakeMigrationsTests(MigrationTestBase): except CommandError: self.fail("Makemigrations fails resolving conflicts in an unspecified app") + @override_settings( + INSTALLED_APPS=[ + "migrations.migrations_test_apps.migrated_app", + "migrations.migrations_test_apps.conflicting_app_with_dependencies"]) + def test_makemigrations_merge_dont_output_dependency_operations(self): + """ + Makes sure that makemigrations --merge does not output any operations + from apps that don't belong to a given app. + """ + # Monkeypatch interactive questioner to auto accept + with mock.patch('django.db.migrations.questioner.input', mock.Mock(return_value='N')): + out = six.StringIO() + with mock.patch('django.core.management.color.supports_color', lambda *args: False): + call_command( + "makemigrations", "conflicting_app_with_dependencies", + merge=True, interactive=True, stdout=out + ) + val = out.getvalue().lower() + self.assertIn('merging conflicting_app_with_dependencies\n', val) + self.assertIn( + ' branch 0002_conflicting_second\n' + ' - create model something\n', + val + ) + self.assertIn( + ' branch 0002_second\n' + ' - delete model tribble\n' + ' - remove field silly_field from author\n' + ' - add field rating to author\n' + ' - create model book\n', + val + ) + def test_makemigrations_with_custom_name(self): """ Makes sure that makemigrations generate a custom migration.