Commit 54d3dcbc authored by Brad Walker's avatar Brad Walker Committed by Tim Graham
Browse files

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.
parent cfa26f29
Loading
Loading
Loading
Loading
+11 −17
Original line number Diff line number Diff line
from itertools import takewhile
import sys
import os
import operator

from django.apps import apps
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.writer import MigrationWriter
from django.utils.six import iteritems
from django.utils.six.moves import reduce
from django.utils.six.moves import zip


class Command(BaseCommand):
@@ -187,24 +187,18 @@ class Command(BaseCommand):
                migration = loader.get_migration(app_label, migration_name)
                migration.ancestry = loader.graph.forwards_plan((app_label, migration_name))
                merge_migrations.append(migration)
            common_ancestor = None
            for level in zip(*[m.ancestry for m in merge_migrations]):
                if reduce(operator.eq, level):
                    common_ancestor = level[0]
                else:
                    break
            if common_ancestor is None:
            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])
            common_ancestor_count = sum(1 for common_ancestor_generation
                                        in takewhile(all_items_equal, merge_migrations_generations))
            if not common_ancestor_count:
                raise ValueError("Could not find common ancestor of %s" % migration_names)
            # Now work out the operations along each divergent branch
            for migration in merge_migrations:
                migration.branch = migration.ancestry[
                    (migration.ancestry.index(common_ancestor) + 1):
                ]
                migration.merged_operations = []
                for node_app, node_name in migration.branch:
                    migration.merged_operations.extend(
                        loader.get_migration(node_app, node_name).operations
                    )
                migration.branch = migration.ancestry[common_ancestor_count:]
                migrations_ops = (loader.get_migration(node_app, node_name).operations
                                  for node_app, node_name in migration.branch)
                migration.merged_operations = sum(migrations_ops, [])
            # In future, this could use some of the Optimizer code
            # (can_optimize_through) to automatically see if they're
            # mergeable. For now, we always just prompt the user.