GitHub Workflow & Branch Protection¶
This document describes the GitHub repository configuration optimized for solo developer workflow while maintaining code quality.
Branch Model¶
- Feature PRs always target
stagingand are squash-merged. Every merge tostagingtriggers CI and, when green, an automatic staging deploy. mainis human-merged and only receives staging→main promotion PRs (orrelease/*hot-fix branches). TheEnforce PR baseworkflow (check-base) blocks any other PR againstmain.- There is no production environment. Promoting to
mainkeeps the branches in sync but does not deploy anywhere; theDeploy to Productionworkflow is disabled.
Promotion: scripts/release.sh¶
Promotion staging→main runs exclusively via:
Since June 2026 this is fully automated: the script finds the green CI run with a successful deploy job for the staging HEAD, opens the promotion PR (base=main, head=staging), rebase-merges it (linear history), recreates staging if GitHub's delete_branch_on_merge removed it, and retargets open PRs back to staging. Promotion PRs skip the heavy CI jobs (the precheck job in ci.yml detects them); check-base is the only required check.
Branch Protection Rules¶
main¶
| Rule | Setting | Purpose |
|---|---|---|
| Required status checks | check-base (Enforce PR base workflow) |
Blocks PRs to main that don't come from staging or release/* |
| Strict status checks | Enabled | Branch must be up-to-date with main |
| Required signatures | Disabled | Removes GPG signing friction for solo dev |
| Enforce admins | Disabled | Owner can bypass rules when needed |
| Required PR reviews | Disabled | No approval needed for solo dev |
| Required conversation resolution | Enabled | Open review threads must be resolved |
| Linear history | Enabled | Clean, readable git history (promotion PRs rebase-merge) |
| Force pushes | Disabled | Protects commit history |
| Deletions | Disabled | Prevents accidental branch deletion |
staging¶
| Rule | Setting | Purpose |
|---|---|---|
| Required status checks | None | Feature PRs gate on CI informally; deploy job only runs after green tests |
| Force pushes | Disabled | Protects deploy history |
| Deletions | Disabled | Prevents delete_branch_on_merge from removing staging after a promotion merge |
Note: the repository has delete_branch_on_merge: true, so feature branches are cleaned up automatically after merge.
Standard Workflow¶
# Create feature branch
git checkout staging && git pull
git checkout -b feature/new-feature
# Make changes and push (pre-push hook runs local tests)
git add .
git commit -m "feat: add new feature"
git push -u origin feature/new-feature
# Create PR against staging — CI starts automatically on the PR
gh pr create --base staging --title "feat: add new feature" --body "Description"
# After CI is green, squash-merge
gh pr merge --squash
# CI on the staging push runs the tests again and deploys to staging
# Later, promote staging → main:
scripts/release.sh --promote-only --no-uat
Direct pushes to main are not part of the workflow — main only moves via promotion PRs.
Quality Gates¶
Local (Pre-push Hook)¶
Located at .githooks/pre-push, runs before every push:
- Backend tests - Django unit tests (subset for speed; requires local PostgreSQL on 5432 and Redis on 6379, e.g. via
docker-compose.dev.yml) - TypeScript check -
tsc --noEmit - CodeRabbit review - AI code review (non-blocking)
- Frontend tests - Jest with coverage
Never bypass the hook with --no-verify.
Remote (GitHub Actions CI)¶
.github/workflows/ci.yml runs on every push to main and staging, and on every PR against main or staging:
- precheck - Detects staging→main promotion PRs and skips the heavy jobs for them (the code was already fully tested on staging)
- backend-test - flake8, migration checks, full pytest suite (parallel, coverage ≥60%)
- frontend-test - TypeScript check, ESLint, full Jest suite
- frontend-build - Next.js production build (artifact reused by the E2E jobs)
- e2e-smoke-test - Playwright
@smoketests - e2e-visual-test - Visual regression tests
- deploy - Only on pushes to
staging: callsdeploy-staging.yml(workflow_call) after all jobs above are green
Runs only on schedule (cron, weekly) or manual dispatch (workflow_dispatch):
- e2e-full-test - Full Playwright suite
- security-scan-dependencies - npm audit, pip-audit, Snyk
- docker-build - Validates Docker images build + Trivy scans
GitHub Pro Features Used¶
| Feature | Usage |
|---|---|
| 3,000 Actions minutes/month | Heavy CI pipeline (tests, E2E, Docker) |
| Private repo CI | All CI runs on private repository |
| Branch protection | Status checks without PR requirement |
Changing Branch Protection¶
Via GitHub CLI¶
# View current settings
gh api repos/Voorman/webshop_freeze_design/branches/main/protection
# Update settings (current config: check-base is the only required check)
gh api repos/Voorman/webshop_freeze_design/branches/main/protection -X PUT \
--input - << 'EOF'
{
"required_status_checks": {
"strict": true,
"checks": [
{"context": "check-base"}
]
},
"enforce_admins": false,
"required_pull_request_reviews": null,
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false
}
EOF
# Enable/disable signature requirement
gh api repos/OWNER/REPO/branches/main/protection/required_signatures -X POST # Enable
gh api repos/OWNER/REPO/branches/main/protection/required_signatures -X DELETE # Disable
Warning
The required-status-checks config has been observed to silently revert to an older set of checks (May 2026, cause unknown). If a promotion PR fails with "base branch policy prohibits the merge", verify the current config with gh api .../branches/main/protection/required_status_checks and re-apply check-base via the API — not via the UI.
Via GitHub UI¶
- Go to Settings → Branches → Branch protection rules
- Click Edit on
main - Modify settings as needed
Future Considerations¶
When to Add PR Requirements¶
Consider enabling required PR reviews if:
- You add team members
- You want enforced code review
- Compliance requirements change
When to Add GPG Signing¶
Consider enabling required signatures if:
- Working on security-sensitive features
- Contributing to open source
- Corporate compliance requires it
To setup GPG signing:
# Generate GPG key
gpg --full-generate-key
# Get key ID
gpg --list-secret-keys --keyid-format=long
# Configure Git
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
# Add public key to GitHub
gpg --armor --export YOUR_KEY_ID | pbcopy
# Paste at: GitHub → Settings → SSH and GPG keys → New GPG key
Related Documentation¶
Architecture Decisions¶
Task Queue: Celery (Decision: January 2026)¶
Decision: Keep Celery + Redis instead of migrating to Django 6.0 Tasks.
Reasons: - Django Tasks backend still in beta (not production guaranteed) - Celery is battle-tested (10+ years) - Current setup works reliably - Low task volume (~50/day) doesn't justify migration risk - €20-40/mo savings not worth beta risk for e-commerce
Revisit when: - Django Tasks backends reach v1.0 stable - Django 6.1+ released with matured ecosystem - Task volume significantly increases
Alternatives considered: - Django Tasks + django-tasks (too new) - Django-Q2 (viable alternative if Celery issues arise)