Package: django-linear-migrations
Ensure your migration history is linear.For a bit of background, see theintroductory blog post.RequirementsPython 3.8 to 3.12 supported.Django 3.2 to 5.0 supported.Want to work smarter and faster?Check out my bookBoost Your Django DXwhich covers django-linear-migrations and many other tools to improve your development experience.InstallationFirst,install with pip:python-mpipinstalldjango-linear-migrationsSecond,add the app to yourINSTALLED_APPSsetting:INSTALLED_APPS=[...,"django_linear_migrations",...,]The app relies on overriding the built-inmakemigrationscommand.If your project has a custommakemigrationscommand,ensure the app containing your custom command isabovedjango_linear_migrations, and that your command subclasses itsCommandclass:# myapp/management/commands/makemigrations.pyfromdjango_linear_migrations.management.commands.makemigrationsimport(CommandasBaseCommand,)classCommand(BaseCommand):...Third,check the automatic detection of first-party apps.
Run this command:pythonmanage.pycreate_max_migration_files--dry-runThis command is for creatingmax_migration.txtfiles (more on which later) - in dry run mode it lists the apps it would make such files for.
It tries to automatically detect which apps are first-party, i.e. belong to your project.
The automatic detection checks the path of app’s code to see if is within a virtualenv, but this detection can sometimes fail, for example on editable packages installed with-e.
If you see any apps listed thataren’tpart of your project, define the list of first-party apps’ labels in aFIRST_PARTY_APPSsetting that you combine intoINSTALLED_APPS:FIRST_PARTY_APPS=[]INSTALLED_APPS=FIRST_PARTY_APPS+["django_linear_migrations",...](Note: Django recommends you always list first-party apps first in your project so they can override things in third-party and contrib apps.)Fourth,create themax_migration.txtfiles for your first-party apps by re-running the command without the dry run flag:pythonmanage.pycreate_max_migration_filesIn the future, when you add a new app to your project, you’ll need to create itsmax_migration.txtfile.
Add the new app toINSTALLED_APPSorFIRST_PARTY_APPSas appropriate, then rerun the creation command for the new app by specifying its label:pythonmanage.pycreate_max_migration_filesmy_new_appUsagedjango-linear-migrations helps you work on Django projects where several branches adding migrations may be in progress at any time.
It enforces that your apps have alinearmigration history, avoiding merge migrations and the problems they can cause from migrations running in different orders.
It does this by makingmakemigrationsrecord the name of the latest migration in per-appmax_migration.txtfiles.
These files will then cause a merge conflicts in your source control tool (Git, Mercurial, etc.) in the case of migrations being developed in parallel.
The first merged migration for an app will prevent the second from being merged, without addressing the conflict.
The includedrebase_migrationcommand can help automatically such conflicts.System Checksdjango-linear-migrations comes with several system checks that verify that yourmax_migration.txtfiles are in sync.
These are:dlm.E001:<app_label>’s max_migration.txt does not exist.dlm.E002:<app_label>’s max_migration.txt contains multiple lines.dlm.E003:<app_label>’s max_migration.txt points to non-existent migration ‘<bad_migration_name>’.dlm.E004:<app_label>’s max_migration.txt contains ‘<max_migration_name>’, but the latest migration is ‘<real_max_migration_name>’.dlm.E005: Conflicting migrations detected; multiple leaf nodes in the migration graph:<conflicting_migrations>create_max_migration_filesCommandpythonmanage.pycreate_max_migration_files[app_label[app_label...]]This management command createsmax_migration.txtfiles for all first party apps, or the given labels.
It’s used in initial installation of django-linear-migrations, and for recreating.Pass the--dry-runflag to only list themax_migration.txtfiles that would be created.Pass the--recreateflag to re-create files that already exist.
This may be useful after altering migrations with merges or manually.rebase_migrationCommandThis management command can help you fix migration conflicts.
Following a conflicted “rebase” operation in Git, run it with the name of the app to auto-fix the migrations for:$pythonmanage.pyrebase_migration<app_label>The command uses the conflict information in themax_migration.txtfile to determine which migration to rebase.
It automatically detects whether a Git merge or rebase operation is in progress, assuming rebase if a Git repository cannot be found.
The command then:renames the migrationedits it to depend on the new migration from your main branchupdatesmax_migration.txt.If Black is installed, the command formats the updated migration file with it, like Django’s built-in migration commands do (from version 4.1+).
See below for some examples and caveats.Note rebasing the migration might not always be thecorrectthing to do.
If the migrations in your main and feature branches have both affected the same models, rebasing the migration to the end may not make sense.
However, such parallel changes wouldnormallycause conflicts in your model files or other parts of the source code as well.Worked ExampleImagine you were working on your project’sbooksapp in a feature branch calledtitlesand created a migration called0002_longer_titles.
Meanwhile a commit has been merged to yourmainbranch with adifferent2nd migration forbookscalled0002_author_nicknames.
Thanks to django-linear-migrations, themax_migration.txtfile will show as conflicted between your feature and main branches.Start the fix by reversing your new migration from your local database.
This is necessary since it will be renamed after rebasing and seen as unapplied.
Do this by switching to the feature branchtitlesmigrating back to the last common migration:$gitswitchtitles$pythonmanage.pymigratebooks0001Then, fetch the latest code:$gitswitchmain$gitpull...Next, rebase yourtitlesbranch on top of it.
During this process, Git will detect the conflict onmax_migration.txt:$gitswitchtitles$gitrebasemainAuto-merging books/models.py
CONFLICT (content): Merge conflict in books/migrations/max_migration.txt
error: could not apply 123456789... Increase Book title length
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 123456789... Increase Book title lengthThe contents of thebooksapp’smax_migration.txtat this point will look something like this:$catbooks/migrations/max_migration.txt<<<<<<< HEAD
0002_author_nicknames
=======
0002_longer_titles
>>>>>>> 123456789 (Increase Book title length)At this point, userebase_migrationto automatically fix thebooksmigration history:$pythonmanage.pyrebase_migrationbooksRenamed 0002_longer_titles.py to 0003_longer_titles.py, updated its dependencies, and updated max_migration.txt.This places the conflicted migration on the end of the migration history.
It renames the file appropriately, modifies itsdependencies =[...]declaration, and updates the migration named inmax_migration.txtappropriately.After this, you should be able to continue the rebase:$gitaddbooks/migrations$gitrebase--continueAnd then migrate your local database to allow you to continue development:$pythonmanage.pymigratebooksOperations to perform:
  Target specific migration: 0003_longer_titles, from books
Running migrations:
  Applying books.0002_author_nicknames... OK
  Applying books.0003_longer_titles... OKCode Formattingrebase_migrationdoes not guarantee that its edits match your code style.
If you use a formatter like Black, you’ll want to run it after applyingrebase_migration.If you usepre-commit, note that Git does not invoke hooks during rebase commits.
You can run it manually on changed files withpre-commitrun.Branches With Multiple CommitsImagine the same example as above, but your feature branch has several commits editing the migration.
This time, before rebasing onto the latestmainbranch, squash the commits in your feature branch together.
This way,rebase_migrationcan edit the migration file when the conflict occurs.You can do this with:$gitrebase-i--keep-basemainThis will open Git’sinteractive modefile.
Edit this so that every comit after the first will be squashed, by starting each line with “s”.
Then close the file, and the rebase will execute.After this operation, you can rebase onto your latestmainbranch as per the previous example.Branches With Multiple Migrationsrebase_migrationdoes not currently support rebasing multiple migrations (in the same app).
This isan open feature request, but it is not a priority, since it’s generally a good idea to restrict changes to one migration at a time.
Consider merging your migrations into one before rebasing.InspirationI’ve seen similar techniques to the one implemented by django-linear-migrations at several places, and they acted as the inspiration for putting this package together.
My previous clientPollenand current clientev.energyboth have implementations.
ThisDoordash blogpostcovers a similar system that uses a single file for tracking latest migrations.
And there’s also a package calleddjango-migrations-git-conflictswhich works fairly similarly.