]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Report on other branch dependencies in "current"
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 11 Jul 2016 19:27:45 +0000 (15:27 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 12 Jul 2016 14:08:28 +0000 (10:08 -0400)
Fixed bug where the "alembic current" command wouldn't show a revision
as a current head if it were also a dependency of a version in a
different branch that's also applied.   Extra logic is added to
extract "implied" versions on different branches from the top-level
versions listed in the alembic_version table.

Change-Id: I9f485fbc67555d13f737ecffdd25e4c0d8e33f1c
Fixes: #378
alembic/command.py
alembic/script/base.py
alembic/script/revision.py
docs/build/changelog.rst
tests/test_command.py

index 6b2bc336a02c0dc177f550648637731da15bae97..0034c181f53f80702eab88bc35e7dd9ab581ece4 100644 (file)
@@ -316,8 +316,9 @@ def current(config, verbose=False, head_only=False):
                 "Current revision(s) for %s:",
                 util.obfuscate_url_pw(context.connection.engine.url)
             )
-        for rev in script.get_revisions(rev):
+        for rev in script.get_all_current(rev):
             config.print_stdout(rev.cmd_format(verbose))
+
         return []
 
     with EnvironmentContext(
index 9fb9e25158d82e8ade5c037220b61441be7a3d97..98c5311bd35b214c436055a7f61f225a70f4ba39 100644 (file)
@@ -191,6 +191,16 @@ class ScriptDirectory(object):
         with self._catch_revision_errors():
             return self.revision_map.get_revisions(id_)
 
+    def get_all_current(self, id_):
+        with self._catch_revision_errors():
+            top_revs = set(self.revision_map.get_revisions(id_))
+            top_revs.update(
+                self.revision_map._get_ancestor_nodes(
+                    list(top_revs), include_dependencies=True)
+            )
+            top_revs = self.revision_map._filter_into_branch_heads(top_revs)
+            return top_revs
+
     def get_revision(self, id_):
         """Return the :class:`.Script` instance with the given rev id.
 
@@ -665,11 +675,11 @@ class Script(revision.Revision):
                 " (head)" if self._is_real_head else "",
                 " (effective head)" if self.is_head and
                     not self._is_real_head else ""
-                )
+            )
         if tree_indicators:
             text += "%s%s" % (
                 " (branchpoint)" if self.is_branch_point else "",
-                " (mergepoint)" if self.is_merge_point else "",
+                " (mergepoint)" if self.is_merge_point else ""
             )
         if include_doc:
             text += ", %s" % self.doc
index 3b4fac9c06cf447605317520c0c51efa4986f67a..6feba77899e7969b65aade93029e15e88daa89f9 100644 (file)
@@ -375,6 +375,17 @@ class RevisionMap(object):
                     (revision.revision, check_branch), resolved_id)
         return revision
 
+    def _filter_into_branch_heads(self, targets):
+        targets = set(targets)
+
+        for rev in list(targets):
+            if targets.intersection(
+                self._get_descendant_nodes(
+                    [rev], include_dependencies=False)).\
+                    difference([rev]):
+                targets.discard(rev)
+        return targets
+
     def filter_for_lineage(
             self, targets, check_against, include_dependencies=False):
         id_, branch_label = self._resolve_revision_number(check_against)
index 2ccc8b52512ac611256861cf483882f8a6cbbaa4..f96232cf383332ce1ef05fe07b83fd84de5b0849 100644 (file)
@@ -6,6 +6,16 @@ Changelog
 .. changelog::
     :version: 0.8.7
 
+    .. change::
+      :tags: bug, versioning
+      :tickets: 378
+
+      Fixed bug where the "alembic current" command wouldn't show a revision
+      as a current head if it were also a dependency of a version in a
+      different branch that's also applied.   Extra logic is added to
+      extract "implied" versions of different branches from the top-level
+      versions listed in the alembic_version table.
+
     .. change::
       :tags: bug, versioning
 
index e13dee0f6de37f483d4a70aeca4877601b95dc39..d20412d54fc341393be24b484dc1d8bdbf295712 100644 (file)
@@ -7,9 +7,21 @@ from alembic.testing.env import staging_env, _sqlite_testing_config, \
     _sqlite_file_db, write_script, env_file_fixture
 from alembic.testing import eq_, assert_raises_message, mock
 from alembic import util
+from contextlib import contextmanager
+import re
 
 
-class HistoryTest(TestBase):
+class _BufMixin(object):
+    def _buf_fixture(self):
+        # try to simulate how sys.stdout looks - we send it u''
+        # but then it's trying to encode to something.
+        buf = BytesIO()
+        wrapper = TextIOWrapper(buf, encoding='ascii', line_buffering=True)
+        wrapper.getvalue = buf.getvalue
+        return wrapper
+
+
+class HistoryTest(_BufMixin, TestBase):
 
     @classmethod
     def setup_class(cls):
@@ -34,14 +46,6 @@ class HistoryTest(TestBase):
             ]).encode("ascii", "replace").decode("ascii").strip()
         )
 
-    def _buf_fixture(self):
-        # try to simulate how sys.stdout looks - we send it u''
-        # but then it's trying to encode to something.
-        buf = BytesIO()
-        wrapper = TextIOWrapper(buf, encoding='ascii', line_buffering=True)
-        wrapper.getvalue = buf.getvalue
-        return wrapper
-
     def test_history_full(self):
         self.cfg.stdout = buf = self._buf_fixture()
         command.history(self.cfg, verbose=True)
@@ -90,6 +94,67 @@ class HistoryTest(TestBase):
         self._eq_cmd_output(buf, [self.c, self.b, self.a])
 
 
+class CurrentTest(_BufMixin, TestBase):
+
+    @classmethod
+    def setup_class(cls):
+        cls.env = env = staging_env()
+        cls.cfg = _sqlite_testing_config()
+        cls.a1 = env.generate_revision("a1", "a1")
+        cls.a2 = env.generate_revision("a2", "a2")
+        cls.a3 = env.generate_revision("a3", "a3")
+        cls.b1 = env.generate_revision("b1", "b1", head="base")
+        cls.b2 = env.generate_revision("b2", "b2", head="b1", depends_on="a2")
+        cls.b3 = env.generate_revision("b3", "b3", head="b2")
+
+    @classmethod
+    def teardown_class(cls):
+        clear_staging_env()
+
+    @contextmanager
+    def _assert_lines(self, revs):
+        self.cfg.stdout = buf = self._buf_fixture()
+
+        yield
+
+        lines = set([
+            re.match(r'(^.\w)', elem).group(1)
+            for elem in re.split(
+                "\n",
+                buf.getvalue().decode('ascii', 'replace').strip()) if elem])
+
+        eq_(lines, set(revs))
+
+    def test_no_current(self):
+        command.stamp(self.cfg, ())
+        with self._assert_lines([]):
+            command.current(self.cfg)
+
+    def test_plain_current(self):
+        command.stamp(self.cfg, ())
+        command.stamp(self.cfg, self.a3.revision)
+        with self._assert_lines(['a3']):
+            command.current(self.cfg)
+
+    def test_two_heads(self):
+        command.stamp(self.cfg, ())
+        command.stamp(self.cfg, (self.a1.revision, self.b1.revision))
+        with self._assert_lines(['a1', 'b1']):
+            command.current(self.cfg)
+
+    def test_heads_one_is_dependent(self):
+        command.stamp(self.cfg, ())
+        command.stamp(self.cfg, (self.b2.revision, ))
+        with self._assert_lines(['a2', 'b2']):
+            command.current(self.cfg)
+
+    def test_heads_upg(self):
+        command.stamp(self.cfg, (self.b2.revision, ))
+        command.upgrade(self.cfg, (self.b3.revision))
+        with self._assert_lines(['a2', 'b3']):
+            command.current(self.cfg)
+
+
 class RevisionTest(TestBase):
     def setUp(self):
         self.env = staging_env()