From 705ad5af26f7c239223ccb2875a76c892ccb2c5a Mon Sep 17 00:00:00 2001 From: Esa Kataja Date: Sat, 5 Sep 2026 20:52:16 +0300 Subject: [PATCH] Cut releases from main only, enforced where it can be main is protected on the remote and takes no direct pushes, so a release arrives as a pull request from dev. Document that flow. `make image` refuses to run outside main. That check has to live locally: the tag and the image are made before anything reaches the remote, so branch protection cannot catch a release built from the wrong branch. A pre-commit hook was tried and dropped. It needed installing per clone, so it enforced nothing that the remote was not already enforcing, while implying it did. --- Makefile | 8 ++++++++ PRD.md | 5 ++++- README.md | 25 ++++++++++++++++++++----- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2ad5146..47af48c 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,14 @@ fix: ## Format Go and templ sources, tidy go.mod image: ## Build and tag an image as vYYYYMMDD-N. Creates a git tag. @test -n "$(FOODSTER_REPO)" || { echo "set FOODSTER_REPO in .env"; exit 1; } + @# A release tag must point into main, or the tag records a commit that + @# was never released. + @branch=$$(git symbolic-ref --short HEAD); \ + if [ "$$branch" != "main" ]; then \ + echo "releases are cut from main, not $$branch:"; \ + echo " git switch main && git merge --ff-only dev"; \ + exit 1; \ + fi @day=$$(date +%Y%m%d); \ tag="v$$day-$$(( $$(git tag -l "v$$day-*" | wc -l) + 1 ))"; \ echo "==> $$tag"; \ diff --git a/PRD.md b/PRD.md index b53f1e6..de48db2 100644 --- a/PRD.md +++ b/PRD.md @@ -360,7 +360,10 @@ on the server and run with Docker Compose. - **Branches**: `main` carries released versions only, so its history is the deployment history and every release tag points into it. Development happens - on `dev` and merges into `main` when a release is cut. + on `dev`, and `main` is protected on the remote: it accepts no direct + pushes, so a release arrives as a pull request from `dev`. `make image` + additionally refuses to run outside `main` — that one has to be local, + because the tag and the image are made before anything reaches the remote. - **Versioning**: CalVer `vYYYYMMDD-N`, where `N` is the Nth build of that day. `make image` derives `N` by counting the day's existing git tags, creates the new tag, and bakes the version into the binary through diff --git a/README.md b/README.md index 0354cfb..1371830 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,31 @@ One static Go binary. No Node.js, no bundler, no separate database server. ## Branches `main` holds released versions only. Every release tag points at a commit on -`main`, so its history is the deployment history. +`main`, so its history is the deployment history. **Nothing is committed to +`main` directly** — it moves only by fast-forwarding `dev` into it. -All development happens on `dev`. Merge into `main` when cutting a release, -then build and push the image from there. +All development happens on `dev`. `main` is protected on the remote: it takes +no direct pushes, so a release arrives through a pull request. ```sh -git switch dev # where the work happens -git switch main && git merge dev && make release +git switch dev # where the work happens +# ... commits ... +make check # lint, unit tests, smoke +git push origin dev + +tea pr create --base main --head dev # or open it in the forge +# merge the pull request, then: + +git switch main && git pull --ff-only +make release # builds, tags vYYYYMMDD-N, pushes the image +git push origin --tags ``` +`make image` additionally refuses to run from any branch but `main`, so a +release tag can never point at a commit that was not released. That check +lives locally because it has to: tags and images are built before anything +reaches the remote, so protection there cannot catch it. + ## Quick start ```sh