alembic-git-revisions
Automatic Alembic migration chaining based on git commit history. No more Multiple head revisions are present for given argument 'head'.
The problem
You merged two branches and Alembic now refuses to run:
ERROR [alembic.util.messaging] Multiple head revisions are present for given argument 'head'; please specify a specific target revision, '<branchname>@head' to narrow to a specific head, or 'heads' for all heads
When multiple developers create Alembic migrations on separate branches, they often end up with the same down_revision — the current head at the time each branch was created. When these branches merge, Alembic fails with this MultipleHeads error because two migrations point to the same predecessor.
The usual fix is manual: rebase, update down_revision, and hope nobody else merges in the meantime.
How it works
Instead of hardcoding down_revision, this library determines the migration chain automatically from git history. It uses git log --reverse --diff-filter=A to find the order in which migration files were first committed, then chains them linearly after the last "static" (hardcoded) migration.
This means:
- New migrations never conflict with each other
- The chain is always linear, regardless of branch merge order
- Existing migrations with hardcoded
down_revisioncontinue to work
Installation
pip install alembic-git-revisions
Setup
Copy the provided template to your Alembic script.py.mako:
"""${message}
Revision ID: ${up_revision}
Create Date: ${create_date}
"""
from alembic import op
from alembic_git_revisions import get_down_revision
import sqlalchemy
${imports if imports else ""}
revision = ${repr(up_revision)}
down_revision = get_down_revision(revision)
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}
A reference template is included in the package at alembic_git_revisions/templates/script.py.mako.
That's it. New migrations generated with alembic revision --autogenerate will automatically chain themselves using git history.
Environments without git (Docker, CI)
In Docker images or CI environments where git history isn't available, pre-generate a revision_chain.json file before building:
# Using the CLI
alembic-git-revisions /path/to/alembic/versions
# Or as a Python module
python -m alembic_git_revisions /path/to/alembic/versions
This writes revision_chain.json next to the versions/ directory. The library uses this file automatically when it exists, falling back to git when it doesn't.
Important: The git clone must have full history (git clone or actions/checkout with fetch-depth: 0). Shallow clones produce incorrect ordering.
Add revision_chain.json to your .gitignore — it should only exist in built artifacts.
How migrations are classified
The library handles three types of migrations:
- Dynamic — uses
get_down_revision(), chained automatically by git history - Static — has a hardcoded
down_revision, managed manually (legacy migrations) - Hybrid — a static migration whose
down_revisionpoints to a dynamic one; participates in the dynamic ordering so the chain stays linear
Classification reads the revision and down_revision attributes from each migration module (the same values Alembic loads), so any Alembic file_template and any rev_id format work.
API
get_down_revision(revision, versions_dir=None)
Returns the down_revision for the given revision ID. Auto-discovers the versions directory from the calling migration file's location. Pass versions_dir explicitly for non-standard setups or tests.
generate_chain_file(versions_dir)
Generates revision_chain.json from git history. Run this before building Docker images.
build_chain(versions_dir)
Returns the full {revision: down_revision} dict. Cached per versions_dir. Use build_chain.cache_clear() to reset in tests.
parse_versions_dir(versions_dir)
Returns the migrations in versions_dir as a list of MigrationFile, so tooling can inspect classification and ordering without building a chain.
The order is the raw order files were added to git. It is not the order the chain walks: build_chain re-parents a hybrid to sit immediately after the revision it hardcodes, which can move it far from its own add position. Use build_chain when you want traversal order.
Unlike build_chain, this never falls back to revision_chain.json, because that file records only {revision: down_revision} and carries neither classification nor ordering. Git is required, and its absence raises RuntimeError rather than returning a plausible wrong order. Results are not cached.
MigrationFile
A frozen dataclass describing one parsed migration:
| Field | Meaning |
|---|---|
revision |
the revision id, read from the module's revision attribute |
filename |
the file's basename |
git_sequence |
position within the parse that produced it (see below) |
is_dynamic |
whether down_revision calls get_down_revision() |
static_down_revisions |
hardcoded parents; more than one means a merge migration |
git_sequence is a position within one particular parse, not a stable property of the file. Files absent from git history all share the same end-of-list sentinel and are separated only by filename, which is why parse_versions_dir sorts on both.
CHAIN_FILENAME
Name of the generated chain file, revision_chain.json. Use it instead of hardcoding the string when locating or cleaning up the generated artifact.
License
Apache-2.0
Metadata
Release files for alembic-git-revisions 13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| alembic_git_revisions-13.tar.gz | 45.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| alembic_git_revisions-13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.6 kB
Release files / alembic_git_revisions-13.tar.gz
| Download URL | alembic_git_revisions-13.tar.gz |
|---|---|
| Size | 45.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f90b99c871711632175e6533fd664ea8c0fe45a432d492f5970268d6ba5c5fc8
|
|
BLAKE2b-256 checksum How to use checksums |
784b2b29098aeebaae3f35e9f9a9c3d546ce7f2805cf821d3d8e563749443690
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / alembic_git_revisions-13-py3-none-any.whl
| Download URL | alembic_git_revisions-13-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
73916abd87727398bd3db44805dd3bf24b8a7b556439a88983c4b043f5c8d740
|
|
BLAKE2b-256 checksum How to use checksums |
523572d4cd1e56b9fde6cd0d38e680684fad908f4ad5a4b4d4ca22cd70074336
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log