Major-Version Branch Strategy
Goal: Keep branch naming and default-branch setup consistent across all module repos during a major-version upgrade cycle, so old versions stay maintainable while new work proceeds on a clearly-named active branch.
The convention applies regardless of ecosystem (Composer/PHP, npm/Node, pip/Python, etc.). Replace "recipe version" with your ecosystem's concept of a shared dependency version.
Branch Naming
- Integer version branches are the default branch:
3, 4, 5, 6, 7.
- The branch integer matches the targeted framework/ecosystem major version — or the ecosystem's shared package/recipe version if repos version against that rather than their own semver. A module targeting v6 of a framework uses branch
6; a module versioned against a shared recipe on 3 targeting the same framework major uses branch 3. Follow whichever the repo already versions against.
master is deprecated as a default branch. It may remain for history, but it is not the active dev target. If master currently holds the latest content, migrate that content to the correct numbered branch and leave master in place.
- Feature branches branch off the active version branch — e.g.
fix/accordion-migration or feature/v6-upgrade off 6.
Default-Branch Rules
| Target version |
Branch name |
State |
v6 |
6 (or ecosystem version, e.g. 3) |
Active development |
v5 |
5 (or ecosystem version, e.g. 2) |
Maintenance — backports only |
v4 |
4 (or ecosystem version, e.g. 1) |
Frozen / EOL |
Preserve the Previous Major
- When a new major version ships, create its numbered branch from the current default (e.g. branch
6 from 5).
- The previous version branch stays in the repo for backport fixes (e.g.
5 remains after 6 becomes default).
- Do not create long-lived
N.x branches for versions that are no longer supported. One integer branch per maintained major is enough.
Forking 3rd-Party Modules for New Version Support
When an upstream module lacks support for the target framework/ecosystem version:
- Fork the upstream repo into your org, keeping the original package prefix.
- Create a
feature/<target>-upgrade branch with the dependency bumps.
- Push and open a PR upstream.
- Add a dependency override to the root project's manifest — e.g. Composer
repositories, npm overrides, or equivalent — to point to the fork. (These overrides are not inherited transitively from shared recipes/configs; they must live in the root project.)
- The override makes the fork replace the upstream package. If/when the upstream merges and tags a release, switch back to the registry and drop the override entry.
Auto-Merge on Integer Default Branches
Auto-merge (gh pr merge --auto, the GitHub "Enable auto-merge" button) is not gated on
the branch being named master/main. It works against any default branch — 6, 5, 3 —
provided the repo is configured for it. If auto-merge "doesn't work," the cause is almost
always a missing repo setting, not the branch name. Do not switch the default branch to
master to make auto-merge work.
Prerequisites (per repo):
- Enable "Allow auto-merge" in repo settings — the most common missing piece:
gh api -X PATCH repos/<owner>/<repo> -F allow_auto_merge=true
- Branch protection on the integer default branch with at least one requirement (a
required status check or required review). Without a protection rule,
--auto errors and
you must use a plain gh pr merge --squash instead. The merge-pr skill already resolves
the default branch dynamically, so plain squash-merge works against 6/5/3 today.
Applying the Strategy to a Repo
- Identify the versioning basis — does this repo version against a shared recipe/manifest number or its own semver? Check existing branch names and the package manifest.
- Migrate
master if it holds active content. If master is the current default and already carries the latest content, create the numbered branch from master so no history is lost, then leave master in place for history. Otherwise, create the new numbered branch from the current default for the new major.
- Set it as the default branch in GitHub settings; keep the previous version branch for backports.
- Update CI configs (GitHub Actions or equivalent) to target the new default branch.
- Update the package manifest:
- Branch aliases — e.g. Composer
"extra": { "branch-alias": { "dev-6": "6.x-dev" } } or npm "version": "6.0.0-dev".
- Dependency constraints use a dev/pre-release specifier for the new major during the upgrade (e.g.
^6@dev in Composer, ^6.0.0-alpha in npm semver).
- Declare dependency overrides in the root project manifest for any 3rd-party modules being upgraded via a fork.
Checklist
Example: Silverstripe CMS + Dynamic Essentials
The following illustrates the convention for the Dynamic Essentials Silverstripe ecosystem,
where module repos version against the recipe number rather than the CMS version.
Reference mapping — Essentials product line
Recipe/module repos version against the recipe number, not the CMS number:
| Module |
SS6 Branch |
SS5 Branch |
Notes |
| recipe-silverstripe-essentials-website |
3 (default) |
2 |
Recipe version, not CMS |
| silverstripe-essentials-tools |
3 (default) |
2 |
|
| silverstripe-elemental-accordion |
6 (default) |
5 |
Module's own major |
| silverstripe-elemental-embedded-code |
4 |
master |
|
| silverstripe-elemental-sponsors |
5 (default) |
master |
|
| silverstripe-elemental-templates |
3 (default) |
2 |
|
Example repo: dynamic/silverstripe-elemental-accordion
| Branch |
CMS Version |
Status |
5 |
SS5 |
Maintenance (backports only) |
6 |
SS6 |
Active development (default) |
Silverstripe fork workflow
- Fork the upstream repo into the
dynamic/ GitHub org, keeping the silverstripe- prefix.
- Create a
feature/ss6-upgrade branch with the dependency bumps.
- Push and open a PR upstream.
- Add a VCS repo entry for the fork to the root project
composer.json (VCS repos are not inherited transitively from recipes — they must live in the root project).
- Composer's VCS-repo priority makes the fork replace the upstream package. When the upstream merges and tags a release, switch back to Packagist and drop the VCS entry.
For Composer specifically, the manifest steps are:
- Branch aliases — e.g.
"extra": { "branch-alias": { "dev-master": "6.x-dev" } } (or alias the numbered branch, e.g. "dev-6": "6.x-dev").
- Recipe constraints use
^6@dev for SS6 branches to allow dev stability during the upgrade.
1---2name: ss-branch-strategy3description: Establish consistent branch-naming and default-branch conventions across all module repos when executing a major framework or CMS version upgrade. Covers integer branch naming, default-branch promotion, previous-version preservation, and the fork-and-upstream workflow for third-party modules. Use when planning or executing a major version upgrade across a suite of repos.4---56# Major-Version Branch Strategy78**Goal:** Keep branch naming and default-branch setup consistent across all module repos during a major-version upgrade cycle, so old versions stay maintainable while new work proceeds on a clearly-named active branch.910The convention applies regardless of ecosystem (Composer/PHP, npm/Node, pip/Python, etc.). Replace "recipe version" with your ecosystem's concept of a shared dependency version.1112---1314## Branch Naming1516- **Integer version branches are the default branch:** `3`, `4`, `5`, `6`, `7`.17- The branch integer matches the **targeted framework/ecosystem major version** — or the ecosystem's shared package/recipe version if repos version against that rather than their own semver. A module targeting v6 of a framework uses branch `6`; a module versioned against a shared recipe on `3` targeting the same framework major uses branch `3`. Follow whichever the repo already versions against.18- **`master` is deprecated as a default branch.** It may remain for history, but it is not the active dev target. If `master` currently holds the latest content, migrate that content to the correct numbered branch and leave `master` in place.19- **Feature branches** branch off the active version branch — e.g. `fix/accordion-migration` or `feature/v6-upgrade` off `6`.2021## Default-Branch Rules2223| Target version | Branch name | State |24|---|---|---|25| `v6` | `6` (or ecosystem version, e.g. `3`) | Active development |26| `v5` | `5` (or ecosystem version, e.g. `2`) | Maintenance — backports only |27| `v4` | `4` (or ecosystem version, e.g. `1`) | Frozen / EOL |2829## Preserve the Previous Major3031- When a new major version ships, create its numbered branch from the current default (e.g. branch `6` from `5`).32- The **previous version branch** stays in the repo for backport fixes (e.g. `5` remains after `6` becomes default).33- **Do not** create long-lived `N.x` branches for versions that are no longer supported. One integer branch per maintained major is enough.3435---3637## Forking 3rd-Party Modules for New Version Support3839When an upstream module lacks support for the target framework/ecosystem version:40411. **Fork** the upstream repo into your org, keeping the original package prefix.422. Create a **`feature/<target>-upgrade`** branch with the dependency bumps.433. Push and **open a PR upstream**.444. **Add a dependency override** to the root project's manifest — e.g. Composer `repositories`, npm `overrides`, or equivalent — to point to the fork. (These overrides are not inherited transitively from shared recipes/configs; they must live in the root project.)455. The override makes the fork replace the upstream package. If/when the upstream merges and tags a release, switch back to the registry and drop the override entry.4647---4849## Auto-Merge on Integer Default Branches5051Auto-merge (`gh pr merge --auto`, the GitHub "Enable auto-merge" button) is **not** gated on52the branch being named `master`/`main`. It works against any default branch — `6`, `5`, `3` —53provided the repo is configured for it. If auto-merge "doesn't work," the cause is almost54always a missing repo setting, not the branch name. Do **not** switch the default branch to55`master` to make auto-merge work.5657Prerequisites (per repo):58591. **Enable "Allow auto-merge"** in repo settings — the most common missing piece:60 `gh api -X PATCH repos/<owner>/<repo> -F allow_auto_merge=true`612. **Branch protection on the integer default branch** with at least one requirement (a62 required status check or required review). Without a protection rule, `--auto` errors and63 you must use a plain `gh pr merge --squash` instead. The `merge-pr` skill already resolves64 the default branch dynamically, so plain squash-merge works against `6`/`5`/`3` today.6566---6768## Applying the Strategy to a Repo69701. **Identify the versioning basis** — does this repo version against a shared recipe/manifest number or its own semver? Check existing branch names and the package manifest.712. **Migrate `master` if it holds active content.** If `master` is the current default and already carries the latest content, create the numbered branch *from* `master` so no history is lost, then leave `master` in place for history. Otherwise, create the new numbered branch from the current default for the new major.723. **Set it as the default branch** in GitHub settings; keep the previous version branch for backports.734. **Update CI configs** (GitHub Actions or equivalent) to target the new default branch.745. **Update the package manifest:**75 - Branch aliases — e.g. Composer `"extra": { "branch-alias": { "dev-6": "6.x-dev" } }` or npm `"version": "6.0.0-dev"`.76 - Dependency constraints use a dev/pre-release specifier for the new major during the upgrade (e.g. `^6@dev` in Composer, `^6.0.0-alpha` in npm semver).776. **Declare dependency overrides** in the root project manifest for any 3rd-party modules being upgraded via a fork.7879---8081## Checklist8283- [ ] Default branch is an integer matching the ecosystem/recipe major (not `master`, not the module's own semver)84- [ ] Any active content on `master` migrated to a numbered branch; `master` left in place for history85- [ ] Previous major's branch retained for backports86- [ ] No stray long-lived `N.x` branches for unsupported versions87- [ ] CI configs point at the new default branch88- [ ] Package manifest branch-alias and constraints updated for dev stability on the new major89- [ ] Fork dependency overrides declared in the **root** project manifest, not a shared recipe90- [ ] `allow_auto_merge` enabled on the repo (auto-merge is branch-name-agnostic — never switch to `master` for it)9192---9394## Example: Silverstripe CMS + Dynamic Essentials9596The following illustrates the convention for the Dynamic Essentials Silverstripe ecosystem,97where module repos version against the recipe number rather than the CMS version.9899### Reference mapping — Essentials product line100101Recipe/module repos version against the **recipe** number, not the CMS number:102103| Module | SS6 Branch | SS5 Branch | Notes |104|--------|-----------|-----------|-------|105| recipe-silverstripe-essentials-website | `3` (default) | `2` | Recipe version, not CMS |106| silverstripe-essentials-tools | `3` (default) | `2` | |107| silverstripe-elemental-accordion | `6` (default) | `5` | Module's own major |108| silverstripe-elemental-embedded-code | `4` | `master` | |109| silverstripe-elemental-sponsors | `5` (default) | `master` | |110| silverstripe-elemental-templates | `3` (default) | `2` | |111112### Example repo: `dynamic/silverstripe-elemental-accordion`113114| Branch | CMS Version | Status |115|--------|-------------|--------|116| `5` | SS5 | Maintenance (backports only) |117| `6` | SS6 | Active development (default) |118119### Silverstripe fork workflow1201211. Fork the upstream repo into the `dynamic/` GitHub org, keeping the `silverstripe-` prefix.1222. Create a `feature/ss6-upgrade` branch with the dependency bumps.1233. Push and open a PR upstream.1244. Add a **VCS repo** entry for the fork to the **root project** `composer.json` (VCS repos are not inherited transitively from recipes — they must live in the root project).1255. Composer's VCS-repo priority makes the fork replace the upstream package. When the upstream merges and tags a release, switch back to Packagist and drop the VCS entry.126127For Composer specifically, the manifest steps are:128- Branch aliases — e.g. `"extra": { "branch-alias": { "dev-master": "6.x-dev" } }` (or alias the numbered branch, e.g. `"dev-6": "6.x-dev"`).129- Recipe constraints use `^6@dev` for SS6 branches to allow dev stability during the upgrade.