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:
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 "
--- /dev/null
+.. 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.
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):
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")
"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):