mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
9.2 KiB
Python
236 lines
9.2 KiB
Python
5 years ago
|
"""
|
||
|
Main migration logic.
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import sys
|
||
|
|
||
|
from django.core.exceptions import ImproperlyConfigured
|
||
|
|
||
|
import south.db
|
||
|
from south import exceptions
|
||
|
from south.models import MigrationHistory
|
||
|
from south.db import db, DEFAULT_DB_ALIAS
|
||
|
from south.migration.migrators import (Backwards, Forwards,
|
||
|
DryRunMigrator, FakeMigrator,
|
||
|
LoadInitialDataMigrator)
|
||
|
from south.migration.base import Migration, Migrations
|
||
|
from south.migration.utils import SortedSet
|
||
|
from south.migration.base import all_migrations
|
||
|
from south.signals import pre_migrate, post_migrate
|
||
|
|
||
|
|
||
|
def to_apply(forwards, done):
|
||
|
return [m for m in forwards if m not in done]
|
||
|
|
||
|
def to_unapply(backwards, done):
|
||
|
return [m for m in backwards if m in done]
|
||
|
|
||
|
def problems(pending, done):
|
||
|
last = None
|
||
|
if not pending:
|
||
|
raise StopIteration()
|
||
|
for migration in pending:
|
||
|
if migration in done:
|
||
|
last = migration
|
||
|
continue
|
||
|
if last and migration not in done:
|
||
|
yield last, migration
|
||
|
|
||
|
def forwards_problems(pending, done, verbosity):
|
||
|
"""
|
||
|
Takes the list of linearised pending migrations, and the set of done ones,
|
||
|
and returns the list of problems, if any.
|
||
|
"""
|
||
|
return inner_problem_check(problems(reversed(pending), done), done, verbosity)
|
||
|
|
||
|
def backwards_problems(pending, done, verbosity):
|
||
|
return inner_problem_check(problems(pending, done), done, verbosity)
|
||
|
|
||
|
def inner_problem_check(problems, done, verbosity):
|
||
|
"Takes a set of possible problems and gets the actual issues out of it."
|
||
|
result = []
|
||
|
for last, migration in problems:
|
||
|
checked = set([])
|
||
|
# 'Last' is the last applied migration. Step back from it until we
|
||
|
# either find nothing wrong, or we find something.
|
||
|
to_check = list(last.dependencies)
|
||
|
while to_check:
|
||
|
checking = to_check.pop()
|
||
|
if checking in checked:
|
||
|
continue
|
||
|
checked.add(checking)
|
||
|
|
||
|
if checking not in done:
|
||
|
# That's bad. Error.
|
||
|
if verbosity:
|
||
|
print((" ! Migration %s should not have been applied "
|
||
|
"before %s but was." % (last, checking)))
|
||
|
result.append((last, checking))
|
||
|
else:
|
||
|
to_check.extend(checking.dependencies)
|
||
|
return result
|
||
|
|
||
|
def check_migration_histories(histories, delete_ghosts=False, ignore_ghosts=False):
|
||
|
"Checks that there's no 'ghost' migrations in the database."
|
||
|
exists = SortedSet()
|
||
|
ghosts = []
|
||
|
for h in histories:
|
||
|
try:
|
||
|
m = h.get_migration()
|
||
|
m.migration()
|
||
|
except exceptions.UnknownMigration:
|
||
|
ghosts.append(h)
|
||
|
except ImproperlyConfigured:
|
||
|
pass # Ignore missing applications
|
||
|
else:
|
||
|
exists.add(m)
|
||
|
if ghosts:
|
||
|
# They may want us to delete ghosts.
|
||
|
if delete_ghosts:
|
||
|
for h in ghosts:
|
||
|
h.delete()
|
||
|
elif not ignore_ghosts:
|
||
|
raise exceptions.GhostMigrations(ghosts)
|
||
|
return exists
|
||
|
|
||
|
def get_dependencies(target, migrations):
|
||
|
forwards = list
|
||
|
backwards = list
|
||
|
if target is None:
|
||
|
backwards = migrations[0].backwards_plan
|
||
|
else:
|
||
|
forwards = target.forwards_plan
|
||
|
# When migrating backwards we want to remove up to and
|
||
|
# including the next migration up in this app (not the next
|
||
|
# one, that includes other apps)
|
||
|
migration_before_here = target.next()
|
||
|
if migration_before_here:
|
||
|
backwards = migration_before_here.backwards_plan
|
||
|
return forwards, backwards
|
||
|
|
||
|
def get_direction(target, applied, migrations, verbosity, interactive):
|
||
|
# Get the forwards and reverse dependencies for this target
|
||
|
forwards, backwards = get_dependencies(target, migrations)
|
||
|
# Is the whole forward branch applied?
|
||
|
problems = None
|
||
|
forwards = forwards()
|
||
|
workplan = to_apply(forwards, applied)
|
||
|
if not workplan:
|
||
|
# If they're all applied, we only know it's not backwards
|
||
|
direction = None
|
||
|
else:
|
||
|
# If the remaining migrations are strictly a right segment of
|
||
|
# the forwards trace, we just need to go forwards to our
|
||
|
# target (and check for badness)
|
||
|
problems = forwards_problems(forwards, applied, verbosity)
|
||
|
direction = Forwards(verbosity=verbosity, interactive=interactive)
|
||
|
if not problems:
|
||
|
# What about the whole backward trace then?
|
||
|
backwards = backwards()
|
||
|
missing_backwards = to_apply(backwards, applied)
|
||
|
if missing_backwards != backwards:
|
||
|
# If what's missing is a strict left segment of backwards (i.e.
|
||
|
# all the higher migrations) then we need to go backwards
|
||
|
workplan = to_unapply(backwards, applied)
|
||
|
problems = backwards_problems(backwards, applied, verbosity)
|
||
|
direction = Backwards(verbosity=verbosity, interactive=interactive)
|
||
|
return direction, problems, workplan
|
||
|
|
||
|
def get_migrator(direction, db_dry_run, fake, load_initial_data):
|
||
|
if not direction:
|
||
|
return direction
|
||
|
if db_dry_run:
|
||
|
direction = DryRunMigrator(migrator=direction, ignore_fail=False)
|
||
|
elif fake:
|
||
|
direction = FakeMigrator(migrator=direction)
|
||
|
elif load_initial_data:
|
||
|
direction = LoadInitialDataMigrator(migrator=direction)
|
||
|
return direction
|
||
|
|
||
|
def get_unapplied_migrations(migrations, applied_migrations):
|
||
|
applied_migration_names = ['%s.%s' % (mi.app_name,mi.migration) for mi in applied_migrations]
|
||
|
|
||
|
for migration in migrations:
|
||
|
is_applied = '%s.%s' % (migration.app_label(), migration.name()) in applied_migration_names
|
||
|
if not is_applied:
|
||
|
yield migration
|
||
|
|
||
|
def migrate_app(migrations, target_name=None, merge=False, fake=False, db_dry_run=False, yes=False, verbosity=0, load_initial_data=False, skip=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, interactive=False):
|
||
|
app_label = migrations.app_label()
|
||
|
|
||
|
verbosity = int(verbosity)
|
||
|
# Fire off the pre-migrate signal
|
||
|
pre_migrate.send(None, app=app_label, verbosity=verbosity, interactive=verbosity, db=database)
|
||
|
|
||
|
# If there aren't any, quit quizically
|
||
|
if not migrations:
|
||
|
print("? You have no migrations for the '%s' app. You might want some." % app_label)
|
||
|
return
|
||
|
|
||
|
# Load the entire dependency graph
|
||
|
Migrations.calculate_dependencies()
|
||
|
|
||
|
# Check there's no strange ones in the database
|
||
|
applied_all = MigrationHistory.objects.filter(applied__isnull=False).order_by('applied').using(database)
|
||
|
applied = applied_all.filter(app_name=app_label).using(database)
|
||
|
south.db.db = south.db.dbs[database]
|
||
|
Migrations.invalidate_all_modules()
|
||
|
|
||
|
south.db.db.debug = (verbosity > 1)
|
||
|
|
||
|
if target_name == 'current-1':
|
||
|
if applied.count() > 1:
|
||
|
previous_migration = applied[applied.count() - 2]
|
||
|
if verbosity:
|
||
|
print('previous_migration: %s (applied: %s)' % (previous_migration.migration, previous_migration.applied))
|
||
|
target_name = previous_migration.migration
|
||
|
else:
|
||
|
if verbosity:
|
||
|
print('previous_migration: zero')
|
||
|
target_name = 'zero'
|
||
|
elif target_name == 'current+1':
|
||
|
try:
|
||
|
first_unapplied_migration = get_unapplied_migrations(migrations, applied).next()
|
||
|
target_name = first_unapplied_migration.name()
|
||
|
except StopIteration:
|
||
|
target_name = None
|
||
|
|
||
|
applied_all = check_migration_histories(applied_all, delete_ghosts, ignore_ghosts)
|
||
|
|
||
|
# Guess the target_name
|
||
|
target = migrations.guess_migration(target_name)
|
||
|
if verbosity:
|
||
|
if target_name not in ('zero', None) and target.name() != target_name:
|
||
|
print(" - Soft matched migration %s to %s." % (target_name,
|
||
|
target.name()))
|
||
|
print("Running migrations for %s:" % app_label)
|
||
|
|
||
|
# Get the forwards and reverse dependencies for this target
|
||
|
direction, problems, workplan = get_direction(target, applied_all, migrations,
|
||
|
verbosity, interactive)
|
||
|
if problems and not (merge or skip):
|
||
|
raise exceptions.InconsistentMigrationHistory(problems)
|
||
|
|
||
|
# Perform the migration
|
||
|
migrator = get_migrator(direction, db_dry_run, fake, load_initial_data)
|
||
|
if migrator:
|
||
|
migrator.print_title(target)
|
||
|
success = migrator.migrate_many(target, workplan, database)
|
||
|
# Finally, fire off the post-migrate signal
|
||
|
if success:
|
||
|
post_migrate.send(None, app=app_label, verbosity=verbosity, interactive=verbosity, db=database)
|
||
|
else:
|
||
|
if verbosity:
|
||
|
# Say there's nothing.
|
||
|
print('- Nothing to migrate.')
|
||
|
# If we have initial data enabled, and we're at the most recent
|
||
|
# migration, do initial data.
|
||
|
# Note: We use a fake Forwards() migrator here. It's never used really.
|
||
|
if load_initial_data:
|
||
|
migrator = LoadInitialDataMigrator(migrator=Forwards(verbosity=verbosity))
|
||
|
migrator.load_initial_data(target, db=database)
|
||
|
# Send signal.
|
||
|
post_migrate.send(None, app=app_label, verbosity=verbosity, interactive=verbosity, db=database)
|