New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transition from multiple revs to one as a mergepoint, however a different merge from the same revs has already proceeded #297

sqlalchemy-bot opened this Issue May 3, 2015 · 2 comments


None yet
1 participant

sqlalchemy-bot commented May 3, 2015

Migrated issue, originally created by Michael Bayer (@zzzeek)

class TwinMergeTest(MigrationTest):
    """Test #XYZ, where we have two mergepoints from the same set of
    originating branches.

    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',
        cls.b2 = env.generate_revision('b2', 'b2',
        cls.b3 = env.generate_revision('b3', 'b3',

        cls.c1 = env.generate_revision(
            'c1', 'c1',
            head=(cls.b1.revision, cls.b2.revision, cls.b3.revision))

        cls.c2 = env.generate_revision(
            'c2', 'c2',
            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:
            eq_(head.heads, set(assert_))


Traceback (most recent call last):
  File "/Users/classic/dev/alembic/tests/", line 507, in test_upgrade
  File "/Users/classic/dev/alembic/alembic/", line 495, in update_to_step
    from_, to_ = step.update_version_num(self.heads)
  File "/Users/classic/dev/alembic/alembic/", line 709, in update_version_num
    "Can't do an UPDATE because downrevision is ambiguous"
AssertionError: Can't do an UPDATE because downrevision is ambiguous

Assume a tree like this:


        A -> B1,
        A -> B2,
        A -> B3,

        B1, B2, B3 -> C1
        B1, B2, B3 -> C2

        C1 -> D1 (head),

        C2 -> D2 (head),

The steps to upgrade from A to D1/D2 come out normally:


upgrade a -> b3, b3
upgrade a -> b1, b1
upgrade a -> b2, b2
upgrade b1, b2, b3 -> c2, c2
upgrade c2 -> d2, d2
upgrade b1, b2, b3 -> c1, c1
upgrade c1 -> d1, d1

The transitions come out as:


DEBUG:alembic.migration:update a to b3
DEBUG:alembic.migration:new branch insert b1
DEBUG:alembic.migration:new branch insert b2
DEBUG:alembic.migration:merge, delete ['b1', 'b2'], update b3 to c2
DEBUG:alembic.migration:update c2 to d2

It then crashes on b1, b2, b3 -> c1, because it thinks it's supposed to to an UPDATE, but there is no row to UPDATE because all three of b1, b2, b3 are gone. What it really should do here is an INSERT of c1.

The internal mechanics ask the question, "if we have more than one anscestor, we're a MERGE point, therefore we definitely aren't INSERTing an identifier". They also assert that, "if we have only one anscestor, we're not a MERGE point, so if our ansestor is in the current heads, we do an UPDATE and if it isn't, we do an INSERT". The logic here can be simplified, such that, "if none of our ancestors are in the current heads, we do an INSERT":

diff --git a/alembic/ b/alembic/
index 9bd34ed..d94db2e 100644
--- a/alembic/
+++ b/alembic/
@@ -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
-            # 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:


This comment has been minimized.

sqlalchemy-bot commented May 4, 2015

Michael Bayer (@zzzeek) wrote:

  • 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.
    fixes #297



This comment has been minimized.

sqlalchemy-bot commented May 4, 2015

Changes by Michael Bayer (@zzzeek):

  • changed status to closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment