Django Fork Maintenance

The mongodb-forks/django repository is a long-lived organisation fork of django/django. Unlike a personal contributor fork, it carries several MongoDB-specific branches that each track a different upstream Django release branch, and it needs to be re-synced periodically as new Django patch releases land. This page describes how dbx is configured to manage that fork and the day-to-day maintenance workflow.

How the fork is configured

The Django fork lives in the django group and is set up entirely through config — no --fork-user flag is needed, because the fork is owned by an organisation rather than a personal account:

[repo.groups.django]
repos = [
    "git@github.com:mongodb-forks/django.git",
    "git@github.com:mongodb-labs/django-mongodb-backend.git",
    "git@github.com:mongodb-labs/django-mongodb-extensions.git",
    "git@github.com:mongodb-labs/django-mongodb-project.git",
]

# Clone mongodb-forks/django directly even when --fork is active
no_fork = ["django"]

# Auto-add an `upstream` remote pointing at the canonical Django repo
[repo.groups.django.upstream]
django = "git@github.com:django/django.git"

# Map each local (fork) branch to the upstream branch it tracks.
# Only branches whose upstream stable/<version>.x still exists are listed.
[repo.groups.django.upstream_branch]
django = {"mongodb-6.1.x" = "stable/6.1.x", "mongodb-6.0.x" = "stable/6.0.x", "mongodb-5.2.x" = "stable/5.2.x"}

# Branch checked out automatically right after cloning
[repo.groups.django.preferred_branch]
django = "mongodb-6.0.x"

Each key plays a specific role in fork maintenance:

no_fork

Lists repos that should be cloned verbatim even when --fork is active. Because mongodb-forks/django is already a fork, it must not have the personal fork_user substituted into its URL.

upstream

Maps the repo to the URL of the canonical upstream (django/django). On clone, dbx adds this as an upstream remote automatically, so no --fork-user flag is required. See Config-Driven Upstream Remotes.

upstream_branch

Maps each local fork branch to the upstream branch it rebases against. The MongoDB branch names (mongodb-6.0.x) differ from the upstream Django branch names (stable/6.0.x), so this mapping tells dbx sync which target to rebase onto based on the branch currently checked out. The dict form is what makes multi-branch maintenance work — each branch resolves to its own upstream target.

preferred_branch

The branch dbx clone switches to automatically after cloning.

Cloning the fork

Clone the whole group (upstream remote and preferred branch are configured automatically):

dbx clone -g django

# Verify the remotes were set up
cd ~/Developer/mongodb/django/django
git remote -v
# origin    git@github.com:mongodb-forks/django.git (fetch)
# upstream  git@github.com:django/django.git (fetch)

To clone only the Django fork itself:

dbx clone django

Syncing a branch with upstream

dbx sync fetches from upstream, rebases the current branch onto its mapped upstream branch, and pushes the result back to origin. Because the upstream target is resolved from upstream_branch based on the branch that is checked out, maintenance is simply “check out a branch, sync it”:

cd ~/Developer/mongodb/django/django

# Bring mongodb-6.0.x up to date with the latest Django stable/6.0.x
git switch mongodb-6.0.x
dbx sync django
# Fetches upstream, rebases mongodb-6.0.x onto upstream/stable/6.0.x, pushes to origin

# Repeat for the next release branch
git switch mongodb-5.2.x
dbx sync django
# Rebases mongodb-5.2.x onto upstream/stable/5.2.x, pushes to origin

Always preview first with --dry-run — it shows the commits that would be applied from upstream and the commits that would be rebased on top:

dbx sync django --dry-run

If a branch has already been pushed and rebased, the follow-up push may be rejected; re-run with --force (which uses --force-with-lease for safety):

dbx sync django --force

Refreshing every release branch

When a new round of Django patch releases lands upstream, every MongoDB branch needs to be rebased. The --all-branches (-b) flag walks the repo’s upstream_branch mapping for you — it checks out each mapped branch in turn, rebases it onto its configured upstream target, force-pushes, and restores the branch you started on. Because rebasing rewrites history, --all-branches force-pushes by default (using the safe --force-with-lease); you do not need to pass --force:

# Preview what would change on every mapped branch
dbx sync django --all-branches --dry-run

# Rebase and push every mapped branch
dbx sync django --all-branches
dbx sync django -b              # short form

This is equivalent to switching to mongodb-6.1.x, mongodb-6.0.x, mongodb-5.2.x, … in turn and running dbx sync django on each. The working tree must be clean for a real sync (each branch is checked out via git switch), and only branches present in the upstream_branch mapping are synced.

Combined with --dry-run, --all-branches previews every mapped branch without checking any of them out: it fetches upstream once and compares each branch’s origin ref against its configured upstream target directly. Because nothing is checked out, the preview works even when the working tree is dirty and never disturbs the branch you have open.

If a branch fails to rebase (for example, conflicts, or an upstream target that no longer exists), that branch’s rebase is aborted so the working tree stays clean and the remaining branches are still processed — one bad branch does not leave a rebase in progress that blocks the rest. At the end, the failed branches are listed so you can rebase them manually:

⚠️  Rebase these branch(es) manually: mongodb-6.0.x

# then, for each listed branch:
cd ~/Developer/mongodb/django/django
git switch mongodb-6.0.x
git rebase upstream/stable/6.0.x   # resolve conflicts, then git rebase --continue

Once resolved, re-run dbx sync django --all-branches (already-synced branches are fast no-ops) to push the remaining branches.

Re-running downstream CI

Because the backend’s PR workflows check out the fork branch at a pinned ref: at CI runtime (see How the fork branches are tested in backend PRs), force-pushing a rebased fork branch does not re-trigger those workflows — so a rebase that breaks the adapted tests would go unnoticed until the next push to the PR. To close that gap, after a successful --all-branches sync dbx sync re-runs the backend CI for each branch that rebased, via the ci_rerun mapping.

The mapping is keyed per fork branch (only branches that actually rebased are processed — a branch that failed or was skipped triggers nothing). Each value maps an owner/name GitHub repo to a target that is either:

  • an integer PR number — re-runs the workflow runs attached to that PR (needs an open PR with a prior run; updates the PR’s own status checks), or

  • a string git ref — dispatches the repo’s test-python* workflows on that backend branch via workflow_dispatch (no PR needed). Each backend branch pins the fork branch it tests via ref:, so the backend ref selects which fork branch is exercised — e.g. the backend’s main pins mongodb-6.0.x. Only test-python* workflows that actually declare a workflow_dispatch trigger are dispatched; any that don’t (push/schedule/pull_request only) are reported as skipped (no workflow_dispatch trigger) instead of failing.

[repo.groups.django.ci_rerun.django]
"mongodb-6.0.x" = {"mongodb/django-mongodb-backend" = "main"}   # dispatch, no PR
"mongodb-6.1.x" = {"mongodb/django-mongodb-backend" = 422}      # re-run PR #422
"mongodb-5.2.x" = {"mongodb/django-mongodb-backend" = 562}
"mongodb-6.2.x" = {"mongodb/django-mongodb-backend" = 535}
✨ Done! Synced 3 branch(es)

♻️  mongodb-6.0.x → dispatching CI on mongodb/django-mongodb-backend@main...
   test-python.yml ✓ queued
   test-python-geo.yml ✓ queued
   test-python-replica.yml — skipped (no workflow_dispatch trigger)
♻️  mongodb-6.1.x → re-running CI on mongodb/django-mongodb-backend#422...
   #422 ✓ queued (4 workflow run(s))

This uses the gh CLI (GitHub CLI), so gh must be installed and authenticated. It is best-effort: a missing gh, an unconfigured ci_rerun mapping, or a GitHub API error is reported as a warning and never fails the sync. Pass --no-ci to skip the re-run, and note it is skipped automatically for --dry-run and when no branch actually synced.

Note

The two mechanisms report results in different places. A PR re-run updates the PR’s own status checks, so the result shows up on the PR. A ``workflow_dispatch`` run is standalone — it appears under the repo’s Actions tab, not as a status check on any PR — so after a dispatch you check the run there rather than on a PR.

Choosing between the two: use a PR number for branches that have an open PR you want to keep green/red (the run attaches to that PR); use a ref to validate a rebase with no PR involved, or when no suitable PR run exists yet (for example the backend’s main, which pins mongodb-6.0.x).

Adding a new release branch

When Django cuts a new stable branch (for example stable/6.2.x) and you create a matching mongodb-6.2.x fork branch, add the mapping to upstream_branch so dbx sync knows where to rebase it:

[repo.groups.django.upstream_branch]
django = {"mongodb-6.2.x" = "stable/6.2.x", "mongodb-6.1.x" = "stable/6.1.x", ...}

Until the mapping exists, dbx sync cannot determine the correct upstream target for that branch and falls back to upstream’s default-branch detection, which is almost certainly not what you want for a release branch.

Only map branches whose upstream target actually exists. A branch pointing at a stable/<version>.x that has not been branched yet (a Django version still in development on main) or one that has been deleted upstream (an end-of-life release) will fail to rebase under dbx sync. When a Django release reaches end of life and its stable branch is removed, drop the corresponding entry from the mapping.

Running the Django test suite

The Django fork uses its own test runner rather than pytest. This is wired up through test_runner / test_runner_args in the group config:

[repo.groups.django.test_runner]
django = "tests/runtests.py"

[repo.groups.django.test_runner_args]
django = ["-v", "2"]

so dbx test django invokes tests/runtests.py with the configured arguments. See Testing for details on how custom test runners are resolved and how to pass additional arguments.

How the fork branches are tested in backend PRs

Keeping the fork branches rebased matters because they are the code under test in every django-mongodb-backend pull request. The backend’s PR CI does not test against upstream Django — it checks out the corresponding branch of the mongodb-forks/django fork and runs Django’s own test suite against MongoDB, using django-mongodb-backend as the database engine. See the open PRs at https://github.com/mongodb/django-mongodb-backend/pulls.

Each PR triggers several GitHub Actions test workflows (.github/workflows/test-python*.yml in the backend repo), covering the core suite plus the geo, Atlas, and encryption feature sets. Those workflows also declare workflow_dispatch, so they can be run manually (with no PR) on any backend branch — this is what the ci_rerun “ref” form uses (see Re-running downstream CI). They all follow the same shape:

  1. Check out django-mongodb-backend and install it (pip install -e .).

  2. Check out mongodb-forks/django at the fork branch for the Django release under test (e.g. ref: mongodb-6.0.x) into a django_repo/ directory, then install it and Django’s test requirements.

  3. Copy the backend’s settings files (mongodb_settings.py, the encryption settings, etc.) and its runtests.py into django_repo/tests/.

  4. Start MongoDB and run Django’s suite via python3 django_repo/tests/runtests_.py.

In other words, the fork branch supplies the (lightly adapted) Django test suite, and the backend supplies the database engine and settings. The ref: in those workflows pins the exact fork branch, so:

  • A rebased fork branch is what the backend PRs actually exercise — if a rebase breaks the adapted tests, it surfaces as failures on backend PRs, not on the fork itself.

  • When the backend adds support for a new Django feature release, the fork needs a matching mongodb-<version>.x branch (see Adding a new release branch) and the workflows’ ref: is bumped to it.

  • Because each backend branch’s workflow pins its own fork ref:, dispatching a workflow on a given backend ref exercises the fork branch that ref pins — the basis for the ci_rerun “ref” form (e.g. the backend’s main pins mongodb-6.0.x).

To reproduce a backend PR run locally against a fork branch you have rebased, mirror those steps: check out the fork branch in your django group clone, install it alongside django-mongodb-backend, copy the backend’s settings / runtests.py into tests/, and run the suite against a local MongoDB (see MongoDB Integration).