]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Only allow partial revision match for > 3 characters
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 17 Sep 2019 22:25:41 +0000 (18:25 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 17 Sep 2019 23:02:21 +0000 (19:02 -0400)
Made the command interface revision lookup behavior more strict in that an
Alembic revision number is only resolved based on a partial match rules if
it has at least four characters, to prevent simple typographical issues
from inadvertently  running migrations.

Change-Id: I9c4c87bb3fdb78d2dd264f37c99422df309c4e5c
Fixes: #534
alembic/script/revision.py
docs/build/unreleased/534.rst [new file with mode: 0644]
tests/test_revision.py

index 43c757efad2d166e0897c8fd362dbaaa8ef47a25..22481a0872e1652b9e1f85dda627e0a6c1bf9dc1 100644 (file)
@@ -377,13 +377,23 @@ class RevisionMap(object):
             revs = [
                 x
                 for x in self._revision_map
-                if x and x.startswith(resolved_id)
+                if x and len(x) > 3 and x.startswith(resolved_id)
             ]
+
             if branch_rev:
                 revs = self.filter_for_lineage(revs, check_branch)
             if not revs:
                 raise ResolutionError(
-                    "No such revision or branch '%s'" % resolved_id,
+                    "No such revision or branch '%s'%s"
+                    % (
+                        resolved_id,
+                        (
+                            "; please ensure at least four characters are "
+                            "present for partial revision identifier matches"
+                            if len(resolved_id) < 4
+                            else ""
+                        ),
+                    ),
                     resolved_id,
                 )
             elif len(revs) > 1:
@@ -477,7 +487,7 @@ class RevisionMap(object):
                 and id_
                 and not isinstance(id_[0], compat.string_types)
             )
-            or not isinstance(id_, compat.string_types + (tuple, ))
+            or not isinstance(id_, compat.string_types + (tuple,))
         ):
             raise RevisionError(
                 "revision identifier %r is not a string; ensure database "
diff --git a/docs/build/unreleased/534.rst b/docs/build/unreleased/534.rst
new file mode 100644 (file)
index 0000000..5bf9361
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: usecase, commands
+    :tickets: 534
+
+    Made the command interface revision lookup behavior more strict in that an
+    Alembic revision number is only resolved based on a partial match rules if
+    it has at least four characters, to prevent simple typographical issues
+    from inadvertently  running migrations.
index 41d8b42ad8f2f2a014f89515859c5b41e37631eb..bf433f51d50ba3eaf173cb4af42c9fb53b98c57e 100644 (file)
@@ -23,24 +23,27 @@ class APITest(TestBase):
             RevisionError,
             "revision identifier b'12345' is not a string; "
             "ensure database driver settings are correct",
-            map_.get_revisions, b'12345'
+            map_.get_revisions,
+            b"12345",
         )
 
         assert_raises_message(
             RevisionError,
             "revision identifier b'12345' is not a string; "
             "ensure database driver settings are correct",
-            map_.get_revision, b'12345'
+            map_.get_revision,
+            b"12345",
         )
 
         assert_raises_message(
             RevisionError,
             r"revision identifier \(b'12345',\) is not a string; "
             "ensure database driver settings are correct",
-            map_.get_revision, (b'12345', )
+            map_.get_revision,
+            (b"12345",),
         )
 
-        map_.get_revision(("a", ))
+        map_.get_revision(("a",))
         map_.get_revision("a")
 
     def test_add_revision_one_head(self):
@@ -336,6 +339,16 @@ class LabeledBranchTest(DownIterateTest):
         eq_(self.map.get_revision("ebranch@some").revision, "someothername")
         eq_(self.map.get_revision("abranch@some").revision, "somelongername")
 
+    def test_partial_id_resolve_too_short(self):
+        assert_raises_message(
+            RevisionError,
+            "No such revision or branch 'sos'; please ensure at least "
+            "four characters are present for partial revision identifier "
+            "matches",
+            self.map.get_revision,
+            "ebranch@sos",
+        )
+
     def test_branch_at_heads(self):
         eq_(self.map.get_revision("abranch@heads").revision, "c")
 
@@ -368,12 +381,24 @@ class LabeledBranchTest(DownIterateTest):
             "abranch@d",
         )
 
+    def test_actually_short_rev_name(self):
+        eq_(self.map.get_revision("e").revision, "e")
+
     def test_no_revision_exists(self):
         assert_raises_message(
             RevisionError,
-            "No such revision or branch 'q'",
+            "No such revision or branch 'qprstuv'$",
+            self.map.get_revision,
+            "abranch@qprstuv",
+        )
+
+        assert_raises_message(
+            RevisionError,
+            "No such revision or branch 'qpr'; please ensure at least "
+            "four characters are present for partial revision identifier "
+            "matches$",
             self.map.get_revision,
-            "abranch@q",
+            "abranch@qpr",
         )
 
     def test_not_actually_a_branch(self):