ci(release): keep the changelog out of a protected branch
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (push) Skipped
CI / e2e (push) Skipped
main is protected (enable_push: false, empty whitelist), so @semantic-release/git's commit-back is rejected by the pre-receive hook -- and it would be rejected *after* the tag was pushed, leaving a tagged release the run then reports as failed. Found by trying to push this branch to main. Whitelisting the CI user was the alternative and is declined: it weakens a protection someone set deliberately and lets a bot push to main without the checks every human PR has to pass. So the release page is the changelog. The changelog plugin now writes a gitignored .release-notes.md, which exists only to carry the notes into gitea-release.sh without interpolating them into a shell command, and CHANGELOG.md is a signpost -- a file claiming to be a changelog while silently never updating is worse than no file. Tags are not protected, so the tag push is unaffected.
This commit is contained in:
@@ -74,12 +74,13 @@ jobs:
|
|||||||
git config --global --add safe.directory /src
|
git config --global --add safe.directory /src
|
||||||
git -C /src log --oneline -1
|
git -C /src log --oneline -1
|
||||||
|
|
||||||
# The @semantic-release/git plugin pushes a `chore(release): x.y.z`
|
# Nothing currently pushes a `chore(release):` commit — main is a
|
||||||
# commit back to main, which is a push to the branch this workflow
|
# protected branch, so .releaserc.yml carries no @semantic-release/git
|
||||||
# runs on. Guarded here rather than by [skip ci], whose handling in
|
# and the release page is the changelog. This guard is kept for the
|
||||||
# Gitea is one more thing that would have to be verified — and this
|
# day someone adds that plugin back: without it the commit-back is a
|
||||||
# is a two-line test of a fact we control.
|
# push to the branch this workflow runs on, and the loop is a release
|
||||||
- name: Skip the changelog commit
|
# per release. Six lines against that is cheap.
|
||||||
|
- name: Skip a changelog commit, if one ever exists
|
||||||
id: guard
|
id: guard
|
||||||
working-directory: /src
|
working-directory: /src
|
||||||
run: |
|
run: |
|
||||||
@@ -161,7 +162,6 @@ jobs:
|
|||||||
-p @semantic-release/commit-analyzer@13 \
|
-p @semantic-release/commit-analyzer@13 \
|
||||||
-p @semantic-release/release-notes-generator@14 \
|
-p @semantic-release/release-notes-generator@14 \
|
||||||
-p @semantic-release/changelog@7 \
|
-p @semantic-release/changelog@7 \
|
||||||
-p @semantic-release/git@11 \
|
|
||||||
-p @semantic-release/exec@7 \
|
-p @semantic-release/exec@7 \
|
||||||
-p conventional-changelog-conventionalcommits@9 \
|
-p conventional-changelog-conventionalcommits@9 \
|
||||||
semantic-release \
|
semantic-release \
|
||||||
|
|||||||
@@ -84,3 +84,7 @@ build/android/build/
|
|||||||
build/android/.gradle/
|
build/android/.gradle/
|
||||||
build/android/gen/
|
build/android/gen/
|
||||||
build/android/overlay.json
|
build/android/overlay.json
|
||||||
|
|
||||||
|
# Written by @semantic-release/changelog purely to carry the release notes
|
||||||
|
# into scripts/gitea-release.sh; the release page is the changelog.
|
||||||
|
.release-notes.md
|
||||||
|
|||||||
+25
-16
@@ -77,32 +77,41 @@ plugins:
|
|||||||
section: Build
|
section: Build
|
||||||
hidden: true
|
hidden: true
|
||||||
|
|
||||||
# Write CHANGELOG.md.
|
# Render the notes to a file.
|
||||||
#
|
#
|
||||||
# This plugin is load-bearing for more than the changelog: it is how the
|
# **This plugin is here to carry the notes, not to maintain a document.**
|
||||||
# release notes reach the Gitea API *without being interpolated into a
|
# It is how they reach the Gitea API *without being interpolated into a
|
||||||
# shell command*. Release notes are rendered commit messages — arbitrary
|
# shell command*: release notes are rendered commit messages — arbitrary
|
||||||
# text carrying backticks, quotes and `$` — so templating
|
# text carrying backticks, quotes and `$` — so templating
|
||||||
# ${nextRelease.notes} into `publishCmd` would be a shell injection with
|
# ${nextRelease.notes} into `publishCmd` would be a shell injection with
|
||||||
# the commit log as its input. scripts/gitea-release.sh reads the top
|
# the commit log as its input. scripts/gitea-release.sh reads the top
|
||||||
# section of this file instead, and the only thing interpolated below is
|
# section of this file instead, and the only thing interpolated below is
|
||||||
# a semver string.
|
# a semver string.
|
||||||
|
#
|
||||||
|
# The target is a gitignored build artifact rather than CHANGELOG.md,
|
||||||
|
# because nothing commits it back — see below.
|
||||||
- - "@semantic-release/changelog"
|
- - "@semantic-release/changelog"
|
||||||
- changelogFile: CHANGELOG.md
|
- changelogFile: .release-notes.md
|
||||||
changelogTitle: "# Changelog"
|
changelogTitle: "# Release notes"
|
||||||
|
|
||||||
# Create the Gitea release, whose body is that changelog section.
|
# Create the Gitea release, whose body is that section.
|
||||||
# `publish` runs after `prepare`, so the tag already exists by here.
|
# `publish` runs after `prepare`, so the tag already exists by here.
|
||||||
- - "@semantic-release/exec"
|
- - "@semantic-release/exec"
|
||||||
- publishCmd: "./scripts/gitea-release.sh ${nextRelease.version}"
|
- publishCmd: "./scripts/gitea-release.sh ${nextRelease.version}"
|
||||||
|
|
||||||
# Commit the changelog back to main.
|
# **There is deliberately no @semantic-release/git here.**
|
||||||
#
|
#
|
||||||
# This is the push that would otherwise re-enter release.yml; the
|
# `main` is a protected branch with `enable_push: false` and an empty
|
||||||
# workflow guards on this subject rather than trusting [skip ci], whose
|
# push whitelist, so a changelog commit-back would be rejected by the
|
||||||
# handling in Gitea is one more thing that would have to be verified.
|
# pre-receive hook — *after* the tag had already been pushed, leaving a
|
||||||
# The subject must also satisfy scripts/commit-check.sh.
|
# tagged release the run then reported as failed. The alternative was to
|
||||||
- - "@semantic-release/git"
|
# whitelist the CI user, which weakens a protection someone set on
|
||||||
- assets:
|
# purpose and lets a bot push to main without passing the checks every
|
||||||
- CHANGELOG.md
|
# human PR has to.
|
||||||
message: "chore(release): ${nextRelease.version} [skip ci]"
|
#
|
||||||
|
# So the release page is the changelog. Tags are not protected, so the
|
||||||
|
# tag push semantic-release does itself is unaffected. CHANGELOG.md in
|
||||||
|
# the repo is a signpost to the releases page and is not written by any
|
||||||
|
# of this; a file that claimed to be a changelog and silently stopped
|
||||||
|
# updating would be worse than no file at all.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
The changelog is the releases page:
|
||||||
|
|
||||||
|
<https://git.ljones.me/yonlu/yellowjacket/releases>
|
||||||
|
|
||||||
|
Every release there is generated from the Conventional Commits it
|
||||||
|
contains, by `.gitea/workflows/release.yml` on merge to `main`. Each one
|
||||||
|
carries its notes as its body, grouped by change type, with a link to the
|
||||||
|
commit behind every line.
|
||||||
|
|
||||||
|
**This file is not generated and is not a copy of that.** `main` is a
|
||||||
|
protected branch, so nothing pushes a changelog commit back to it — and a
|
||||||
|
file that claimed to be a changelog while silently never updating would
|
||||||
|
be worse than no file at all. `make release-dry` prints what the next
|
||||||
|
merge would release.
|
||||||
|
|
||||||
|
History before `v0.0.1` is in `git log`. The versions before it were cut
|
||||||
|
by hand and are not on the releases page; the entries this file used to
|
||||||
|
hold were generated against a GitHub remote this project no longer has,
|
||||||
|
and every link in them was dead.
|
||||||
|
|||||||
@@ -2060,7 +2060,13 @@ Tests use `database.NewTestDB(t)` for in-memory SQLite, built by the same
|
|||||||
|
|
||||||
## Git Workflow
|
## Git Workflow
|
||||||
|
|
||||||
Feature branches and PRs are the norm, but direct pushes to `main` are allowed. Pre-commit runs vet, lint, codegen check, and frontend typecheck in parallel. Pre-push runs the full test suite.
|
Feature branches and PRs are the only way in: **`main` is a protected
|
||||||
|
branch** (`enable_push: false`, an empty push whitelist, and `CI / check*`
|
||||||
|
+ `CI / e2e*` as required status checks), so a direct push is rejected by
|
||||||
|
the pre-receive hook. This file said otherwise for a long time. Tags are
|
||||||
|
*not* protected, which is what lets `release.yml` push one.
|
||||||
|
|
||||||
|
Pre-commit runs vet, lint, codegen check, and frontend typecheck in parallel. Pre-push runs the full test suite.
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
@@ -2092,9 +2098,19 @@ Four things about it are load-bearing:
|
|||||||
triggers nothing. All four publishers additionally skip `v0.0.0`
|
triggers nothing. All four publishers additionally skip `v0.0.0`
|
||||||
explicitly, cleanly rather than by failing, because a floor is not a
|
explicitly, cleanly rather than by failing, because a floor is not a
|
||||||
shipment.
|
shipment.
|
||||||
- **The changelog commit must not re-enter the workflow.** The `git`
|
- **The release page is the changelog, and that follows from the branch
|
||||||
plugin pushes `chore(release): x.y.z` back to `main`; the job guards on
|
protection.** `@semantic-release/git` would push a `chore(release):`
|
||||||
that subject rather than trusting `[skip ci]`.
|
commit back to `main`, which the pre-receive hook rejects — *after* the
|
||||||
|
tag had been pushed, leaving a tagged release the run then reports as
|
||||||
|
failed. Whitelisting the CI user was the alternative and was declined:
|
||||||
|
it weakens a protection someone set on purpose and lets a bot push to
|
||||||
|
`main` without the checks every human PR passes. So the plugin is
|
||||||
|
absent, `@semantic-release/changelog` writes to a gitignored
|
||||||
|
`.release-notes.md` purely to carry the notes into
|
||||||
|
`scripts/gitea-release.sh`, and `CHANGELOG.md` is a signpost to the
|
||||||
|
releases page rather than a file that would silently stop updating.
|
||||||
|
The workflow keeps its `chore(release):` guard anyway, for the day
|
||||||
|
someone adds the plugin back.
|
||||||
- **An asset upload waits for the release to exist.** semantic-release
|
- **An asset upload waits for the release to exist.** semantic-release
|
||||||
pushes the tag in `prepare` and creates the release in `publish`, so
|
pushes the tag in `prepare` and creates the release in `publish`, so
|
||||||
the tag push that starts these workflows happens *before* there is a
|
the tag push that starts these workflows happens *before* there is a
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ release-dry: ## Print the version a merge to main would release
|
|||||||
-p @semantic-release/commit-analyzer@13 \
|
-p @semantic-release/commit-analyzer@13 \
|
||||||
-p @semantic-release/release-notes-generator@14 \
|
-p @semantic-release/release-notes-generator@14 \
|
||||||
-p @semantic-release/changelog@7 \
|
-p @semantic-release/changelog@7 \
|
||||||
-p @semantic-release/git@11 \
|
|
||||||
-p @semantic-release/exec@7 \
|
-p @semantic-release/exec@7 \
|
||||||
-p conventional-changelog-conventionalcommits@9 \
|
-p conventional-changelog-conventionalcommits@9 \
|
||||||
semantic-release --dry-run --no-ci
|
semantic-release --dry-run --no-ci
|
||||||
|
|||||||
@@ -6,13 +6,17 @@
|
|||||||
# Gitea's API is /api/v1 and @semantic-release/github speaks GitHub's.
|
# Gitea's API is /api/v1 and @semantic-release/github speaks GitHub's.
|
||||||
# That is the whole of the Gitea-shaped work: one POST.
|
# That is the whole of the Gitea-shaped work: one POST.
|
||||||
#
|
#
|
||||||
# **The notes come from CHANGELOG.md, not from an argument.** Release
|
# **The notes come from a file, not from an argument.** Release notes are
|
||||||
# notes are rendered commit messages — arbitrary text carrying backticks,
|
# rendered commit messages — arbitrary text carrying backticks, quotes and
|
||||||
# quotes and `$` — so interpolating ${nextRelease.notes} into a shell
|
# `$` — so interpolating ${nextRelease.notes} into a shell command would
|
||||||
# command would be an injection whose input is the commit log. The
|
# be an injection whose input is the commit log. @semantic-release/changelog
|
||||||
# changelog plugin has already written them to the top of CHANGELOG.md by
|
# has already written them to .release-notes.md by the time `publish` runs,
|
||||||
# the time `publish` runs, so the only thing crossing the shell boundary
|
# so the only thing crossing the shell boundary here is a semver string,
|
||||||
# here is a semver string, which is validated below anyway.
|
# which is validated below anyway.
|
||||||
|
#
|
||||||
|
# That file is a gitignored build artifact, not a document: `main` is a
|
||||||
|
# protected branch, so nothing commits a changelog back to it and the
|
||||||
|
# release page is the changelog. See .releaserc.yml.
|
||||||
#
|
#
|
||||||
# Usage: scripts/gitea-release.sh <version> # e.g. 0.0.1
|
# Usage: scripts/gitea-release.sh <version> # e.g. 0.0.1
|
||||||
#
|
#
|
||||||
@@ -48,10 +52,10 @@ tag="v${version}"
|
|||||||
notes=$(awk '
|
notes=$(awk '
|
||||||
/^## / { seen++; if (seen > 1) exit }
|
/^## / { seen++; if (seen > 1) exit }
|
||||||
seen { print }
|
seen { print }
|
||||||
' CHANGELOG.md)
|
' .release-notes.md)
|
||||||
|
|
||||||
if [ -z "$notes" ]; then
|
if [ -z "$notes" ]; then
|
||||||
echo "gitea-release: found no release section at the top of CHANGELOG.md" >&2
|
echo "gitea-release: found no release section at the top of .release-notes.md" >&2
|
||||||
echo ' the changelog plugin runs in prepare and this runs in publish, so' >&2
|
echo ' the changelog plugin runs in prepare and this runs in publish, so' >&2
|
||||||
echo ' an empty section means the plugin order in .releaserc.yml moved.' >&2
|
echo ' an empty section means the plugin order in .releaserc.yml moved.' >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user