Mercurialなどのバージョン管理システムでブランチを使っていて、DjangoとSouthを使っている場合、Southのマイグレーションがブランチ間で衝突して、マージしないといけないことがよくある。
その際の解消手順についてメモ。
試したのはDjango1.4.1, South0.7.6
myappにモデルを追加するところから、マイグレーションが衝突するフローとマージまで。
マイグレーションの初期化
モデルがない状態でとりあえず初期化する。
$ python manage.py schemamigration myapp --initial Created 0001_initial.py. You can now apply this migration with: ./manage.py migrate myapp
syncdbとmigrateを実行する。
$ python manage.py syncdb $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0001_initial. > myapp:0001_initial - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
ここからモデルを作成していく。
モデルの追加
myapp/models.py
idのみのItemモデルを定義。
from django.db import models class Item(models.Model): class Meta: db_table = 'item'
schemamigrationを実行してマイグレーションファイルを追加し、migrateを実行。
$ python manage.py schemamigration myapp --auto + Added model myapp.Item Created 0002_auto__add_item.py. You can now apply this migration with: ./manage.py migrate myapp $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0002_auto__add_item. > myapp:0002_auto__add_item - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
この状態でソースコードをリポジトリにコミットする。コミットしたのはdefaultブランチ。
$ hg add $ hg commit
ブランチを作成してモデルにフィールドを追加
defaultから作業用にブランチを作成する(issue-123ブランチ)。
$ hg branch issue-123
Itemモデルにnameというフィールドを追加する。
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField(max_length=20) # 追加 class Meta: db_table = 'item'
追加したら、マイグレーションを実行する。
$ python manage.py schemamigration myapp --auto ? The field 'Item.name' does not have a default specified, yet is NOT NULL. ? Since you are adding this field, you MUST specify a default ? value to use for existing rows. Would you like to: ? 1. Quit now, and add a default to the field in models.py ? 2. Specify a one-off value to use for existing columns now ? Please select a choice: 2 ? Please enter Python code for your one-off default value. ? The datetime module is available, so you can do e.g. datetime.date.today() >>> '' + Added field name on myapp.Item Created 0003_auto__add_field_item_name.py. You can now apply this migration with: ./manage.py migrate myapp $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0003_auto__add_field_item_name. > myapp:0003_auto__add_field_item_name - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
この状態でコミットする。
defaultブランチでフィールドを追加する
ここで、別の優先される作業をdefaultで行なうとする。nameフィールドの追加より前に別のフィールドが増えるとする。
まず、defaultに切り替える前に、defaultブランチの状態までマイグレーションを実行しておく。
$ python manage.py migrate --list myapp (*) 0001_initial (*) 0002_auto__add_item (*) 0003_auto__add_field_item_name $ python manage.py migrate myapp 0002 - Soft matched migration 0002 to 0002_auto__add_item. Running migrations for myapp: - Migrating backwards to just after 0002_auto__add_item. < myapp:0003_auto__add_field_item_name
ブランチをdefaultに切り替える。
$ hg update default 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
Itemモデルにフィールドを追加する。
myapp/models.py
from django.db import models class Item(models.Model): value = models.IntegerField(default=0) class Meta: db_table = 'item'
マイグレーションを実行する。
$ python manage.py schemamigration myapp --auto + Added field value on myapp.Item Created 0003_auto__add_field_item_value.py. You can now apply this migration with: ./manage.py migrate myapp $ python manage.py migrate myapp --list myapp (*) 0001_initial (*) 0002_auto__add_item ( ) 0003_auto__add_field_item_value $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0003_auto__add_field_item_value. > myapp:0003_auto__add_field_item_value - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
この状態でdefaultブランチにコミットする。
$ hg add $ hg commit
この時点で履歴はこうなっている。
$ hg glog -l 3 @ changeset: 4:248b48490ff8 | tag: tip | parent: 2:f9731044b980 | user: Shinya Okano<tokibito@example.com> | date: Sat Oct 06 23:42:21 2012 +0900 | summary: valueフィールドを追加 | | o changeset: 3:42ee06323dd9 |/ branch: issue-123 | user: Shinya Okano<tokibito@example.com> | date: Sat Oct 06 23:23:24 2012 +0900 | summary: nameフィールドを追加 | o changeset: 2:f9731044b980 | user: Shinya Okano<tokibito@example.com> | date: Sat Oct 06 23:16:22 2012 +0900 | summary: Itemモデルを追加
ブランチをマージする
issue-123ブランチのコミット後にdefaultにコミットがあったため、issue-123のほうでdefaultを取り込む。
まずは、issue-123のブランチ開始時点までマイグレーションしてからブランチを切り替え、issue-123側の最新までマイグレーションする。
$ python manage.py migrate myapp 0002 - Soft matched migration 0002 to 0002_auto__add_item. Running migrations for myapp: - Migrating backwards to just after 0002_auto__add_item. < myapp:0003_auto__add_field_item_value $ hg update issue-123 2 files updated, 0 files merged, 1 files removed, 0 files unresolved $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0003_auto__add_field_item_name. > myapp:0003_auto__add_field_item_name - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
ブランチをマージする。同じ行を変更していたので衝突する。
$ hg merge default merging myapp/models.py warning: conflicts during merge. merging myapp/models.py incomplete! (edit conflicts, then use 'hg resolve --mark') 1 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon (django-1.4)tokibito@ubuntu:~/sandbox/myproject$ hg resolve -l U myapp/models.py
モデルを編集して衝突を解消し、コミットする。
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField(max_length=20) value = models.IntegerField(default=0) class Meta: db_table = 'item'
$ hg resolve -m myapp/models.py $ hg ci
マージ後にmigrateを実行しておく。
$ python manage.py migrate myapp --list myapp (*) 0001_initial (*) 0002_auto__add_item (*) 0003_auto__add_field_item_name ( ) 0003_auto__add_field_item_value $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0003_auto__add_field_item_value. > myapp:0003_auto__add_field_item_value - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
さらにブランチでフィールドを追加する(問題発生箇所)
issue-123ブランチで更にItemモデルにフィールドを追加する。
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField(max_length=20) value = models.IntegerField(default=0) cost = models.IntegerField(default=0) class Meta: db_table = 'item'
schemamigrationを実行する。
$ python manage.py schemamigration myapp --auto ? The field 'Item.name' does not have a default specified, yet is NOT NULL. ? Since you are adding this field, you MUST specify a default ? value to use for existing rows. Would you like to: ? 1. Quit now, and add a default to the field in models.py ? 2. Specify a one-off value to use for existing columns now ? Please select a choice:
ここで、ようやく問題が発生する。既にマイグレーションファイルが存在するItem.nameが追加されたフィールドとして検出されてしまう。
これは、最後に適用した0003_auto__add_field_item_valueで、nameフィールドが存在しないことになっているため。
この場合、Southの--emptyオプションでマイグレーションファイルを作成し、存在するフィールドの状態にマイグレーションのデータベースを合わせればよいらしい。
追加するフィールドを一度コメントアウトし、--emptyオプションを指定してschemamigrationを実行する。
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField(max_length=20) value = models.IntegerField(default=0) # cost = models.IntegerField(default=0) コメントアウト class Meta: db_table = 'item'
$ python manage.py schemamigration myapp merge_item_model --empty Created 0004_merge_item_model.py. You must now edit this migration and add the code for each direction. $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0004_merge_item_model. > myapp:0004_merge_item_model - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
migrateを実行してから、追加するフィールドのコメントアウトを外し、再度schemamigration, migrateを実行する。
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField(max_length=20) value = models.IntegerField(default=0) cost = models.IntegerField(default=0) class Meta: db_table = 'item'
$ python manage.py schemamigration myapp --auto + Added field cost on myapp.Item Created 0005_auto__add_field_item_cost.py. You can now apply this migration with: ./manage.py migrate myapp $ python manage.py migrate myapp Running migrations for myapp: - Migrating forwards to 0005_auto__add_field_item_cost. > myapp:0005_auto__add_field_item_cost - Loading initial data for myapp. Installed 0 object(s) from 0 fixture(s)
これでフィールドを追加できた。
defaultにマージ後、マイグレーション実行時にInconsistentMigrationHistory例外が発生する場合は、--mergeオプションを指定してmigrateを実行すればよい。