| name | release |
| description | Use when releasing freebsd-oci-containers from integration to main |
Release
Orchestrates the release workflow for freebsd-oci-containers. Two phases:
- Merge feature branch to Integration — lint, test, roborev review, merge
- Merge to Main — version bump, tag, push, cleanup
When invoked with no arguments, Phase 1 is skipped and the workflow starts
directly at Phase 2 (releasing what is already on integration).
Arguments
- First argument:
--from-feature <branch> to include a feature branch merge. Optional.
- Second argument:
--integration-only to stop after Phase 1. Optional.
Prerequisites
The integration branch must exist. If it does not:
git fetch --prune --tags --force origin
if git show-ref --verify --quiet "refs/remotes/origin/integration"; then
git checkout --track origin/integration
else
git checkout main
git merge --ff-only origin/main
git checkout -b integration
git push -u origin integration
fi
Phase 1: Merge Feature Branch to Integration
When to run: Only when --from-feature <branch> is specified. If releasing directly from integration, skip to Phase 2.
1.1 Checkout Feature Branch
if [[ -n "$(git status --porcelain)" ]]; then
echo "ERROR: Worktree has uncommitted or untracked changes"
git status --short
exit 1
fi
git fetch --prune --tags --force origin
if git show-ref --verify --quiet "refs/heads/${feature_branch}"; then
git checkout "${feature_branch}"
if git rev-parse --verify "origin/${feature_branch}" &>/dev/null; then
LOCAL_HEAD=$(git rev-parse HEAD)
REMOTE_HEAD=$(git rev-parse "origin/${feature_branch}")
MERGE_BASE=$(git merge-base HEAD "origin/${feature_branch}")
if [[ "${LOCAL_HEAD}" == "${REMOTE_HEAD}" ]]; then
echo "Local and remote branches are in sync"
elif [[ "${LOCAL_HEAD}" == "${MERGE_BASE}" ]]; then
git merge --ff-only "origin/${feature_branch}"
elif [[ "${REMOTE_HEAD}" == "${MERGE_BASE}" ]]; then
echo "NOTE: Local branch is ahead of remote (unpushed commits)"
else
echo "ERROR: Local and remote branches have diverged"
exit 1
fi
fi
else
git checkout -b "${feature_branch}" "origin/${feature_branch}"
fi
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [[ "${current_branch}" != "${feature_branch}" ]]; then
echo "ERROR: Failed to checkout feature branch ${feature_branch}"
echo "Currently on: ${current_branch}"
exit 1
fi
1.2 Run Linter
uv run --group lint ruff check . && uv run --group lint ruff format --check .
1.3 Run Tests
if [[ -d tests ]]; then
uv run --group dev pytest tests/ -v
fi
If tests fail, iterate until fixed.
1.4 Commit Changes (if any)
if [[ -n "$(git status --porcelain)" ]]; then
git add -A
git commit -m "chore: prepare ${feature_branch} for integration merge"
fi
1.5 Roborev Review
roborev review --branch --wait
Address all findings. Commit fixes. Re-run until review passes.
1.6 Update Integration
git fetch --prune --tags origin
git checkout integration
git merge --ff-only origin/integration
INTEGRATION_COMMIT_AT_MERGE=$(git rev-parse HEAD)
1.7 Merge Integration into Feature Branch
Merge integration INTO the feature branch first (keeps integration stable):
git checkout "${feature_branch}"
git merge --no-ff integration -m "chore: merge integration into ${feature_branch}"
Resolve conflicts if any.
1.8 Post-Merge Validation
uv run --group lint ruff check . && uv run --group lint ruff format --check .
if [[ -d tests ]]; then
uv run --group dev pytest tests/ -v
fi
If fixes needed, commit and run roborev review --branch --wait again.
1.9 Check if Integration Advanced
git fetch --prune --tags origin
git checkout integration
git merge --ff-only origin/integration
CURRENT_INTEGRATION_COMMIT=$(git rev-parse HEAD)
if [[ "${CURRENT_INTEGRATION_COMMIT}" != "${INTEGRATION_COMMIT_AT_MERGE}" ]]; then
echo "NOTE: Integration has advanced. Re-checkout feature branch and repeat steps 1.7–1.9."
fi
1.10 Merge Feature Branch to Integration
git merge --no-ff "${feature_branch}" -m "chore: merge ${feature_branch} into integration"
1.11 Push Integration
git push origin integration
If --integration-only was specified, stop here.
Phase 2: Merge to Main
Requires human approval for version bump type.
2.1 Prerequisites Check
git fetch --prune --tags --force origin
git checkout integration
git merge --ff-only origin/integration
LOCAL_HEAD=$(git rev-parse HEAD)
REMOTE_HEAD=$(git rev-parse origin/integration)
if [[ "${LOCAL_HEAD}" != "${REMOTE_HEAD}" ]]; then
echo "ERROR: Local integration has unpushed commits."
exit 1
fi
if [[ -n "$(git status --porcelain)" ]]; then
echo "ERROR: Uncommitted changes detected"
git status --short
exit 1
fi
2.2 Run Tests
if [[ -d tests ]]; then
uv run --group dev pytest tests/ -v
fi
If tests fail, create a fix branch and go through Phase 1 first.
2.3 Recommend Version Bump
LATEST_TAG=$(git tag --merged origin/main -l 'v*' --sort=-v:refname | head -1)
if [[ -n "${LATEST_TAG}" ]]; then
git log "${LATEST_TAG}"..HEAD --format="%h %s%n%b"
else
echo "NOTE: No previous tags. This will be the first release."
git log --format="%h %s%n%b"
fi
Version bump guidelines:
| Commit Type | Bump |
|---|
fix:, docs:, chore:, refactor:, test:, style: | patch |
feat: | minor |
feat!: or BREAKING CHANGE: in body | major |
Present recommendation and wait for human approval.
2.4 Bump Version on Integration
PRE_BUMP_SHA=$(git rev-parse origin/integration)
cz bump --increment <patch|minor|major> --no-verify
LATEST_TAG=$(git describe --tags --abbrev=0)
2.5 Merge to Main
git fetch --prune --tags --force origin
POST_FETCH_SHA=$(git rev-parse origin/integration)
if [[ "${PRE_BUMP_SHA}" != "${POST_FETCH_SHA}" ]]; then
echo "ERROR: origin/integration advanced since bump. Stop and investigate."
exit 1
fi
git checkout main
git merge --ff-only origin/main
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
echo "ERROR: Local main has stray commits. Resolve manually."
exit 1
fi
git merge --no-ff integration -m "chore: merge integration into main (${LATEST_TAG})"
2.6 Move Tag to Main
git tag -f "${LATEST_TAG}" main
2.7 Return to Integration
git checkout integration
2.8 Push
REMOTE_TAG_SHA=$(git ls-remote --tags origin "refs/tags/${LATEST_TAG}" 2>/dev/null | awk '{print $1}')
LOCAL_TAG_SHA=$(git rev-parse "${LATEST_TAG}" 2>/dev/null)
if [[ -n "${REMOTE_TAG_SHA}" && "${REMOTE_TAG_SHA}" != "${LOCAL_TAG_SHA}" ]]; then
echo "ERROR: Remote tag ${LATEST_TAG} already exists with different SHA. Investigate."
exit 1
fi
git push --atomic origin main integration "+refs/tags/${LATEST_TAG}:refs/tags/${LATEST_TAG}"
2.9 Cleanup
git fetch --prune origin
if [[ -n "${feature_branch:-}" ]]; then
if git show-ref --verify --quiet "refs/remotes/origin/${feature_branch}"; then
if git merge-base --is-ancestor "origin/${feature_branch}" main; then
git push origin --delete "${feature_branch}" 2>/dev/null || true
fi
fi
git branch -d "${feature_branch}" 2>/dev/null || true
fi
git fetch --prune origin
Examples
/release
/release --from-feature feat/project-rationalization
/release --from-feature feat/project-rationalization --integration-only
Critical Rules
- NEVER merge main into integration — one-directional flow only
- NEVER skip human approval for version bump type
- NEVER rebase
- ALWAYS run tests before Phase 2
- ALWAYS run roborev review before merging to integration