diff options
| -rw-r--r-- | alembic/migration.py | 15 | ||||
| -rw-r--r-- | docs/build/changelog.rst | 13 | ||||
| -rw-r--r-- | tests/test_version_traversal.py | 149 |
3 files changed, 170 insertions, 7 deletions
diff --git a/alembic/migration.py b/alembic/migration.py index 9bd34ed..d94db2e 100644 --- a/alembic/migration.py +++ b/alembic/migration.py @@ -670,14 +670,15 @@ class RevisionStep(MigrationStep): if not downrevs: # is a base return True - elif len(downrevs) == 1: - if downrevs[0] in heads: - return False - else: - return True else: - # is a merge point - return False + # none of our downrevs are present, so... + # we have to insert our version. This is true whether + # or not there is only one downrev, or multiple (in the latter + # case, we're a merge point.) + if not heads.intersection(downrevs): + return True + else: + return False def should_merge_branches(self, heads): if not self.is_upgrade: diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index 8e3824c..c5d4f6f 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -7,6 +7,19 @@ Changelog :version: 0.7.6 .. change:: + :tags: feature, versioning + :tickets: 297 + + Fixed bug where the case of multiple mergepoints that all + have the identical set of ancestor revisions would fail to be + upgradable, producing an assertion failure. Merge points were + previously assumed to always require at least an UPDATE in + alembic_revision from one of the previous revs to the new one, + however in this case, if one of the mergepoints has already + been reached, the remaining mergepoints have no row to UPDATE therefore + they must do an INSERT of their target version. + + .. change:: :tags: feature, autogenerate :tickets: 296 diff --git a/tests/test_version_traversal.py b/tests/test_version_traversal.py index 410995e..198c7c6 100644 --- a/tests/test_version_traversal.py +++ b/tests/test_version_traversal.py @@ -434,6 +434,155 @@ class BranchFrom3WayMergepointTest(MigrationTest): ) +class TwinMergeTest(MigrationTest): + """Test #297, where we have two mergepoints from the same set of + originating branches. + + """ + @classmethod + def setup_class(cls): + """ + + 33e21c000cfe -> 178d4e761bbd (head), + 2bef33cb3a58, 3904558db1c6, 968330f320d -> 33e21c000cfe (mergepoint) + 46c99f866004 -> 18f46b42410d (head), + 2bef33cb3a58, 3904558db1c6, 968330f320d -> 46c99f866004 (mergepoint) + f0fa4315825 -> 3904558db1c6 (branchpoint), + + -------------------------- + + A -> B2 (branchpoint), + + B1, B2, B3 -> C1 (mergepoint) + B1, B2, B3 -> C2 (mergepoint) + + C1 -> D1 (head), + + C2 -> D2 (head), + + + """ + cls.env = env = staging_env() + + cls.a = env.generate_revision( + 'a', 'a' + ) + cls.b1 = env.generate_revision('b1', 'b1', + head=cls.a.revision) + cls.b2 = env.generate_revision('b2', 'b2', + splice=True, + head=cls.a.revision) + cls.b3 = env.generate_revision('b3', 'b3', + splice=True, + head=cls.a.revision) + + cls.c1 = env.generate_revision( + 'c1', 'c1', + head=(cls.b1.revision, cls.b2.revision, cls.b3.revision)) + + cls.c2 = env.generate_revision( + 'c2', 'c2', + splice=True, + head=(cls.b1.revision, cls.b2.revision, cls.b3.revision)) + + cls.d1 = env.generate_revision( + 'd1', 'd1', head=cls.c1.revision) + + cls.d2 = env.generate_revision( + 'd2', 'd2', head=cls.c2.revision) + + def test_upgrade(self): + head = HeadMaintainer(mock.Mock(), [self.a.revision]) + + steps = [ + (self.up_(self.b3), ('b3',)), + (self.up_(self.b1), ('b1', 'b3',)), + (self.up_(self.b2), ('b1', 'b2', 'b3',)), + (self.up_(self.c2), ('c2',)), + (self.up_(self.d2), ('d2',)), + (self.up_(self.c1), ('c1', 'd2')), + (self.up_(self.d1), ('d1', 'd2')), + ] + for step, assert_ in steps: + head.update_to_step(step) + eq_(head.heads, set(assert_)) + + +class NotQuiteTwinMergeTest(MigrationTest): + """Test a variant of #297. + + """ + @classmethod + def setup_class(cls): + """ + A -> B2 (branchpoint), + + B1, B2 -> C1 (mergepoint) + B2, B3 -> C2 (mergepoint) + + C1 -> D1 (head), + + C2 -> D2 (head), + + + """ + cls.env = env = staging_env() + + cls.a = env.generate_revision( + 'a', 'a' + ) + cls.b1 = env.generate_revision('b1', 'b1', + head=cls.a.revision) + cls.b2 = env.generate_revision('b2', 'b2', + splice=True, + head=cls.a.revision) + cls.b3 = env.generate_revision('b3', 'b3', + splice=True, + head=cls.a.revision) + + cls.c1 = env.generate_revision( + 'c1', 'c1', + head=(cls.b1.revision, cls.b2.revision)) + + cls.c2 = env.generate_revision( + 'c2', 'c2', + splice=True, + head=(cls.b2.revision, cls.b3.revision)) + + cls.d1 = env.generate_revision( + 'd1', 'd1', head=cls.c1.revision) + + cls.d2 = env.generate_revision( + 'd2', 'd2', head=cls.c2.revision) + + def test_upgrade(self): + head = HeadMaintainer(mock.Mock(), [self.a.revision]) + + """ + upgrade a -> b2, b2 + upgrade a -> b3, b3 + upgrade b2, b3 -> c2, c2 + upgrade c2 -> d2, d2 + upgrade a -> b1, b1 + upgrade b1, b2 -> c1, c1 + upgrade c1 -> d1, d1 + """ + + steps = [ + (self.up_(self.b2), ('b2',)), + (self.up_(self.b3), ('b2', 'b3',)), + (self.up_(self.c2), ('c2',)), + (self.up_(self.d2), ('d2',)), + + (self.up_(self.b1), ('b1', 'd2',)), + (self.up_(self.c1), ('c1', 'd2')), + (self.up_(self.d1), ('d1', 'd2')), + ] + for step, assert_ in steps: + head.update_to_step(step) + eq_(head.heads, set(assert_)) + + class DependsOnBranchTestOne(MigrationTest): @classmethod |
