skip to content

Conflicts, Merges & Squashing

Two feature branches each adding a migration leave an app with two leaf nodes; makemigrations --merge joins them and squashmigrations folds history. Interviewers ask when old files can go.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

After merging two branches that each added an orders migration 0042, why does Django's migrate report multiple leaf nodes, and how do you fix it?

level: juniorimportance: must knowfreq 48%

answer

  1. numbers are for people
  2. both files depend on 0041
  3. two ends of one graph
  4. a migration that depends on both

basics

~20 s

Both 0042 files depend on 0041, so the orders app's migration graph ends in two leaf nodes and Django refuses to guess their order. Run makemigrations --merge to add a migration depending on both, after checking they touch different fields.

solid answer

~40 s

Django orders migrations by their `dependencies`, not by the number in the file name; the number only helps humans. Branch A added `0042_order_gift_note` and branch B added `0042_order_coupon_code`, both depending on `0041`, so after the git merge the `orders` app has two **leaf nodes** and neither is ordered after the other. `migrate` and `makemigrations` both stop with `CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph` and suggest `python manage.py makemigrations --merge`. That command prints each branch's operations, asks for confirmation, and writes `0043_merge_...` with no operations and a dependency on both 0042 files, giving the graph a single leaf again. It is only safe when the two branches change different fields or models.

code

bash · 12 lines
bash
$ python manage.py migrate
CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0042_order_coupon_code, 0042_order_gift_note in orders).
To fix them run 'python manage.py makemigrations --merge'

$ python manage.py makemigrations --merge
Merging orders
  Branch 0042_order_coupon_code
    + Add field coupon_code to order
  Branch 0042_order_gift_note
    + Add field gift_note to order
...
Created new merge migration orders/migrations/0043_merge_0042_order_coupon_code_0042_order_gift_note.py

go deeper

for a junior

Recall that migrations are ordered by dependencies, that two migrations depending on the same parent create two leaf nodes, and that makemigrations --merge fixes it.

for a middle

Explain what the merge file contains, why it is enough to rejoin the graph, and what the merge prompt is warning you about.

for a senior

Show how you prevent forks reaching main with a CI check and when you inspect the branches' operations before accepting a merge.

for a principal

Decide the team's convention for parallel migration work so forks are rare and their resolution is reviewed, not reflexive.

## Migrations form a graph, not a list Each Django migration file declares `dependencies`, a list of `(app_label, migration_name)` pairs that must be applied first. Django builds a **migration graph** from those declarations for every app. The four-digit prefix in a file name such as `0042_order_gift_note.py` is only a convenience for developers; the documentation says outright that Django "just cares that each migration has a different name". A **leaf node** is a migration that no other migration in the same app depends on: the current end of that app's history. On a healthy project each app has exactly one. ## How two leaves appear 1. `main` has `orders` migrations up to `0041_order_status_index`. 2. Developer A branches and adds `gift_note` to `Order`; `makemigrations` writes `0042_order_gift_note`, depending on `0041`. 3. Developer B branches from the same commit and adds `coupon_code`; `makemigrations` writes `0042_order_coupon_code`, also depending on `0041`. 4. Both pull requests merge. Git sees two different file names, so there is no textual conflict at all. 5. The `orders` graph now forks after `0041` and has two leaves. | State | Leaves in `orders` | `migrate` result | |---|---|---| | Before the merge | `0041_order_status_index` | runs normally | | After both branches land | `0042_order_coupon_code`, `0042_order_gift_note` | `CommandError`, nothing applied | | After `makemigrations --merge` | `0043_merge_...` | runs both branches, then the merge | ## The error and the fix Before doing anything else, both `migrate` and `makemigrations` look for apps with more than one leaf. If they find one, they stop with: `Conflicting migrations detected; multiple leaf nodes in the migration graph: (0042_order_coupon_code, 0042_order_gift_note in orders). To fix them run 'python manage.py makemigrations --merge'` Running `makemigrations --merge`: - Finds the common ancestor (`0041`) and lists the operations on each branch. - Warns that merging only works if those operations do not conflict, meaning they work on different fields or models, and asks `[y/N]`; with `--noinput` it merges without asking. - Writes a new migration numbered one above the highest conflicting number, named `0043_merge_<leaf names>` (or with a timestamp when the combined names are long, or with your own `--name`). - The file has **no operations**; its only content is `dependencies` on both 0042 migrations. ## Why the merge file is enough A migration with two dependencies joins the fork: Django must apply both branches before it, and the app again has one leaf. Databases that already applied one branch simply apply the other and then record the merge. Because the merge carries no operations, it costs nothing at deploy time. ## Catching it before deploy - Run `makemigrations --check` in CI; conflict detection happens before the check, so a forked graph fails the build. - Review the printed operations before answering `y`: if both branches altered the same field, a merge hides a real conflict. - Keep merge files small and committed with the change that caused them, so reviewers see why the graph forked.

  • Why did git not flag a conflict when both branches added migration 0042?
    The files have different names, `0042_order_coupon_code.py` and `0042_order_gift_note.py`, so git merges them cleanly. The conflict exists only in Django's dependency graph, which is why Django, not git, reports it.
  • How would you stop forked migration graphs from reaching the main branch?
    Run `python manage.py makemigrations --check` in CI on the merged result. Conflict detection runs before the missing-migration check, so two leaves fail the job, and the author adds a merge migration or rebases their migration before it lands.

saying these in an interview costs you the question

  • Django applies migrations in order of their number prefix
  • Two 0042 files cause a git conflict that must be resolved by hand
  • The merge migration copies both branches' operations into one file
  • migrate applies both branches anyway and only warns
  • Two leaf nodes mean one of the migrations is corrupt
open as a page

When is committing Django's makemigrations --merge output unsafe, and what does the merge migration not do for you?

level: middleimportance: should knowfreq 32%

basics

~20 s

A merge migration only joins the graph; it reconciles nothing. If both branches change the same field or model, databases can apply them in different orders and end with different schemas, so edit the migrations or add an explicit fix-up instead.

open as a page

In a Django team, when would you renumber one of two conflicting 0042 migrations onto the other instead of committing a merge migration?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Renumber, making one migration 0043 that depends on the other 0042, when it has only run on disposable local databases; history stays linear. Once both ran on shared databases, merge instead: a renamed file looks unapplied and Django would run it again.

open as a page

How does Django's squashmigrations work, and when is it safe to delete the migration files a squashed migration replaces?

level: seniorimportance: should knowfreq 40%

basics

~20 s

squashmigrations concatenates an app's migrations, optimizes the operations and writes one migration whose replaces lists the originals. Keep the old files until every database has applied them all; then delete them, repoint dependencies and remove replaces.

open as a page

When Django's squashmigrations meets RunPython operations, why does it report manual porting, and how does elidable=True change the squashed result?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

The squashed file references RunPython functions defined in the old numbered migration modules, which you will delete, so you copy them in by hand. Non-elidable RunPython and RunSQL also block the optimizer; elidable ones may be dropped.

open as a page