From: Mike Bayer Date: Thu, 20 May 2021 18:17:53 +0000 (-0400) Subject: ensure heads remain unique in topological X-Git-Tag: rel_1_6_3~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d5a9a1b0d32fff5726010afffa48cc0fb738238;p=thirdparty%2Fsqlalchemy%2Falembic.git ensure heads remain unique in topological Fixed regression where a revision file that contained its own down revision as a dependency would cause an endless loop in the traversal logic. Change-Id: Ie8bd3b4d9f81e4bda131ffdbabe7a90c4a715054 Fixes: #843 --- diff --git a/alembic/script/revision.py b/alembic/script/revision.py index ef03d2e5..f2055663 100644 --- a/alembic/script/revision.py +++ b/alembic/script/revision.py @@ -833,7 +833,6 @@ class RevisionMap(object): key=inserted_order.index, ) ) - ancestors_by_idx = [get_ancestors(rev_id) for rev_id in current_heads] output = [] @@ -864,11 +863,15 @@ class RevisionMap(object): candidate_rev = id_to_rev[candidate] - # immediate ancestor nodes + # immediate ancestor nodes, use a set to uniquify + _u = set() heads_to_add = [ r for r in candidate_rev._normalized_down_revisions - if r in todo and r not in current_heads + if r in todo + and r not in current_heads + and r not in _u + and (_u.add(r) or True) ] if not heads_to_add: diff --git a/docs/build/unreleased/843.rst b/docs/build/unreleased/843.rst new file mode 100644 index 00000000..02af2b5a --- /dev/null +++ b/docs/build/unreleased/843.rst @@ -0,0 +1,6 @@ +.. change:: + :tags: bug, regression, versioning + :tickets: 843 + + Fixed regression where a revision file that contained its own down revision + as a dependency would cause an endless loop in the traversal logic. diff --git a/tests/test_revision.py b/tests/test_revision.py index 31590e3a..ef1202a0 100644 --- a/tests/test_revision.py +++ b/tests/test_revision.py @@ -1347,6 +1347,16 @@ class GraphWithLoopTest(DownIterateTest, InvalidRevisionMapTest): ) self._assert_raises_revision_map_loop(map_, "a") + def test_revision_dupe_head(self): + r1 = Revision("user_foo", None) + r2 = Revision("user", "user_foo", dependencies="user_foo") + + self.map = RevisionMap(lambda: [r1, r2]) + + self._assert_iteration("heads", None, ["user", "user_foo"]) + + eq_(self.map._topological_sort([r1, r2], [r2]), ["user", "user_foo"]) + def test_revision_map_no_loop_w_overlapping_substrings(self): r1 = Revision("user_foo", None) r2 = Revision("user", "user_foo") diff --git a/tests/test_version_traversal.py b/tests/test_version_traversal.py index 3895607f..d3709d2a 100644 --- a/tests/test_version_traversal.py +++ b/tests/test_version_traversal.py @@ -1125,6 +1125,29 @@ class DependsOnBranchTestThree(MigrationTest): ) +class DependsOnOwnDownrevTest(MigrationTest): + @classmethod + def setup_class(cls): + """ + test #843 + """ + cls.env = env = staging_env() + cls.a1 = env.generate_revision("a1", "->a1", head="base") + cls.a2 = env.generate_revision("a2", "->a2", depends_on="a1") + + @classmethod + def teardown_class(cls): + clear_staging_env() + + def test_traverse(self): + self._assert_upgrade( + self.a2.revision, + None, + [self.up_(self.a1), self.up_(self.a2)], + set(["a2"]), + ) + + class DependsOnBranchTestFour(MigrationTest): @classmethod def setup_class(cls):