Removed reduce() usage in makemigrations; refs #23796.

A lambda all_items_equal() replaced a reduce() that was broken for potential
3+-way merges. A reduce(operator.eq, ...) accumulates bools and can't
generically check equality of all items in a sequence:

>>> bool(reduce(operator.eq, [('migrations', '0001_initial')] * 3))
False

The code now counts the number of common ancestors to calculate slice offsets
for the branches. Each branch shares the same number of common ancestors.

The common_ancestor for loop statement had incomplete branch coverage.
This commit is contained in:
Brad Walker 2014-11-12 16:06:12 -07:00 committed by Tim Graham
parent cfa26f29bd
commit 54d3dcbc51
1 changed files with 11 additions and 17 deletions

View File

@ -1,6 +1,6 @@
from itertools import takewhile
import sys import sys
import os import os
import operator
from django.apps import apps from django.apps import apps
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@ -11,7 +11,7 @@ from django.db.migrations.questioner import MigrationQuestioner, InteractiveMigr
from django.db.migrations.state import ProjectState from django.db.migrations.state import ProjectState
from django.db.migrations.writer import MigrationWriter from django.db.migrations.writer import MigrationWriter
from django.utils.six import iteritems from django.utils.six import iteritems
from django.utils.six.moves import reduce from django.utils.six.moves import zip
class Command(BaseCommand): class Command(BaseCommand):
@ -187,24 +187,18 @@ class Command(BaseCommand):
migration = loader.get_migration(app_label, migration_name) migration = loader.get_migration(app_label, migration_name)
migration.ancestry = loader.graph.forwards_plan((app_label, migration_name)) migration.ancestry = loader.graph.forwards_plan((app_label, migration_name))
merge_migrations.append(migration) merge_migrations.append(migration)
common_ancestor = None all_items_equal = lambda seq: all(item == seq[0] for item in seq[1:])
for level in zip(*[m.ancestry for m in merge_migrations]): merge_migrations_generations = zip(*[m.ancestry for m in merge_migrations])
if reduce(operator.eq, level): common_ancestor_count = sum(1 for common_ancestor_generation
common_ancestor = level[0] in takewhile(all_items_equal, merge_migrations_generations))
else: if not common_ancestor_count:
break
if common_ancestor is None:
raise ValueError("Could not find common ancestor of %s" % migration_names) raise ValueError("Could not find common ancestor of %s" % migration_names)
# Now work out the operations along each divergent branch # Now work out the operations along each divergent branch
for migration in merge_migrations: for migration in merge_migrations:
migration.branch = migration.ancestry[ migration.branch = migration.ancestry[common_ancestor_count:]
(migration.ancestry.index(common_ancestor) + 1): migrations_ops = (loader.get_migration(node_app, node_name).operations
] for node_app, node_name in migration.branch)
migration.merged_operations = [] migration.merged_operations = sum(migrations_ops, [])
for node_app, node_name in migration.branch:
migration.merged_operations.extend(
loader.get_migration(node_app, node_name).operations
)
# In future, this could use some of the Optimizer code # In future, this could use some of the Optimizer code
# (can_optimize_through) to automatically see if they're # (can_optimize_through) to automatically see if they're
# mergeable. For now, we always just prompt the user. # mergeable. For now, we always just prompt the user.