Doing some bad things, it is possible to get to a state where a migration is recorded as executed without all of its dependencies being executed. For example, if we have migrations for the same app developed on separate branches, and the person who merges them decides to resolve the conflict not by adding a "merge migration" as suggested by Django, but by editing the dependencies on migrations which were already run.
One should not do that, of course, but if one does do that, Django should refuse to do anything with these migrations until the situation is resolved. Currently, it just happily ignores the unfulfilled dependencies, and will migrate forwards and even cheerfully create new migrations. We don't even have an InconsistentMigrationHistory
exception.
A failing test draft (add at the end of tests/migrations/test_executor.py
):
def test_reject_holes_in_history(self):
"""
If the history is inconsistent with the sources, cry foul
a: 1 <--- 2 <--- 3
If a2 is applied already and a1 is not, and we're asked to migrate to
a3, then something is very wrong and we should not proceed
"""
a1_impl = FakeMigration('a1')
a1 = ('a', '1')
a2_impl = FakeMigration('a2')
a2 = ('a', '2')
a3_impl = FakeMigration('a3')
a3 = ('a', '3')
graph = MigrationGraph()
graph.add_node(a1, a1_impl)
graph.add_node(a2, a2_impl)
graph.add_node(a3, a3_impl)
graph.add_dependency(None, a2, a1)
graph.add_dependency(None, a3, a2)
executor = MigrationExecutor(None)
executor.loader = FakeLoader(graph, {a2})
with self.assertRaises(Exception):
plan = executor.migration_plan({a3})
Yeah, as discussed at DutH Django should bail out loudly.