Skip to content

Branching and Versioning Strategy

This document covers branch naming, PR rules, version format, and the release workflow for contributors.


Branch Model

Branch Purpose PR required? Approval required?
main Stable trunk. Never commit directly. Yes Yes (at least 1)
feature/<name> All feature work. Created from main. Merged back to main via PR. Yes No
bugfixes/<name> Bug fixes. Branch from main for pre-release fixes; branch from a release tag for hotfixes. Yes (to main) No

Rules:

  • feature/ branches must be branched off main.
  • bugfixes/ branches branch from main for pre-release fixes. For hotfixes to an already-released version, branch from the release tag: git checkout -b bugfixes/fix-foo v5.2.0.
  • Main first — nothing is released that is not on main. A hotfix is fixed on main (PR, approval, merge) before it is released; the hotfix branch holds only git cherry-pick -x copies of merged commits. The cut workflows enforce this.
  • All merges to main go through a Pull Request — no direct pushes.
  • Branch names: lowercase, hyphens. Examples: feature/risk-score-export, bugfixes/fix-login-redirect.
  • One issue per branch. Each branch fixes exactly one issue or implements exactly one feature. Never combine unrelated fixes into a single branch or PR.

Starting New Work

# Feature or pre-release bugfix — branch from main
git checkout main && git pull
git checkout -b feature/<name>
# or
git checkout -b bugfixes/<name>

# Hotfix to a released version — branch from the release tag, NOT from main
git checkout -b bugfixes/<name> v5.2.0

Version Number Format

Context Format Example
main dev builds (:edge) Major.Minor.yyyyMMdd.HHmm 6.3.20261023.1430
Pre-release tags (:beta) Major.Minor.Patch-beta.N 5.3.0-beta.1
Release tags (:latest) Major.Minor.Patch.0 5.2.1.0

Who updates what:

Action Who When
Minor bump + timestamp bump-version.yml GitHub Action Automatically on every PR merge to main
Pre-release version cut-beta.yml GitHub Action When you run Actions → Cut Beta
Release version cut-release.yml GitHub Action When you run Actions → Cut Release
Hotfix version cut-hotfix.yml GitHub Action When you run Actions → Cut Hotfix
Major bump bump-version.yml GitHub Action On the first PR merge after a release (or beta) of a new major is tagged — Major follows the highest vX.Y.Z tag and Minor restarts at 1
Branch work Nobody Never touch setup/IdentityAtlas.psd1 on a branch

Changelog Fragments

Every feature/ or bugfixes/ branch must include a changelog fragment. Never edit CHANGES.md directly — the bump-version.yml CI action merges all fragments on PR merge.

File: changes/<descriptive-name>.md (e.g. changes/fix-login-redirect.md)

Format:

- Fixed the login redirect when auth is enabled and no session exists
- Improved error message when tenant ID is missing

Write in user-facing language. One bullet per functional change. Add the file alongside the code change — don't batch at the end.


Merging to Main (via PR)

  1. Open a PR from feature/<name> or bugfixes/<name> into main.
  2. Use the changelog fragment content as the PR description body.
  3. Requires 1 approval and passing CI.
  4. After merge, bump-version.yml automatically increments Minor, updates the timestamp, and merges all changes/*.md fragments into CHANGES.md. The docker-publish.yml action then builds and pushes Docker images tagged :edge.

Cutting a Pre-Release (Beta / RC)

To publish a build for testers without touching :latest:

  1. Go to Actions → Cut Beta → Run workflow
  2. Enter the version: Major.Minor.Patch-beta.N (e.g. 5.3.0-beta.1, 5.3.0-rc.1, 5.3.0-alpha.1)
  3. The workflow creates the tag on the current main HEAD (or on the optional ref input — any branch, tag or commit)
  4. docker-publish.yml builds :beta + :5.3.0-beta.1

Users on docker-compose.prod.yml tracking :latest are not affected.

Cutting a Release

The body of the GitHub release — changelog, baseline comparison and the dependency-update list — is assembled by the workflow. See How a release's notes are assembled.

When main is stable and ready to ship to customers:

  1. Go to Actions → Cut Release → Run workflow
  2. Enter the version: Major.Minor.Patch (e.g. 5.2.0)
  3. The workflow creates tag v5.2.0 on the current main HEAD (or on the optional ref input — any branch, tag or commit)
  4. docker-publish.yml triggers automatically on the tag push and builds :latest + :5.2.0.0

Customers who track :latest will receive the new version on their next docker compose pull.


Hotfix Releases

To ship a bugfix without including features already on main, fix it on main first, then copy the merged commit onto the release:

# 1. Fix on main: bugfixes/fix-login-crash from main → PR → approval → merge

# 2. Branch from the release tag — NOT from main — and cherry-pick the merged commit WITH -x
git checkout -b bugfixes/fix-login-crash-hotfix v5.2.0
git cherry-pick -x <merged commit on main>
git push origin bugfixes/fix-login-crash-hotfix
  1. Go to Actions → Cut Hotfix → Run workflow (from main)
  2. Enter the branch name (bugfixes/fix-login-crash-hotfix) and new version (5.2.1)
  3. The workflow checks every commit after v5.2.0 with tools/release/Assert-ReleaseFromMain.ps1: each must be on main, or a -x cherry-pick of a commit on main that changes exactly the same lines. Anything else — a fix written on the hotfix branch, a pick without -x, a conflict resolved by hand — stops the run before a tag exists.
  4. The workflow creates tag v5.2.1 on the HEAD of your branch; docker-publish.yml builds :latest + :5.2.1.0

If the cherry-pick conflicts, the resolution is code nobody reviewed: land a backport-specific fix on main that applies cleanly instead.

Cut Release and Cut Beta likewise only tag a commit that is on main, and all three must be dispatched from main (they load the checker from their own commit, so a branch cannot ship an edited copy).


Stacked PRs

For larger features, break the work into a stack of small focused PRs. Each PR targets the previous branch in the stack:

# Step 1 — targets main
git checkout -b feature/foo-step-1
gh pr create --base main --title "step 1: ..."

# Step 2 — stacked on step 1
git checkout -b feature/foo-step-2
gh pr create --base feature/foo-step-1 --title "step 2: ..."

When a bottom PR merges, retarget the next one: gh pr edit <number> --base main.


Image Channels

Tag Published when Who uses it
:latest A release tag (v5.2.0) is created via Actions → Cut Release or Cut Hotfix Customers (default)
:beta A pre-release tag is created via Actions → Cut Beta Beta testers
:edge Every PR merges to main Developers and testers
:5.2.0.0 Same time as :latest — exact pinned version Production deployments needing controlled upgrades
:5.3.0-beta.1 Same time as :beta — exact pre-release version Testers who want a reproducible pre-release build

See Docker Setup for how to select a channel via IMAGE_TAG.


Repository Rulesets

Branch protection is configured via two GitHub rulesets. If you ever need to recreate them (e.g. after transferring the repo), do so manually in Settings → Rules → Rulesets.

Protect main

Setting Value
Target main
Merge methods Squash only
Required approvals 1 + code owner review
Dismiss stale reviews Yes
Required status checks PR Summary (strict)
Code scanning CodeQL — errors threshold, high/higher security alerts
Deletion Blocked
Force push Blocked

Bypass actors:

Actor Type Mode Why
Fortigi CI bot Integration Always Pushes automated version bump commits on PR merge
IdentityAtlas-Owners Team Pull request only Allows team members to merge without a second approval

Protect gh-pages

Setting Value
Target gh-pages
Deletion Blocked
Force push Blocked

No bypass actors — mike (the docs versioning tool) only does regular fast-forward pushes and never needs to delete or force-push the branch.