summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alembic/migration.py15
-rw-r--r--docs/build/changelog.rst13
-rw-r--r--tests/test_version_traversal.py149
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