limit_to_lower_branch = \
isinstance(lower, compat.string_types) and lower.endswith('@base')
- uppers = self.get_revisions(upper)
+ uppers = util.dedupe_tuple(self.get_revisions(upper))
+
if not uppers and not requested_lowers:
raise StopIteration()
from .langhelpers import ( # noqa
- asbool, rev_id, to_tuple, to_list, memoized_property,
+ asbool, rev_id, to_tuple, to_list, memoized_property, dedupe_tuple,
immutabledict, _with_legacy_names, Dispatcher, ModuleClsProxy)
from .messaging import ( # noqa
write_outstream, status, err, obfuscate_url_pw, warn, msg, format_as_comma)
raise ValueError("Don't know how to turn %r into a tuple" % x)
+def unique_list(seq, hashfunc=None):
+ seen = set()
+ seen_add = seen.add
+ if not hashfunc:
+ return [x for x in seq
+ if x not in seen
+ and not seen_add(x)]
+ else:
+ return [x for x in seq
+ if hashfunc(x) not in seen
+ and not seen_add(hashfunc(x))]
+
+
+def dedupe_tuple(tup):
+ return tuple(unique_list(tup))
+
+
+
class memoized_property(object):
"""A read-only @property that is only evaluated once."""
:ref:`alembic.autogenerate.toplevel`
+ .. change::
+ :tags: bug, versioning
+ :tickets: 314
+
+ Fixed bug where in the erroneous case that alembic_version contains
+ duplicate revisions, some commands would fail to process the
+ version history correctly and end up with a KeyError. The fix
+ allows the versioning logic to proceed, however a clear error is
+ emitted later when attempting to update the alembic_version table.
.. changelog::
:version: 0.7.7
command.revision, self.cfg, autogenerate=True
)
+ def test_err_correctly_raised_on_dupe_rows(self):
+ self._env_fixture()
+ command.revision(self.cfg)
+ r2 = command.revision(self.cfg)
+ db = _sqlite_file_db()
+ command.upgrade(self.cfg, "head")
+ db.execute("insert into alembic_version values ('%s')" % r2.revision)
+ assert_raises_message(
+ util.CommandError,
+ "Online migration expected to match one row when "
+ "updating .* in 'alembic_version'; 2 found",
+ command.downgrade, self.cfg, "-1"
+ )
+
def test_create_rev_plain_need_to_select_head(self):
self._env_fixture()
command.revision(self.cfg)
)
eq_(map_.get_revision('base'), None)
+ def test_iterate_tolerates_dupe_targets(self):
+ map_ = RevisionMap(
+ lambda: [
+ Revision('a', ()),
+ Revision('b', ('a',)),
+ Revision('c', ('b',)),
+ ]
+ )
+
+ eq_(
+ [
+ r.revision for r in
+ map_._iterate_revisions(('c', 'c'), 'a')
+ ],
+ ['c', 'b', 'a']
+ )
+
class DownIterateTest(TestBase):
def _assert_iteration(