Git workflow, environments & CI/CD pipeline (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# InboxIntel — Git Workflow & Environment Strategy
|
||||
|
||||
The single source of truth for how we branch, commit, version, and promote code
|
||||
across environments. Optimised for a **solo developer on Windows 11 with a future
|
||||
Linux production server**. Kept deliberately lightweight — every rule here earns
|
||||
its place.
|
||||
|
||||
---
|
||||
|
||||
## 1. Branch strategy
|
||||
|
||||
A trimmed **GitHub Flow + a long-lived `develop`** model. Two permanent branches,
|
||||
short-lived branches off `develop`.
|
||||
|
||||
| Branch | Lives for | Purpose | Deploys to |
|
||||
|-----------------|-----------|------------------------------------------------------|------------|
|
||||
| `main` | forever | Always releasable. Every commit is tagged & shippable | production |
|
||||
| `develop` | forever | Integration branch. What staging runs | staging |
|
||||
| `feature/*` | hours–days| One feature or refactor | dev (local)|
|
||||
| `fix/*` | hours | Non-urgent bug fix | dev (local)|
|
||||
| `hotfix/*` | minutes–hrs| Urgent prod fix, branched from `main` | prod (fast)|
|
||||
| `release/x.y.0` | optional | Only if a release needs stabilisation before tagging | staging |
|
||||
|
||||
**Why this shape (not full GitFlow):** a solo dev doesn't need GitFlow's ceremony
|
||||
(separate release managers, parallel release trains). But keeping `develop`
|
||||
separate from `main` gives one thing that pure trunk-based can't: a **staging
|
||||
environment that always mirrors `develop`** while `main` stays clean and
|
||||
tag-perfect for production. `release/*` exists only when you want to freeze
|
||||
features and stabilise — skip it for routine work.
|
||||
|
||||
### Normal flow
|
||||
|
||||
```
|
||||
main ──────●────────────────────●────────── (tagged: v1.2.0, v1.3.0)
|
||||
\ /
|
||||
develop ──●──●──●──●──●──●──●──●──────────── (staging)
|
||||
\ / \ /
|
||||
feature/x ●─● \ /
|
||||
fix/y ●───●
|
||||
```
|
||||
|
||||
1. `git switch develop && git pull`
|
||||
2. `git switch -c feature/sender-policy`
|
||||
3. Commit in small conventional commits.
|
||||
4. Push, open a PR **into `develop`** (Gitea). CI must be green.
|
||||
5. Squash-merge. Delete the branch.
|
||||
6. When `develop` is ready to ship → PR `develop → main`, tag, deploy.
|
||||
|
||||
---
|
||||
|
||||
## 2. Commit conventions — Conventional Commits
|
||||
|
||||
Format: `type(scope): short imperative summary`
|
||||
|
||||
**Types:** `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `perf`,
|
||||
`build`, `style`, `security`.
|
||||
|
||||
Rules:
|
||||
- Summary ≤ 72 chars, imperative mood ("add", not "added").
|
||||
- One logical change per commit.
|
||||
- Body explains **why**, not what (the diff shows what).
|
||||
- Breaking change → add `!` (`feat!:`) and a `BREAKING CHANGE:` footer.
|
||||
|
||||
Examples (matching this repo's history):
|
||||
```
|
||||
feat(ui): F1+F2 — component primitives + rebuilt app shell
|
||||
fix(security): systemic IDOR safeguard via EF global query filters
|
||||
ci: add Gitea Actions build + test pipeline
|
||||
```
|
||||
|
||||
Why: conventional commits drive **automatic semver bumps** and a generated
|
||||
CHANGELOG, and make `git log` scannable. `feat` → minor, `fix` → patch,
|
||||
`BREAKING CHANGE` → major.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pull request / merge flow (Gitea)
|
||||
|
||||
Even solo, PRs are worth it: they run CI, give a diff review checkpoint, and build
|
||||
a paper trail.
|
||||
|
||||
- **Target:** `feature/*` and `fix/*` → `develop`. `develop`/`hotfix/*` → `main`.
|
||||
- **Gate:** the `CI` workflow (backend build+test, frontend build) must pass.
|
||||
- **Merge style:** **squash-merge** feature branches (one clean commit on
|
||||
`develop`). **Merge commit** for `develop → main` (preserves the integration
|
||||
history and makes the release boundary visible).
|
||||
- **Branch protection (Gitea → Settings → Branches):** protect `main` and
|
||||
`develop`; require status checks to pass; disallow force-push.
|
||||
|
||||
---
|
||||
|
||||
## 4. Versioning — Semantic Versioning (`MAJOR.MINOR.PATCH`)
|
||||
|
||||
- **MAJOR** — breaking API/behaviour change.
|
||||
- **MINOR** — backward-compatible feature.
|
||||
- **PATCH** — backward-compatible fix.
|
||||
|
||||
The current version lives in [`VERSION`](../VERSION) and is the one place bumped
|
||||
per release. Pre-1.0 while scaffolding: stay on `0.x` (minor = features, patch =
|
||||
fixes; anything may change).
|
||||
|
||||
---
|
||||
|
||||
## 5. Tagging & releases
|
||||
|
||||
Tags are cut **only on `main`**, annotated, prefixed `v`:
|
||||
|
||||
```bash
|
||||
git switch main && git pull
|
||||
# bump VERSION + CHANGELOG in a release commit, then:
|
||||
git tag -a v1.3.0 -m "v1.3.0 — sender policy + staging overlay"
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
- Tag == the exact commit deployed to production == the rollback target.
|
||||
- The tag message summarises the release; details live in `CHANGELOG.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Hotfix process
|
||||
|
||||
For a bug already in production:
|
||||
|
||||
```bash
|
||||
git switch main && git pull
|
||||
git switch -c hotfix/oauth-callback-500
|
||||
# fix + test
|
||||
git commit -m "fix(security): guard null OAuth state on callback"
|
||||
# PR hotfix/* -> main, CI green, merge
|
||||
git switch main && git pull
|
||||
git tag -a v1.3.1 -m "v1.3.1 hotfix — OAuth callback" && git push origin main --tags
|
||||
# deploy the tag, THEN back-merge so develop doesn't lose the fix:
|
||||
git switch develop && git merge main && git push
|
||||
```
|
||||
|
||||
The **back-merge to `develop`** is the step people forget — without it the next
|
||||
release silently reintroduces the bug.
|
||||
|
||||
---
|
||||
|
||||
## 7. Environments
|
||||
|
||||
| Env | Host | Runtime | Config source | Purpose |
|
||||
|-------------|-------------------------|--------------------------------------|--------------------------|--------------------|
|
||||
| Development | Windows 11 (native) | `dotnet run` + `vite` (hot reload) | `appsettings.Development.json` + user-secrets | fast iteration, debugging |
|
||||
| Staging | Windows 11 (Docker) | Linux containers, `ASPNETCORE_ENVIRONMENT=Production` | `deploy/.env.staging` + `docker-compose.staging.yml` | production rehearsal |
|
||||
| Production | Linux server (Docker) | identical Linux containers | `deploy/.env` on the server | live |
|
||||
|
||||
**Parity principle:** dev is fast (native, Windows) and *not* production-shaped —
|
||||
that's fine, it's for inner-loop speed. **Staging is the parity gate**: it runs the
|
||||
*same Linux images* Docker builds for production, so "works in staging" genuinely
|
||||
predicts "works in prod". The only staging↔prod differences are the dev banner,
|
||||
capped sync, ports, and volume namespace — see `docker-compose.staging.yml`.
|
||||
|
||||
### Commands
|
||||
|
||||
```powershell
|
||||
# DEV (native, hot reload) — two terminals
|
||||
dotnet run --project src/InboxIntel.Api # http://localhost:5080
|
||||
cd frontend; npm run dev # http://localhost:5173
|
||||
|
||||
# STAGING (production-shaped, on Windows via Docker)
|
||||
./deploy/up.ps1 -Staging # http://localhost:18081
|
||||
./deploy/down.ps1 -Staging
|
||||
|
||||
# PRODUCTION-shaped run locally (smoke test the real config)
|
||||
./deploy/up.ps1 # http://localhost:8081
|
||||
```
|
||||
|
||||
Dev and staging use **different ports and different volumes**, so they can run at
|
||||
the same time and never share a database.
|
||||
|
||||
---
|
||||
|
||||
## 8. Git hooks
|
||||
|
||||
Installed via `core.hooksPath` (run once per clone):
|
||||
|
||||
```powershell
|
||||
./scripts/install-hooks.ps1
|
||||
```
|
||||
|
||||
- **pre-commit** (fast): blocks committed `.env`/secrets, verifies `dotnet format`.
|
||||
- **pre-push** (thorough): `dotnet build -c Release` + `dotnet test` + frontend
|
||||
build — the same gate CI runs, caught before the push.
|
||||
|
||||
Emergency bypass: `--no-verify`. CI still enforces the gate server-side, so a
|
||||
bypassed push can still be rejected by branch protection.
|
||||
Reference in New Issue
Block a user