6
votes

Let's suppose we have migrations with the following dependency graph (all applied): Initial state

Now, for some reason we want to revert database schema to the state after applying migration 0006_f. We type:

./manage.py migrate myapp 0006_f

and now we have the following state: One branch reverted

The problem is that Django does not revert the right branch, so now we have some migrations applied from left branch and some from the right one.

One way to avoid it is to migrate back to 0002_b and forward to 0006_f but this can cause data loss. Also some of migrations 0006_f, 0005_e, 0004_d, 0003_c can be irreversible.

Another way is to run the following:

./manage.py migrate myapp 0006_f
./manage.py migrate myapp 0004_d1

Now, to achieve the desired state we only have to revert the migration 0004_d1 and I do not see a way to undo 0004_d1 without undoing 0006_f, 0005_e and 0004_d except for opening DB shell and reverting it manually.

Is there a way to explicitly undo only one migration? Is there another way to properly undo migrations from parallel branch? Is there some reason for Django not to automatically revert migrations from parallel branch when undoing merge migration?

1

1 Answers

4
votes

edited 2019-10-17: I'm adding a step 0 which I've found reduces some risks when cross-app dependencies are involved.

If I'm reading your question correctly, you had a situation similar to mine where I wanted to revert one specific branch without touching the others.

I managed to do this (in v1.11.7, and v2.2) with these steps:

  1. reverse migrate to the first node of each branch you want to un-apply (0004_d1)
  2. fake a reverse migration to the common ancestor (0003_c) edit: see note at bottom
  3. fake migrations to the first node of each branch you want to revert (0004_d1)
  4. reverse migrate to the common ancestor from step 1 (0003_c)
  5. fake a migration to the tip of the branch you wanted to preserve (0007_g)
  6. delete or modify the merge migration as needed; see below (0008_merge)

So in your situation:

./manage.py migrate 0004_d1
./manage.py migrate --fake myapp 0003_c
./manage.py migrate --fake myapp 0004_d1
./manage.py migrate myapp 0003_c
./manage.py migrate --fake myapp 0007_g

If your merge migration 0008_merge does any actual work or migrates more branches, you'll probably have to edit it manually to omit the 0005_e1 branch and then fake-migrate to it; otherwise you should be able to just delete it.

edit: note regarding step 2: It seems that sometimes, if step 2 causes some cross-app dependency migrations to be fake-unapplied, they may cause step 4 to fail. Step 1 mitigates this risk, but you should check what operations have been applied and try to make sure any cross-app dependencies are not in a faked state during step 4.