# Contributing to KisaniCal ## Branch workflow ``` feature/* ─PR→ develop ─PR→ main ``` - **`main`** — protected, release-ready. Never commit or push directly. Every change arrives via a reviewed pull request. Pushes to `main` trigger the release workflow (archive → IPA → TestFlight). - **`develop`** — the default working branch and integration target. CI runs on every PR into it. - **`feature/*`** — short-lived branches for a single change, e.g. `feature/voice-quick-add`, `fix/icloud-restore`. Branch off `develop`. ## Day-to-day ```bash git checkout develop git pull git checkout -b feature/my-change # ...work, commit... git push -u origin feature/my-change # then open a PR in Gitea: feature/my-change → develop ``` When `develop` is ready to ship, open a PR **`develop` → `main`**. ## Pull request requirements A PR into `main` (and `develop`) cannot be merged until: 1. **CI passes** — the `build-and-test` job in `.gitea/workflows/ci.yml` builds the app and runs `KisaniCalTests` on an iOS Simulator. 2. **At least 1 approval** — reviewed and approved. 3. **No unresolved change requests** — a rejected review blocks the merge. 4. Your branch is **up to date** with the base branch. Direct pushes, force-pushes, and deletion of `main` are blocked server-side by Gitea branch protection, and a local `pre-push` hook blocks accidental direct pushes to `main` from this clone. ## CI / CD - **CI** (`.gitea/workflows/ci.yml`) — runs on every PR into `main`/`develop` and on pushes to `develop`. Builds + tests. A red check blocks the merge. - **Release** (`.gitea/workflows/release.yml`) — runs only on push to `main` (i.e. after a PR merges). Archives, exports an IPA via `ExportOptions.plist`, and (once configured) uploads to TestFlight. Both workflows run on a **self-hosted macOS runner** with Xcode — there is no Gitea-hosted Mac runner. ## Versioning `MARKETING_VERSION` (e.g. `2.0`) and `CURRENT_PROJECT_VERSION` (build number) live in `project.yml`. The build number must **increase** for every TestFlight upload — App Store Connect rejects a build number it has already seen.