From e813f1ff87c58d5521868a02f08f1c1ad81303b8 Mon Sep 17 00:00:00 2001 From: Alexey Bezhan Date: Fri, 17 Nov 2017 13:58:44 +0000 Subject: [PATCH] Add a script to fix migration ordering conflicts When generating a new migration we give it a number that increments the latest existing migration on master. This means that when there are multiple PRs open containing a migration and one of them gets merged the others need to be updated to move their migration files to apply on top of the recently merged one. This requires renaming the file and changing migration references for both the migration revision and down_revision. If a PR introduced more than 1 migration they all need to be updated one after another since each one needs to be renamed. This adds a script to simplify this process. `./scripts/fix_migrations.py` will check for any branch points If it finds exactly one (which should be the common case), it asks which migration should be moved and renames / updates references to move the selected branch on top of the other one. It won't resolve any conflicts within migrations themselves (eg if both branches modified the same column) and it won't try to resolve cases with more than 1 branch. --- scripts/fix_migrations.py | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 scripts/fix_migrations.py diff --git a/scripts/fix_migrations.py b/scripts/fix_migrations.py new file mode 100755 index 000000000..3965ed411 --- /dev/null +++ b/scripts/fix_migrations.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import os +import sys + +from alembic.script import ScriptDirectory + +sys.path.append('.') + + +def get_branch_points(migrations): + return [m for m in migrations.walk_revisions() if m.is_branch_point] + + +def get_branches(migrations, branch_point, heads): + return [list(migrations.iterate_revisions(m, branch_point.revision))[::-1] + for m in heads] + + +def choice(prompt, options, option_fmt=lambda x: x): + print("{}:\n".format(prompt)) + for i, option in enumerate(options): + print("{}. {}".format(i + 1, option_fmt(option))) + + print() + choice = input("Option> ") + + return options[int(choice) - 1] + + +def rename_revision(current_revision, new_base): + new_id = int(new_base[:4]) + 1 + return "{:04d}{}".format(new_id, current_revision[4:]) + + +def reorder_revisions(revisions, old_base, new_base): + if not revisions: + return + + head, *tail = revisions + new_revision_id = rename_revision(head.revision, new_base) + + print("Moving {} to {}".format(head.revision, new_revision_id)) + with open(head.path, 'r') as rev_file: + file_data = rev_file.read() + + file_data = file_data.replace(head.revision, new_revision_id).replace(old_base, new_base) + + with open(head.path.replace(head.revision, new_revision_id), 'w') as rev_file: + rev_file.write(file_data) + + print("Removing {}".format(head.path)) + os.remove(head.path) + + reorder_revisions(tail, head.revision, new_revision_id) + + +def fix_branch_point(migrations, branch_point, heads): + print("Migrations directory has a branch point at {}".format(branch_point.revision)) + + branches = get_branches(migrations, branch_point, heads) + move_branch = choice("Select migrations to move", branches, + lambda x: " -> ".join(m.revision for m in x)) + branches.remove(move_branch) + + reorder_revisions(move_branch, branch_point.revision, branches[0][-1].revision) + + +def main(migrations_path): + migrations = ScriptDirectory(migrations_path) + + branch_points = get_branch_points(migrations) + heads = migrations.get_heads() + + if not branch_points: + print("Migrations are ordered") + elif len(branch_points) == 1 and len(heads) == 2: + fix_branch_point(migrations, branch_points[0], heads) + else: + print("Found {} branch points and {} heads, can't fix automatically".format( + [bp.revision for bp in branch_points], heads)) + sys.exit(1) + + +if __name__ == '__main__': + main('migrations/')