# Git Workflow

PiCloud uses **trunk-based development**: a single long-lived branch (`main`) that is always deployable, with short-lived branches for changes that need review.

This document defines how branches, commits, reviews, and releases work — and how bug fixes flow through the system.

---

## 1. Branches

| Branch | Purpose | Lifetime |
|--------|---------|----------|
| `main` | The trunk. Always green, always deployable. | Permanent |
| `feat/<topic>` | New feature or non-trivial change. | ≤ 2 days |
| `fix/<topic>` | Bug fix (non-critical). | ≤ 1 day |
| `hotfix/<topic>` | Critical bug fix that needs to ship now. | Hours |
| `chore/<topic>` | Refactor, deps update, CI tweak, docs. | ≤ 1 day |
| `release/vX.Y` | Long-lived release branch for back-porting hotfixes to a shipped version. | Per minor version, once we tag releases |

**Rules:**

- Branch directly off `main`. Never branch off another feature branch.
- Keep branches short. If a branch lives longer than 2 days, you're doing too much in one go — split it.
- Branch names use `kebab-case` after the prefix: `feat/script-crud-endpoints`, `fix/executor-timeout-leak`.
- Delete the branch after merge.

---

## 2. Commits

**Format: Conventional Commits.**

```
<type>(<scope>): <subject>

<body — optional, wrapped at 72 chars>

<footer — optional: BREAKING CHANGE, issue refs>
```

**Types:** `feat`, `fix`, `chore`, `refactor`, `docs`, `test`, `perf`, `build`, `ci`.

**Scopes (current crates / areas):** `executor-core`, `orchestrator-core`, `manager-core`, `shared`, `picloud`, `dashboard`, `caddy`, `compose`, `ci`, `docs`.

**Examples:**

```
feat(executor-core): add operation budget enforcement

fix(orchestrator-core): release Postgres pool guard before dispatch

chore(ci): pin Rust toolchain to 1.92.0
```

**Rules:**

- One logical change per commit. If you can't describe it in one line, split it.
- Subject line: imperative, ≤ 72 chars, no trailing period.
- Body explains *why*, not *what* — the diff already shows what.
- No `WIP`, `fixup`, or `oops` commits on `main`. Squash or rewrite before merging.

---

## 3. The Feature Loop

```
                 ┌─────────────────────┐
                 │ Pull latest main    │
                 └──────────┬──────────┘
                            │
                 ┌──────────▼──────────┐
                 │ Branch: feat/<name> │
                 └──────────┬──────────┘
                            │
                 ┌──────────▼──────────┐
                 │ Commit small steps  │◄────┐
                 │ Run tests locally   │     │
                 └──────────┬──────────┘     │
                            │                │
                 ┌──────────▼──────────┐     │
                 │ Open PR to main     │─────┘  (iterate on feedback)
                 │ CI must be green    │
                 └──────────┬──────────┘
                            │
                 ┌──────────▼──────────┐
                 │ Squash-merge to main│
                 │ Delete branch       │
                 └─────────────────────┘
```

**Before opening a PR:**

```sh
git fetch origin
git rebase origin/main        # keep history linear
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --workspace
```

**PR requirements:**

- CI green (fmt, clippy, tests, build).
- At least one approving review for non-trivial changes. Solo dev exception: self-review the diff explicitly before merging.
- PR description names *why* and links to any issue.
- Squash-merge by default → one commit per PR on `main`.

---

## 4. Bug-fix Workflow

Bugs are classified by severity:

### Non-critical bug — normal flow

1. Branch `fix/<topic>` from `main`.
2. Write a failing test that reproduces the bug.
3. Fix it. The test now passes.
4. PR → review → squash-merge.

### Critical / production hotfix

```
                 ┌──────────────────┐
                 │ Bug reported     │
                 └────────┬─────────┘
                          │
              ┌───────────▼───────────┐
              │ Reproduce locally     │  ← required before any fix
              │ Write failing test    │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │ Branch hotfix/<name>  │
              │ from main             │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │ Minimal fix only      │  ← no refactors, no unrelated changes
              │ Test passes           │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │ Fast-track review     │
              │ Merge to main         │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │ Tag patch release     │  (vX.Y.Z+1)
              │ Deploy                │
              └───────────┬───────────┘
                          │
              ┌───────────▼───────────┐
              │ Cherry-pick to        │  ← only if old releases are
              │ release/vX.Y if active│    still in use
              └───────────────────────┘
```

**Critical hotfix rules:**

- A hotfix fixes one bug. Nothing else. No "while I'm here" cleanups.
- The reproduction test stays in the suite forever — it's our guarantee the bug doesn't return.
- If the fix can't be minimal (e.g. requires an architectural change), ship a *mitigation* as the hotfix (feature flag off, rate limit, revert), then schedule the real fix as a normal feature.

---

## 5. Keeping `main` Green

Trunk-based only works if `main` is always green. Mechanisms:

- **CI gate:** PRs cannot merge unless CI passes (`fmt`, `clippy -D warnings`, `cargo test --workspace`, dashboard build).
- **Feature flags:** Code for incomplete features lives on `main` behind a flag, off by default. We finish features in small mergeable slices, not in long-lived branches.
- **Reverts are cheap:** If something slips through and breaks `main`, revert first, debug after. `git revert <merge-sha>` and ship.
- **No force-push to `main`.** Ever.

---

## 6. Releases

Until we ship publicly we deploy off `main` continuously. Once we have external users:

- Tag releases on `main`: `v0.1.0`, `v0.1.1`, `v0.2.0` (SemVer).
- Patch releases (`v0.1.1`) come from hotfixes; bump the patch number on each fix.
- Minor releases (`v0.2.0`) come from accumulated features on `main`.
- For each minor version supported in the wild, create `release/v0.1` and cherry-pick patches there. Otherwise don't bother — single trunk is simpler.

**Tagging:**

```sh
git tag -a v0.1.0 -m "v0.1.0 — MVP"
git push origin v0.1.0
```

---

## 7. What We Do Not Do

- **No GitFlow.** No `develop`, no `release` branches by default, no `next`. They add merge overhead without buying anything at our scale.
- **No long-lived feature branches.** If a feature is too big to land in 2 days, split it. Use a feature flag.
- **No merge commits on `main` from feature branches.** Squash-merge keeps history linear and bisectable.
- **No commits straight to `main`** — even for typo fixes. Go through a PR (it can take 30 seconds to review and merge).
- **No `--no-verify`** to bypass hooks. If a hook fails, fix the underlying issue.
