Skip to main content

Detect, diagnose, and auto-fix Django migration problems, circular dependencies, and merge conflicts.

Project description

django-migraid

Detect, diagnose, and auto-fix Django migration problems in Git workflows.

CI PyPI Python License


The Problem

Two developers branch from main at migration 0004. Both generate 0005_*.py. Main merges one. The second developer's rebase leaves them with a conflict that makemigrations --merge doesn't cleanly handle in rebase-based workflows. django-migraid fixes this — and ten other common migration pain points.

Installation

pip install django-migraid

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    "migraid",
]

Quick Start

# Diagnose all migration issues in your project
python manage.py migraid doctor

# Rebase your branch's migrations onto main
python manage.py migraid rebase --base main --dry-run

# Rebase AND keep django_migrations in sync for applied migrations (CI-friendly)
python manage.py migraid rebase --base main --update-db --noinput

# Fix conflicting leaf migrations
python manage.py migraid fix-conflicts --dry-run

# Fix out-of-order numbering
python manage.py migraid renumber myapp --dry-run

# Remove stale django_migrations rows
python manage.py migraid prune --yes

# Clean up untracked migration files after switching branches
python manage.py migraid sync-branch --dry-run

# Visualize the migration DAG
python manage.py migraid graph myapp --format mermaid

Problems This Solves

Issue Code Django Error / Search Term Command
Conflicting leaf migrations E001 multiple leaf nodes, migration merge conflict fix-conflicts
Circular migration dependencies E002 CircularDependencyError, circular dependency — (reports)
Out-of-order numbering E003 gap in numbering, renumber migrations renumber
Dependency on a deleted migration E004 NodeNotFoundError, missing dependency — (reports)
Renamed applied migration E005 InconsistentMigrationHistory, table desync rebase/renumber/fix-conflicts --update-db
Stale django_migrations rows W001 ghost migrations, remove from django_migrations prune
RunPython without reverse_code W002 unreversible data migration — (reports)
Squashed migration cleanup W003 remove old migrations after squash — (reports)
Merge migrations in rebase flow W004 delete django merge migrations rebase
Non-deterministic dependencies W005 random migration order — (reports)

Common Scenarios & How-To

How to fix a Django migration merge conflict?

When two developers create 0005_*.py on different branches, run:

python manage.py migraid fix-conflicts

This linearizes the migrations into 0005_... and 0006_... automatically.

How to rebase migrations onto another branch?

To renumber your local migrations to follow the latest from main:

python manage.py migraid rebase --base main

How to fix InconsistentMigrationHistory?

If you've renamed or renumbered migrations that are already applied, use the --update-db flag:

python manage.py migraid renumber myapp --update-db

This synchronizes the django_migrations table with your new file names.

How to find circular dependencies?

Run the diagnostic tool to identify cycles in your migration graph:

python manage.py migraid doctor

Command Reference

Commands fall into three groups:

Diagnose (read-only): doctor, graph

Rewrite migration files: rebase, fix-conflicts, linearize, renumber — file plane only by default; pass --update-db to also rename django_migrations rows.

Repair DB / branch state: prune, sync-branch — these commands exist to fix the database or file state directly.

doctor

Read-only diagnostic. Reports every detected issue with severity.

python manage.py migraid doctor [--app LABEL] [--format text|json]

rebase

Renumber local branch migrations to follow the latest from a target branch.

python manage.py migraid rebase [--base BRANCH] [--app LABEL] [--dry-run] [--yes] [--force] [--allow-applied] [--update-db] [--noinput] [--database ALIAS]

fix-conflicts

Resolve multiple-leaf conflicts by linearizing the fork.

python manage.py migraid fix-conflicts [--app LABEL] [--dry-run] [--yes] [--force] [--allow-applied] [--update-db] [--noinput] [--database ALIAS]

linearize

Rewrite history into a gap-free 0001..N chain where each migration depends on exactly one predecessor — renumbering, collapsing redundant dependency lists, resolving forks, and deleting merge migrations in one pass. Cross-app dependencies are preserved by default (--strip-cross-app to drop them).

python manage.py migraid linearize [--app LABEL] [--strip-cross-app] [--dry-run] [--yes] [--force] [--allow-applied] [--update-db] [--noinput] [--database ALIAS]

renumber

Fix gap or duplicate numbering in a single app's migrations.

python manage.py migraid renumber <app> [--dry-run] [--yes] [--force] [--allow-applied] [--update-db] [--noinput] [--database ALIAS]

prune

Remove orphaned django_migrations rows for migrations no longer on disk.

python manage.py migraid prune [--dry-run] [--yes] [--noinput] [--database ALIAS] [--allow-remote-db]

sync-branch

Align local migration files (and optionally the database) to the current git branch state. Detects untracked migration files and optionally removes stale django_migrations rows or reverses applied schema changes.

python manage.py migraid sync-branch [--app LABEL] [--dry-run] [--yes] [--noinput] [--database ALIAS] [--update-db] [--schema]

graph

Print or export the migration DAG.

python manage.py migraid graph [app] [--format mermaid|dot|ascii] [--output FILE]

Safety Model

Commands act on the file plane by default. Any DB change requires an explicit flag.

  • --update-db authorizes renaming django_migrations rows in step with file renames (rewrite commands) or deleting stale rows (sync-branch).
  • --schema authorizes running migrate backwards to reverse schema changes (sync-branch only).
  • prune and sync-branch are inherently DB/file-repair commands — running them is the authorization, and they always preview + confirm before writing.

Every mutation command also:

  1. Checks for uncommitted git changes (bypass with --force on rewrite commands)
  2. Guards against rewriting already-applied migrations (bypass with --allow-applied, or use --update-db which implies it)
  3. Creates a migraid-backup-<timestamp> git ref before any writes
  4. Shows a diff-style preview before making changes
  5. Asks for confirmation (bypass with --yes / --noinput)
  6. Maintains an undo log — reverses all file ops automatically if anything fails
  7. Self-validates after apply: if the migration graph gets worse, auto-reverts

--dry-run on any mutation command prints the full preview without writing.

When renaming applied migrations, --update-db renames the matching django_migrations rows in the same per-app transaction.atomic() block as the file changes (preserving the applied timestamp), writes a replayable inverse-SQL undo script, and rolls back both files and rows on any failure. See the --update-db guide.

CI Integration

Add to your pre-push hook or CI pipeline:

python manage.py migraid doctor --format json | jq '.[] | select(.severity == "error")'

Or fail CI on any ERROR-level issue:

python manage.py migraid doctor

(exits non-zero if any E0xx issues are found)

Contributing

See CONTRIBUTING.md.

License

Apache 2.0 — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_migraid-0.3.0b2.tar.gz (75.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_migraid-0.3.0b2-py3-none-any.whl (46.3 kB view details)

Uploaded Python 3

File details

Details for the file django_migraid-0.3.0b2.tar.gz.

File metadata

  • Download URL: django_migraid-0.3.0b2.tar.gz
  • Upload date:
  • Size: 75.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_migraid-0.3.0b2.tar.gz
Algorithm Hash digest
SHA256 1bab48b72b7ade068306d560b99f6a005e1621a27d991bc9d6dade12cf3f7e02
MD5 4ac9ec2b9f850f96c2c32363c50a1365
BLAKE2b-256 e9a7dc9f33ae7686df3e414d0d5c3581e99c1be082d960586955fcd597911a78

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_migraid-0.3.0b2.tar.gz:

Publisher: release.yml on AhmedShehab/django-migraid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_migraid-0.3.0b2-py3-none-any.whl.

File metadata

File hashes

Hashes for django_migraid-0.3.0b2-py3-none-any.whl
Algorithm Hash digest
SHA256 44d87e9e44289c5625d034a519a4740c01da50dbf0d64b0408d45c5d14dc99aa
MD5 e0239e2ca693ddf5c2ee2f32a4b3b158
BLAKE2b-256 360f43f94aafbd362fb8993c7a701574b412e9856f1ad3ef6b54d573368abc73

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_migraid-0.3.0b2-py3-none-any.whl:

Publisher: release.yml on AhmedShehab/django-migraid

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page