Releases¶
Prism follows a Git Flow-inspired release workflow adapted for a small project with one maintainer and two protected branches.
This page documents the branch model, release procedure, hotfix procedure, and the CI pipeline that automates artifact builds.
Quick reference¶
| Action | Steps |
|---|---|
| Pre-release | Tag on development: git tag vX.Y.Z-beta.N && git push origin vX.Y.Z-beta.N. CI auto-builds + creates GitHub Pre-release. No branch, no PR, no gh release create. |
| Stable release | 1. Cut release/vX.Y.Z from development → 2. PR to main → 3. Squash-merge via web UI → 4. Tag vX.Y.Z on main → 5. Push tag → 6. Sync main back to development → 7. Delete release branch. |
| Hotfix | 1. Cut hotfix/vX.Y.Z from main → 2. Fix + commit → 3. PR to main → 4. Squash-merge via web UI → 5. Tag → 6. Sync back to development → 7. Delete hotfix branch. |
| Sync main→dev | Check git diff --stat origin/main development first. If empty → already synced, don't rebase. If clean rebase → git rebase main. If diverged → git merge main on a sync branch + PR. |
Critical rules:
- Merge via GitHub web UI —
gh pr mergeis blocked by a shell wrapper - NEVER
gh release create— CI auto-creates releases from tag pushes - NEVER create
release/vX.Y.Z-beta.Nbranches — pre-releases are tags only - Use rebase, not merge on
development(linear history enforced) - Check
git diffbefore rebasing — squash merges create duplicate SHAs
Branch model¶
| Branch | Purpose | Protection |
|---|---|---|
main |
Production-ready code. Every commit on main is a released version. |
PR required, strict CI, linear history |
development |
Active development. All features and fixes land here first. | PR required, strict CI, linear history |
feature/* |
Individual features or bug fixes. Cut from development, PR'd back to development. |
None (deleted after merge) |
release/vX.Y.Z |
Release preparation. Cut from development, PR'd to main. |
None (deleted after merge) |
hotfix/vX.Y.Z |
Emergency fix for a released version. Cut from main, PR'd to both main and development. |
None (deleted after merge) |
Branch naming conventions¶
- Feature branches:
feature/<short-description>(e.g.feature/cast-plugins) - Release branches:
release/vX.Y.Z-- withvprefix (e.g.release/v0.2.0) - Hotfix branches:
hotfix/vX.Y.Z-- withvprefix (e.g.hotfix/v0.2.1) - Tags:
vX.Y.Z-- withvprefix (e.g.v0.2.0)
The v prefix is used consistently on release/hotfix branches and tags,
matching the tag name they produce.
Versioning¶
Prism follows semantic versioning:
| Pattern | Example | When to use |
|---|---|---|
vX.Y.Z |
v0.2.0 |
Stable release |
vX.Y.Z-beta.N |
v0.2.0-beta.5 |
Pre-release (testing before stable) |
vX.Y.Z-rc.N |
v0.2.0-rc.1 |
Release candidate (optional) |
The CI pipeline syncs the version from the git tag into pyproject.toml
and src/prism/__init__.py at build time. You never edit version numbers
manually for a release -- just tag and push.
Release procedure¶
1. Cut a release branch¶
When development is ready for release, cut a release branch:
2. Prepare the release¶
On the release branch, do any final preparation:
- Bump version in
pyproject.tomlandsrc/prism/__init__.py(if not already set) - Update
CHANGELOG.mdif needed - Final documentation review
# Example: ensure version is set for stable release
# pyproject.toml: version = "0.2.0"
# src/prism/__init__.py: __version__ = "0.2.0"
git add -A
git commit -m "release: prepare v0.2.0"
3. Open PR to main¶
git push -u origin release/v0.2.0
gh pr create --base main --head release/v0.2.0 \
--title "release: v0.2.0" \
--body "Stable release v0.2.0"
All CI checks must pass before merging:
- Lint (
lintcontext) -- ruff check + format - Unit Tests (
test-linuxcontext) -- 884 unit tests (Linux) + 884 unit tests (Windows) - Integration Tests (
test-linuxcontext) -- 82 integration tests against a live IRIS container (Linux only; 7 skip on CI due to IRIS Community license limits) - Windows Build (
test-windowscontext) -- Unit tests + PyInstaller frozen binary tests (--version,--help,cast --list,prism servestartup)
Required status checks on main: lint, test-linux. Branch protection
enforces enforce_admins: true, required_linear_history: true,
allow_force_pushes: false, allow_deletions: false.
4. Merge to main — via GitHub web UI¶
Merge the PR using squash merge (linear history is enforced on main).
⚠️ Merge via the GitHub web UI, NOT
gh pr merge. A shell wrapper at~/.local/bin/ghblocksgh pr mergeentirely. This is intentional — it prevents agents and scripts from auto-merging PRs. All merges must be performed manually in the GitHub web UI.
5. Tag the release¶
⚠️ NEVER run
gh release create. The CI pipeline creates the GitHub Release automatically when the tag is pushed (see below). Running it manually races with CI and can create a duplicate or empty release.
Pushing the tag triggers two workflows:
Build and Release (build-release.yml):
- Runs lint + tests (Linux + Windows)
- Syncs the version from the tag into
pyproject.tomland__init__.py - Builds the Python wheel (Linux)
- Builds the PyInstaller exe (Windows)
- Builds the Inno Setup installer (Windows)
- Tests the frozen binary (
--version,--help,cast --list,prism serve) - Creates a GitHub Release with changelog generated by git-cliff
Changelog (changelog.yml):
- Runs git-cliff to regenerate
CHANGELOG.md(full history) - Copies a MkDocs-friendly version to
docs/changelog.md - Commits both files to
maindirectly (triggers Pages deploy)
For stable releases, git-cliff uses --ignore-tags '.*-beta\..*' so
pre-release commits roll up into the stable release notes.
6. Sync main back to development¶
After the release, main has the squash-merged release commit + version
bump. Development needs those changes. With enforce_admins: true and
required_linear_history: true, you cannot push merge commits directly
to development.
If development has few or no diverged commits (rebase is clean):
If development has diverged significantly (many commits on both sides, rebase would replay dozens of commits with conflicts):
git checkout development
git checkout -b sync/main-to-development
git merge main --no-edit # merge, NOT rebase — see pitfall below
git push -u origin sync/main-to-development
gh pr create --base development --head sync/main-to-development \
--title "chore: sync main into development" \
--body "Sync main back to development after release v0.2.0."
# Merge via GitHub web UI (squash merge preserves linear history)
Squash-merge deduplication: After a squash merge,
git logshows main's commits as "not in development" because the SHA changed, even though the content is identical. Before rebasing, always check:If the diff is empty (or only shows changes you intentionally made), the branches are already content-synced — do NOT rebase. A rebase through dozens of already-applied commits will produce conflict after conflict for no benefit.
7. Clean up¶
Delete the release branch (local + remote):
Prune stale local branches that no longer have a remote counterpart:
Pre-releases¶
A pre-release is just a tag on the current branch — no release branch,
no PR to main, no gh release create. The tag push triggers CI which
builds artifacts and creates the GitHub Release automatically.
# Tag a pre-release from development (or any feature branch)
git checkout development
git pull origin development
git tag v0.2.0-beta.1
git push origin v0.2.0-beta.1
GitHub automatically marks the release as a Pre-release when the tag
contains a hyphen (-beta, -rc). Pre-release tags do not affect the
latest release pointer on GitHub.
⚠️ NEVER run
gh release createfor pre-releases. The CI pipeline creates the release automatically. Manual creation races with CI and can create duplicates.⚠️ NEVER create a
release/vX.Y.Z-beta.Nbranch. Release branches are for stable releases only. Pre-releases are tags directly on the working branch.
You can tag as many pre-releases as needed before cutting the stable release. When ready, follow the full release procedure above.
Re-tagging after a failed pipeline¶
If CI failed and no release was created:
git tag -d v0.2.0-beta.1
git push origin :refs/tags/v0.2.0-beta.1
git tag v0.2.0-beta.1
git push origin v0.2.0-beta.1
If the tag push doesn't auto-trigger CI, use workflow_dispatch:
Hotfix procedure¶
When a critical bug is found in a released version:
1. Cut a hotfix branch from main¶
2. Fix and bump version¶
# Bump the patch version
# pyproject.toml: version = "0.2.1"
# src/prism/__init__.py: __version__ = "0.2.1"
git add -A
git commit -m "fix: critical bug in SQL execution"
3. PR to main¶
git push -u origin hotfix/v0.2.1
gh pr create --base main --head hotfix/v0.2.1 \
--title "hotfix: v0.2.1" \
--body "Critical fix for v0.2.0"
4. Merge, tag, and sync¶
# Merge the PR to main via GitHub web UI (squash merge)
git checkout main
git pull origin main
git tag v0.2.1
git push origin v0.2.1
# Sync back to development (use merge if diverged, rebase if clean)
git checkout development
git checkout -b sync/hotfix-v0.2.1-to-dev
git merge main --no-edit
git push -u origin sync/hotfix-v0.2.1-to-dev
gh pr create --base development --head sync/hotfix-v0.2.1-to-dev \
--title "sync: hotfix v0.2.1 to development" \
--body "Sync hotfix back to development after release v0.2.1."
# Merge via GitHub web UI (squash merge)
# Clean up
git branch -d hotfix/v0.2.1
git push origin --delete hotfix/v0.2.1
Visual workflow¶
┌─── PRE-RELEASE ───────────────────────────┐
│ │
development ──┼── tag vX.Y.Z-beta.N ──> CI builds ──> GitHub Pre-release
│ (no branch, no PR, (auto-created)
│ no gh release create)
│
│ re-tag if CI fails: delete tag, re-tag, push
└──────────────────────────────────────────┘
┌─── STABLE RELEASE ──────────────────────────────────┐
│ │
development ──CUT── release/vX.Y.Z ──PR──> main ──SQUASH MERGE (web UI)
│ │ │
│ │ TAG vX.Y.Z ──> CI builds
│ │ │ + git-cliff
│ │ ├──> GitHub Release (auto)
│ │ └──> CHANGELOG.md commit
│ │
│ SYNC: rebase (if clean) or merge+PR (if diverged)
│<──────── main ──────────────────────────────────┘
│
└── DELETE release/vX.Y.Z (local + remote)
│
┌─── HOTFIX ────────────────────────┘
│
main ──CUT── hotfix/vX.Y.Z ──PR──> main ──SQUASH MERGE (web UI)
│ │
│ TAG vX.Y.Z ──> CI builds
│
│ SYNC: merge main into dev via sync PR
│<──────── main ──────────────────────┘
│
└── DELETE hotfix/vX.Y.Z (local + remote)
CI pipelines¶
| Workflow | File | Trigger | What it does |
|---|---|---|---|
| Test Linux | test-linux.yml |
PR/push to development or main |
Lint (ruff check + format), unit tests (884), integration tests (82, Docker IRIS) |
| Test Windows | test-windows.yml |
PR/push to development or main |
Unit tests (884), PyInstaller frozen binary tests |
| Build and Release | build-release.yml |
Tag push (v*) |
Full pipeline: lint → tests → wheel → exe → installer → frozen binary tests → GitHub Release (git-cliff changelog) |
| Changelog | changelog.yml |
Tag push (v*) |
Regenerates CHANGELOG.md + docs/changelog.md via git-cliff, commits to main |
| GitHub Pages | pages.yml |
Push to main |
MkDocs documentation deploy to GitHub Pages |
Branch protection rules¶
Both main and development are protected with identical rules:
| Rule | Value |
|---|---|
| Required PR reviews | 0 (PR itself is required) |
| Required status checks | lint, test-linux |
| Strict (up-to-date) | Yes |
| Linear history | Yes (squash merges only, no merge commits) |
| Force pushes | Disabled |
| Branch deletion | Disabled |
| enforce_admins | True (no bypasses, even for admins) |
| require_last_push_approval | False (sole developer) |
main only accepts PRs from release/* or hotfix/* branches.
development is the target for all feature/* branches and Dependabot PRs.
With enforce_admins: true, all changes must go through pull requests —
even for repository admins. To sync main back to development after a
release, use git rebase main (if clean) or create a sync branch with
git merge main and PR it (if diverged). Squash-merge on the PR preserves
linear history.
Dependabot is configured to target development (not main) in
.github/dependabot.yml.
Changelog¶
Release notes are auto-generated by git-cliff
using the configuration in cliff.toml. Two workflows handle changelog:
- Build and Release (
build-release.yml): Generates release notes for the GitHub Release itself (attached to the release, not committed). - Changelog (
changelog.yml): RegeneratesCHANGELOG.mdanddocs/changelog.mdwith full history and commits them tomain.
Commits should follow Conventional Commits so they are categorized correctly:
| Prefix | Category in changelog |
|---|---|
feat: |
Features |
fix: |
Bug Fixes |
docs: |
Documentation |
ci: |
CI/CD |
refactor: |
Refactoring |
test: |
Tests |
chore(deps): |
Dependencies |
chore: |
Miscellaneous |
For stable releases, pre-release tags are excluded from the changelog so their commits roll up into the stable release notes.
Release artifacts¶
Each release includes:
| Artifact | Platform | Description |
|---|---|---|
prism.exe |
Windows | Standalone PyInstaller binary (~36 MB) |
prism-X.Y.Z-setup.exe |
Windows | Inno Setup installer with Prism branding |
prism-X.Y.Z-py3-none-any.whl |
Cross-platform | Python wheel (pip install prism) |