#!/usr/bin/env sh # # The markdown body for a release: what went live since the previous one. # # release-notes.sh # # A release BUILDS NOTHING now (M314 step 7). The merge to `main` already published # `:latest`, `:` and both channel feeds, so a tag rebuilding that same source # would produce identical artifacts and re-push `:` with different bytes — # which rule 145 forbids even when the bytes match. # # So what is a release FOR? Note 3127 §5 answers it: the changelog. There are two # halves to "what am I running" and the version answers only the first — # # which build is this the footer, /api/config, the APK's versionName # what is in it that was not ← this # in the one I ran last month # # DERIVED FROM GIT, not hand-maintained. A CHANGELOG.md drifts into being # aspirational — it records what someone meant to ship. `git log` records what # shipped, and cannot say otherwise. set -eu cd "$(git rev-parse --show-toplevel)" tag="${1:?usage: release-notes.sh }" # The previous release tag, by DATE rather than by name. # # `v*` only: this repo also carries `dev` and `stable` tags, which are the fixed-tag # pointer releases the updater reads. They move constantly and are not releases in # this sense; sorting them in would make "the previous release" mean whichever # channel published most recently. # # Excludes the tag being described, so re-running on an existing tag still produces # the range that tag covers rather than an empty one. prev="$(git tag -l 'v*' --sort=-creatordate | grep -vxF "$tag" | head -1 || true)" if [ -n "$prev" ]; then range="$prev..$tag" header="Changes since \`$prev\`." else # The first release. Everything is new, and listing the entire history would be # noise — say so instead. range="$tag" header="First release." fi printf 'ThoughtSync %s\n\n%s\n\n' "$tag" "$header" # `--no-merges`: a merge commit's subject is "Merge branch ..." and says nothing # about what shipped. The commits it brought in are listed individually, which is # what somebody reading this wants. # # `%s` alone, not `%s (%h)`: the sha is in the forge's own view of the release and # a reader chasing a specific change clicks through rather than copying a hash out # of prose. # CAPPED, because an unbounded list is not a changelog — it is a wall. # # The first dated release spans everything since `v0.1.0` — 181 commits at the # time of writing: nobody reads that, and burying twelve interesting changes in it is worse # than not writing one. Later releases will be short and the cap will never bite. # # The most RECENT are kept, not the oldest, and the count of what was dropped is # stated — a truncated list that does not say it is truncated is a lie. CAP=60 total="$(git log --no-merges --format='%s' "$range" | wc -l | tr -d ' ')" git log --no-merges --reverse --format='- %s' "$range" | tail -"$CAP" if [ "$total" -gt "$CAP" ]; then printf '\n_...and %s earlier commits in this range, omitted for length._\n' \ "$((total - CAP))" fi printf '\n' printf '%s\n' "_No artifacts here. Builds reach users from \`main\`: the desktop and Android" printf '%s\n' "channels and the server image all publish on merge, with no tag required. This" printf '%s\n' "release is a bookmark — it names a moment and says what was in it._"