- Relative revision identifiers as used with ``alembic upgrade``,
``alembic downgrade`` and ``alembic history`` can be combined with specific revisions as well, e.g. ``alembic upgrade ae10+3``, to produce a migration target relative to the given exact version.
This commit is contained in:
parent
6b0b54b35b
commit
2639f8f5f8
|
@ -34,6 +34,7 @@ if py3k:
|
|||
def ue(s):
|
||||
return s
|
||||
|
||||
range = range
|
||||
else:
|
||||
import __builtin__ as compat_builtins
|
||||
string_types = basestring,
|
||||
|
@ -47,6 +48,8 @@ else:
|
|||
def ue(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
|
||||
range = xrange
|
||||
|
||||
if py3k:
|
||||
from configparser import ConfigParser as SafeConfigParser
|
||||
import configparser
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import re
|
||||
import collections
|
||||
import itertools
|
||||
|
||||
from . import util
|
||||
from sqlalchemy import util as sqlautil
|
||||
from . import compat
|
||||
|
||||
_relative_destination = re.compile(r'(?:(.+?)@)?((?:\+|-)\d+)')
|
||||
_relative_destination = re.compile(r'(?:(.+?)@)?(\w+)?((?:\+|-)\d+)')
|
||||
|
||||
|
||||
class RevisionError(Exception):
|
||||
|
@ -402,6 +403,83 @@ class RevisionMap(object):
|
|||
else:
|
||||
return util.to_tuple(id_, default=None), branch_label
|
||||
|
||||
def _relative_iterate(
|
||||
self, destination, source, is_upwards,
|
||||
implicit_base, inclusive, assert_relative_length):
|
||||
if isinstance(destination, compat.string_types):
|
||||
match = _relative_destination.match(destination)
|
||||
if not match:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
relative = int(match.group(3))
|
||||
symbol = match.group(2)
|
||||
branch_label = match.group(1)
|
||||
|
||||
reldelta = 1 if inclusive and not symbol else 0
|
||||
|
||||
if is_upwards:
|
||||
if branch_label:
|
||||
from_ = "%s@head" % branch_label
|
||||
elif symbol:
|
||||
if symbol.startswith("head"):
|
||||
from_ = symbol
|
||||
else:
|
||||
from_ = "%s@head" % symbol
|
||||
else:
|
||||
from_ = "head"
|
||||
to_ = source
|
||||
else:
|
||||
if branch_label:
|
||||
to_ = "%s@base" % branch_label
|
||||
elif symbol:
|
||||
to_ = "%s@base" % symbol
|
||||
else:
|
||||
to_ = "base"
|
||||
from_ = source
|
||||
|
||||
revs = list(
|
||||
self._iterate_revisions(
|
||||
from_, to_,
|
||||
inclusive=inclusive, implicit_base=implicit_base))
|
||||
|
||||
if symbol:
|
||||
if branch_label:
|
||||
symbol_rev = self.get_revision(
|
||||
"%s@%s" % (branch_label, symbol))
|
||||
else:
|
||||
symbol_rev = self.get_revision(symbol)
|
||||
if symbol.startswith("head"):
|
||||
index = 0
|
||||
elif symbol == "base":
|
||||
index = len(revs) - 1
|
||||
else:
|
||||
range_ = compat.range(len(revs) - 1, 0, -1)
|
||||
for index in range_:
|
||||
if symbol_rev.revision == revs[index].revision:
|
||||
break
|
||||
else:
|
||||
index = 0
|
||||
else:
|
||||
index = 0
|
||||
if is_upwards:
|
||||
revs = revs[index - relative - reldelta:]
|
||||
if not index and assert_relative_length and \
|
||||
len(revs) < abs(relative - reldelta):
|
||||
raise RevisionError(
|
||||
"Relative revision %s didn't "
|
||||
"produce %d migrations" % (destination, abs(relative)))
|
||||
else:
|
||||
revs = revs[0:index - relative + reldelta]
|
||||
if not index and assert_relative_length and \
|
||||
len(revs) != abs(relative) + reldelta:
|
||||
raise RevisionError(
|
||||
"Relative revision %s didn't "
|
||||
"produce %d migrations" % (destination, abs(relative)))
|
||||
|
||||
return iter(revs)
|
||||
|
||||
def iterate_revisions(
|
||||
self, upper, lower, implicit_base=False, inclusive=False,
|
||||
assert_relative_length=True):
|
||||
|
@ -417,54 +495,22 @@ class RevisionMap(object):
|
|||
|
||||
"""
|
||||
|
||||
if isinstance(upper, compat.string_types) and \
|
||||
_relative_destination.match(upper):
|
||||
relative_upper = self._relative_iterate(
|
||||
upper, lower, True, implicit_base,
|
||||
inclusive, assert_relative_length
|
||||
)
|
||||
if relative_upper:
|
||||
return relative_upper
|
||||
|
||||
reldelta = 1 if inclusive else 0
|
||||
match = _relative_destination.match(upper)
|
||||
relative = int(match.group(2))
|
||||
branch_label = match.group(1)
|
||||
if branch_label:
|
||||
from_ = "%s@head" % branch_label
|
||||
else:
|
||||
from_ = "head"
|
||||
revs = list(
|
||||
self._iterate_revisions(
|
||||
from_, lower,
|
||||
inclusive=inclusive, implicit_base=implicit_base))
|
||||
revs = revs[-relative - reldelta:]
|
||||
if assert_relative_length and \
|
||||
len(revs) != abs(relative) + reldelta:
|
||||
raise RevisionError(
|
||||
"Relative revision %s didn't "
|
||||
"produce %d migrations" % (upper, abs(relative)))
|
||||
return iter(revs)
|
||||
elif isinstance(lower, compat.string_types) and \
|
||||
_relative_destination.match(lower):
|
||||
reldelta = 1 if inclusive else 0
|
||||
match = _relative_destination.match(lower)
|
||||
relative = int(match.group(2))
|
||||
branch_label = match.group(1)
|
||||
relative_lower = self._relative_iterate(
|
||||
lower, upper, False, implicit_base,
|
||||
inclusive, assert_relative_length
|
||||
)
|
||||
if relative_lower:
|
||||
return relative_lower
|
||||
|
||||
if branch_label:
|
||||
to_ = "%s@base" % branch_label
|
||||
else:
|
||||
to_ = "base"
|
||||
|
||||
revs = list(
|
||||
self._iterate_revisions(
|
||||
upper, to_,
|
||||
inclusive=inclusive, implicit_base=implicit_base))
|
||||
revs = revs[0:-relative + reldelta]
|
||||
if assert_relative_length and \
|
||||
len(revs) != abs(relative) + reldelta:
|
||||
raise RevisionError(
|
||||
"Relative revision %s didn't "
|
||||
"produce %d migrations" % (lower, abs(relative)))
|
||||
return iter(revs)
|
||||
else:
|
||||
return self._iterate_revisions(
|
||||
upper, lower, inclusive=inclusive, implicit_base=implicit_base)
|
||||
return self._iterate_revisions(
|
||||
upper, lower, inclusive=inclusive, implicit_base=implicit_base)
|
||||
|
||||
def _get_descendant_nodes(
|
||||
self, targets, map_=None, check=False, include_dependencies=True):
|
||||
|
|
|
@ -498,6 +498,11 @@ This kind of thing works from history as well::
|
|||
|
||||
$ alembic history -r current:shoppingcart@+2
|
||||
|
||||
The newer ``relnum+delta`` format can be combined as well, for example
|
||||
if we wanted to list along ``shoppingcart`` up until two revisions
|
||||
before the head::
|
||||
|
||||
$ alembic history -r :shoppingcart@head-2
|
||||
|
||||
.. _multiple_bases:
|
||||
|
||||
|
|
|
@ -72,6 +72,14 @@ Changelog
|
|||
|
||||
:ref:`batch_migrations`
|
||||
|
||||
.. change::
|
||||
:tags: feature, commands
|
||||
|
||||
Relative revision identifiers as used with ``alembic upgrade``,
|
||||
``alembic downgrade`` and ``alembic history`` can be combined with
|
||||
specific revisions as well, e.g. ``alembic upgrade ae10+3``, to produce
|
||||
a migration target relative to the given exact version.
|
||||
|
||||
.. change::
|
||||
:tags: bug, autogenerate, postgresql
|
||||
:tickets: 247
|
||||
|
|
|
@ -405,6 +405,20 @@ Running again to ``head``::
|
|||
|
||||
We've now added the ``last_transaction_date`` column to the database.
|
||||
|
||||
Partial Revision Identifiers
|
||||
=============================
|
||||
|
||||
Any time we need to refer to a revision number explicitly, we have the option
|
||||
to use a partial number. As long as this number uniquely identifies the
|
||||
version, it may be used in any command in any place that version numbers
|
||||
are accepted::
|
||||
|
||||
$ alembic upgrade ae1
|
||||
|
||||
Above, we use ``ae1`` to refer to revision ``ae1027a6acf``.
|
||||
Alembic will stop and let you know if more than one version starts with
|
||||
that prefix.
|
||||
|
||||
.. relative_migrations:
|
||||
|
||||
Relative Migration Identifiers
|
||||
|
@ -419,19 +433,13 @@ Negative values are accepted for downgrades::
|
|||
|
||||
$ alembic downgrade -1
|
||||
|
||||
Partial Revision Identifiers
|
||||
=============================
|
||||
Relative identifiers may also be in terms of a specific revision. For example,
|
||||
to upgrade to revision ``ae1027a6acf`` plus two additional steps::
|
||||
|
||||
Any time we need to refer to a revision number explicitly, we have the option
|
||||
to use a partial number. As long as this number uniquely identifies the
|
||||
version, it may be used in any command in any place that version numbers
|
||||
are accepted::
|
||||
$ alembic upgrade ae10+2
|
||||
|
||||
$ alembic upgrade ae1
|
||||
|
||||
Above, we use ``ae1`` to refer to revision ``ae1027a6acf``.
|
||||
Alembic will stop and let you know if more than one version starts with
|
||||
that prefix.
|
||||
.. versionadded:: 0.7.0 Support for relative migrations in terms of a specific
|
||||
revision.
|
||||
|
||||
Getting Information
|
||||
===================
|
||||
|
|
|
@ -99,6 +99,18 @@ class RevisionPathTest(MigrationTest):
|
|||
set([e.revision])
|
||||
)
|
||||
|
||||
self._assert_upgrade(
|
||||
"%s+2" % b.revision, a.revision,
|
||||
[self.up_(b), self.up_(c), self.up_(d)],
|
||||
set([d.revision])
|
||||
)
|
||||
|
||||
self._assert_upgrade(
|
||||
"%s-2" % d.revision, a.revision,
|
||||
[self.up_(b)],
|
||||
set([b.revision])
|
||||
)
|
||||
|
||||
def test_invalid_relative_upgrade_path(self):
|
||||
a, b, c, d, e = self.a, self.b, self.c, self.d, self.e
|
||||
assert_raises_message(
|
||||
|
@ -142,6 +154,18 @@ class RevisionPathTest(MigrationTest):
|
|||
set([b.revision])
|
||||
)
|
||||
|
||||
self._assert_downgrade(
|
||||
"%s+2" % a.revision, d.revision,
|
||||
[self.down_(d)],
|
||||
set([c.revision])
|
||||
)
|
||||
|
||||
self._assert_downgrade(
|
||||
"%s-2" % c.revision, d.revision,
|
||||
[self.down_(d), self.down_(c), self.down_(b)],
|
||||
set([a.revision])
|
||||
)
|
||||
|
||||
def test_invalid_relative_downgrade_path(self):
|
||||
a, b, c, d, e = self.a, self.b, self.c, self.d, self.e
|
||||
assert_raises_message(
|
||||
|
@ -275,6 +299,28 @@ class BranchedPathTest(MigrationTest):
|
|||
set([a.revision])
|
||||
)
|
||||
|
||||
def test_relative_upgrade(self):
|
||||
a, b, c1, d1, c2, d2 = (
|
||||
self.a, self.b, self.c1, self.d1, self.c2, self.d2
|
||||
)
|
||||
|
||||
self._assert_upgrade(
|
||||
"c2branch@head-1", b.revision,
|
||||
[self.up_(c2)],
|
||||
set([c2.revision])
|
||||
)
|
||||
|
||||
def test_relative_downgrade(self):
|
||||
a, b, c1, d1, c2, d2 = (
|
||||
self.a, self.b, self.c1, self.d1, self.c2, self.d2
|
||||
)
|
||||
|
||||
self._assert_downgrade(
|
||||
"c2branch@base+2", [d2.revision, d1.revision],
|
||||
[self.down_(d2), self.down_(c2), self.down_(d1)],
|
||||
set([c1.revision])
|
||||
)
|
||||
|
||||
|
||||
class BranchFromMergepointTest(MigrationTest):
|
||||
"""this is a form that will come up frequently in the
|
||||
|
|
Loading…
Reference in New Issue