krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit 262b2ad426

262b2ad426ad0684942c09e688f00faf727add26

parent: c8b514e1a0

Verified · cmc ci/pages: success ci/test: failure

cmc <hello@cleberg.net> · 2026-08-30 16:32 UTC

Replace the GitHub workflows with a gitbay pipeline

The release ran on GitHub Actions and reached it only because gitbay push-mirrors
there. `.gitbay/ci.yml` moves it onto the VPS runner: `test` on every push,
`pages` publishing the site to the pages branch on main, and `release` on a `v*`
tag doing the Linux tarballs, the release, and `cargo publish --locked`.

crates.io trusted publishing supports GitHub Actions, GitLab.com and CircleCI, so
there is no OIDC path from gitbay. The token is a repository secret instead,
which is a real downgrade from a credential that did not exist at rest — it is
the price of the move, and `release.sh` runs publish last so a failure earlier
costs a tag rather than a version.

The runner is Linux and `runner next` claims the oldest pending build with no
platform targeting, so a Mac runner could not be aimed at the darwin builds.
`.githooks/pre-push` builds them on the Mac doing the push, which also makes the
tag a gate: a build failure aborts the push, where release.yml found out after
the tag was public. Uploading waits for the release the tag push creates, so the
hook hands it to a detached `.githooks/upload-macos`.

Tarball layout and checksum format are unchanged from release.yml, verified
against the v0.23.0 assets.

Untested end to end until a tag runs it. The prerequisites it assumes are in
RELEASING.org: musl target and musl-gcc on the runner, a runner key that can push
and call `release create`, `core.hooksPath`, and the crates.io secret.

Layout: unified · split

.gitbay/ci.yml added +27
@@ -0,0 +1,27 @@
1# gitbay CI. Each job becomes one build per push; the runner clones this
2# repository at the pushed commit and runs every step with `sh -c`, stopping at
3# the first failure. Repository secrets arrive as environment variables.
4#
5# The runner is Linux, and `runner next` claims the oldest pending build with no
6# platform targeting, so a second runner could not be aimed at macOS jobs. The
7# darwin tarballs are therefore built on a Mac by .githooks/pre-push at tag time
8# and uploaded to the same release; see RELEASING.org.
9jobs:
10 test:
11 steps:
12 - cargo test --locked
13 - cargo clippy --all-targets --locked -- -D warnings
14 - cargo run --locked -- build docs -o "/tmp/orgo-docs-$GITBAY_SHA" --strict
15 - rm -rf "/tmp/orgo-docs-$GITBAY_SHA"
16
17 # Publishes the documentation site by force-pushing the built output to the
18 # `pages` branch, which gitbay serves at https://orgo.krz.sh. A no-op off main.
19 pages:
20 steps:
21 - sh .gitbay/pages.sh
22
23 # The tag push is the release: binaries, the gitbay release, and crates.io.
24 release:
25 tags: "v*"
26 steps:
27 - sh .gitbay/release.sh
.gitbay/pages.sh added +36
@@ -0,0 +1,36 @@
1#!/bin/sh
2# Publish the documentation site to the `pages` branch, which gitbay serves at
3# https://orgo.krz.sh. Queued on every branch push; does nothing off main.
4#
5# The site is fifteen org files built by the tool that documents them, so this is
6# also the most honest smoke test in the repository: if orgo cannot build its own
7# documentation, the job fails and says so. --strict makes a broken internal link
8# a failure, because docs rot by having their links quietly stop resolving.
9set -eu
10
11if [ "${GITBAY_REF:-}" != "main" ]; then
12 echo "ref is ${GITBAY_REF:-unset}, not main — nothing to publish"
13 exit 0
14fi
15
16tmp=$(mktemp -d)
17trap 'rm -rf "$tmp"' EXIT
18site="$tmp/site"
19work="$tmp/work"
20
21cargo run --release --locked -- build docs -o "$site" --strict
22rm -f "$site/.orgo-cache.json"
23
24# An orphan history: the pages branch carries the built site and nothing else, so
25# each publish replaces it wholesale rather than accumulating every past build.
26git init -q "$work"
27cp -R "$site/." "$work/"
28printf '.orgo-cache.json\n' >"$work/.gitignore"
29git -C "$work" add -A
30git -C "$work" -c user.name=gitbay-ci -c user.email=ci@orgo.krz.sh commit -q \
31 -m "Publish the documentation site
32
33Built from ${GITBAY_SHA} by \`orgo build docs -o _site --strict\`."
34git -C "$work" push -q --force "ssh://git@gitbay.org/${GITBAY_REPO}.git" HEAD:refs/heads/pages
35
36echo "published the site from ${GITBAY_SHA} to the pages branch"
.gitbay/release.sh added +62
@@ -0,0 +1,62 @@
1#!/bin/sh
2# The tag push is the release. Builds the Linux binaries, creates the gitbay
3# release with the tag's own annotation as its notes, attaches the tarballs, and
4# publishes to crates.io.
5#
6# The two darwin tarballs are not built here: this runner is Linux, and gitbay's
7# `runner next` claims the oldest pending build with no platform targeting, so a
8# Mac runner could not be aimed at them. .githooks/pre-push builds them on a Mac
9# before the tag is pushed and uploads them to this release once it exists.
10#
11# crates.io is the one step that cannot be undone — a version can be yanked but
12# never replaced — so it runs last, after the release exists and the binaries are
13# attached. CARGO_REGISTRY_TOKEN is a repository secret (`gitbay repo secret set`).
14set -eu
15
16tag="${GITBAY_REF:?no tag in GITBAY_REF}"
17repo="${GITBAY_REPO:?no repository in GITBAY_REPO}"
18: "${CARGO_REGISTRY_TOKEN:?CARGO_REGISTRY_TOKEN secret is not set}"
19
20# The tag is the source of truth for the version, checked rather than trusted: a
21# release tagged v0.18.0 whose binary reports 0.17.0 is the kind of thing nobody
22# notices for months.
23version=$(cargo metadata --no-deps --format-version 1 |
24 python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])')
25if [ "$tag" != "v$version" ]; then
26 echo "tag $tag does not match Cargo.toml version $version" >&2
27 exit 1
28fi
29
30dist=dist
31mkdir -p "$dist"
32
33# glibc for ordinary distributions, musl for containers and anything older than
34# the runner's glibc — a dynamically linked binary is the usual reason a download
35# does not run.
36for target in x86_64-unknown-linux-gnu x86_64-unknown-linux-musl; do
37 cargo build --release --locked --target "$target"
38 # A tarball rather than a bare binary: it keeps the executable bit through the
39 # download path, and carries the licence with the thing it licenses.
40 staging="orgo-$tag-$target"
41 rm -rf "$staging"
42 mkdir "$staging"
43 cp "target/$target/release/orgo" README.md LICENSE "$staging/"
44 tar czf "$dist/$staging.tar.gz" "$staging"
45 rm -rf "$staging"
46 (cd "$dist" && sha256sum "$staging.tar.gz" >"$staging.tar.gz.sha256")
47done
48
49# Notes come from the annotated tag, so the person cutting the release writes
50# them at the moment they decide to cut it (`git tag -a "$tag" -F notes.md`).
51git tag -l --format='%(contents)' "$tag" |
52 ssh git@gitbay.org release create "$repo" "$tag" --title "${tag#v}" --file -
53
54for f in "$dist"/*; do
55 ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f"
56 echo "attached $(basename "$f")"
57done
58
59# --locked publishes exactly the dependency versions the tests ran against,
60# rather than whatever resolves at publish time.
61cargo publish --locked
62echo "published $tag to crates.io"
.githooks/pre-push added +54
@@ -0,0 +1,54 @@
1#!/bin/sh
2# Build the macOS binaries when a version tag is pushed.
3#
4# gitbay's runner is Linux and has no platform targeting, so the darwin tarballs
5# cannot be built in CI. They are built here instead, on the Mac doing the push,
6# and this is the better place for the gate: a build failure aborts the push, so
7# a tag whose Mac binaries do not compile is never published. GitHub Actions
8# found that out only after the tag was already public.
9#
10# Uploading has to wait: `release asset add` needs the release, and the release is
11# created by the CI job this push triggers. So the upload is handed to a detached
12# helper that waits for the release to appear. Its log, and the tarballs, stay in
13# dist/macos/<tag>/ — rerun .githooks/upload-macos <tag> if it fails.
14#
15# Wired up with: git config core.hooksPath .githooks
16set -eu
17
18root=$(git rev-parse --show-toplevel)
19targets="aarch64-apple-darwin x86_64-apple-darwin"
20
21# stdin: <local ref> <local sha> <remote ref> <remote sha>, one line per ref.
22while read -r _local_ref _local_sha remote_ref _remote_sha; do
23 case "$remote_ref" in
24 refs/tags/v*) ;;
25 *) continue ;;
26 esac
27 tag=${remote_ref#refs/tags/}
28
29 version=$(cargo metadata --no-deps --format-version 1 --manifest-path "$root/Cargo.toml" |
30 python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])')
31 if [ "$tag" != "v$version" ]; then
32 echo "pre-push: tag $tag does not match Cargo.toml version $version" >&2
33 exit 1
34 fi
35
36 dist="$root/dist/macos/$tag"
37 rm -rf "$dist"
38 mkdir -p "$dist"
39
40 for target in $targets; do
41 echo "pre-push: building $target"
42 (cd "$root" && cargo build --release --locked --target "$target")
43 staging="orgo-$tag-$target"
44 rm -rf "$root/$staging"
45 mkdir "$root/$staging"
46 cp "$root/target/$target/release/orgo" "$root/README.md" "$root/LICENSE" "$root/$staging/"
47 (cd "$root" && tar czf "$dist/$staging.tar.gz" "$staging")
48 rm -rf "$root/$staging"
49 (cd "$dist" && shasum -a 256 "$staging.tar.gz" >"$staging.tar.gz.sha256")
50 done
51
52 echo "pre-push: built $tag for $targets; upload waits for the release"
53 nohup "$root/.githooks/upload-macos" "$tag" >>"$dist/upload.log" 2>&1 &
54done
.githooks/upload-macos added +34
@@ -0,0 +1,34 @@
1#!/bin/sh
2# Attach the macOS tarballs built by pre-push to the gitbay release.
3#
4# The release is created by the CI job the tag push triggers, so this waits for it
5# rather than assuming it is there. Run by hand as `.githooks/upload-macos <tag>`
6# if the detached run from the hook failed; the tarballs are already on disk and
7# uploading one twice replaces it rather than duplicating it.
8set -eu
9
10tag=${1:?usage: upload-macos <tag>}
11root=$(git rev-parse --show-toplevel)
12dist="$root/dist/macos/$tag"
13repo=$(git config --get remote.origin.url | sed 's#.*gitbay.org[:/]##; s#\.git$##')
14
15[ -d "$dist" ] || { echo "no build for $tag in $dist" >&2; exit 1; }
16
17# The release job builds two Linux targets before creating the release, so the
18# wait is minutes rather than seconds. Twenty minutes, then give up loudly.
19i=0
20until ssh git@gitbay.org release show "$repo" "$tag" >/dev/null 2>&1; do
21 i=$((i + 1))
22 if [ "$i" -gt 80 ]; then
23 echo "$(date -u +%FT%TZ) release $tag never appeared; rerun this script" >&2
24 exit 1
25 fi
26 sleep 15
27done
28
29for f in "$dist"/*.tar.gz "$dist"/*.sha256; do
30 [ -e "$f" ] || continue
31 ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f"
32 echo "$(date -u +%FT%TZ) attached $(basename "$f")"
33done
34echo "$(date -u +%FT%TZ) macOS assets attached to $tag"
.github/workflows/docs.yml deleted −75
@@ -1,75 +0,0 @@
1name: Docs
2
3# Publish the documentation site to GitHub Pages.
4#
5# The site is fifteen org files under docs/, built by the tool they document — so this
6# job is also the most honest smoke test in the repository: if orgo cannot build its own
7# documentation, the deploy fails and says so.
8#
9# `--strict` makes a broken internal link a build failure. Docs rot by having their links
10# quietly stop resolving, and that is exactly the failure a static site generator is
11# supposed to be able to catch.
12
13on:
14 push:
15 branches: [main]
16 # Templates and the config shape the pages as much as the prose does, and a change to
17 # the renderer changes every page. Anything else — a test, a fixture — cannot.
18 paths:
19 - "docs/**"
20 - "src/**"
21 - "Cargo.toml"
22 - "Cargo.lock"
23 - ".github/workflows/docs.yml"
24 workflow_dispatch:
25
26# Read the repository, write a Pages deployment. Nothing else.
27permissions:
28 contents: read
29 pages: write
30 id-token: write
31
32# One deployment at a time, and let a newer commit win: a queue of doc builds racing each
33# other to publish is worse than the last one arriving late.
34concurrency:
35 group: pages
36 cancel-in-progress: true
37
38jobs:
39 build:
40 runs-on: ubuntu-latest
41 steps:
42 - name: Checkout
43 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
45 - name: Install Rust
46 uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
47 with:
48 toolchain: stable
49
50 - name: Cache cargo
51 uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
52
53 - name: Configure Pages
54 uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
55
56 # Built with the release profile: the docs deploy is also the only place a release
57 # build runs outside a tag, so a break in it surfaces here rather than at release.
58 - name: Build the site
59 run: cargo run --release --locked -- build docs -o docs/_site --strict
60
61 - name: Upload the artifact
62 uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v3.0.1
63 with:
64 path: docs/_site
65
66 deploy:
67 needs: build
68 runs-on: ubuntu-latest
69 environment:
70 name: github-pages
71 url: ${{ steps.deployment.outputs.page_url }}
72 steps:
73 - name: Deploy
74 id: deployment
75 uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
.github/workflows/release.yml deleted −155
@@ -1,155 +0,0 @@
1name: Release
2
3# Build binaries for a tag and attach them to a GitHub release.
4#
5# WHY BINARIES AT ALL, given `cargo install orgo` exists: installing from source needs
6# a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site
7# generator should be able to download one file and point it at their notes.
8#
9# The tag is the source of truth for the version. The build checks it against Cargo.toml
10# rather than trusting them to match, because a release tagged v0.18.0 containing a binary
11# that reports 0.17.0 is the kind of thing nobody notices for months.
12
13on:
14 push:
15 tags: ["v*"]
16 workflow_dispatch:
17 inputs:
18 tag:
19 description: "Tag to build (for a re-run of a failed release)"
20 required: true
21
22env:
23 CARGO_TERM_COLOR: always
24
25jobs:
26 build:
27 name: ${{ matrix.target }}
28 runs-on: ${{ matrix.os }}
29 strategy:
30 fail-fast: false
31 matrix:
32 include:
33 # Apple Silicon and Intel Macs, each built natively on its own runner. The
34 # Intel label moves with the image: macos-13 was retired, and a job asking for
35 # a retired label does not fail — it sits queued until someone notices, which
36 # is how the v0.20.0 release spent fifteen minutes doing nothing.
37 - { os: macos-latest, target: aarch64-apple-darwin }
38 - { os: macos-15-intel, target: x86_64-apple-darwin }
39 # glibc for ordinary distributions, musl for containers and anything older than
40 # the runner's glibc — a dynamically linked binary is the usual reason a
41 # download does not run.
42 - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
43 - { os: ubuntu-latest, target: x86_64-unknown-linux-musl }
44 steps:
45 - name: Checkout
46 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47 with:
48 ref: ${{ github.event.inputs.tag || github.ref }}
49
50 - name: Install Rust
51 uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
52 with:
53 toolchain: stable
54 targets: ${{ matrix.target }}
55
56 - name: Install musl tools
57 if: endsWith(matrix.target, '-musl')
58 run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools
59
60 - name: Check the tag against Cargo.toml
61 shell: bash
62 run: |
63 tag="${{ github.event.inputs.tag || github.ref_name }}"
64 crate=$(cargo metadata --no-deps --format-version 1 \
65 | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])')
66 if [ "$tag" != "v$crate" ]; then
67 echo "tag $tag does not match Cargo.toml version $crate" >&2
68 exit 1
69 fi
70
71 - name: Build
72 run: cargo build --release --locked --target ${{ matrix.target }}
73
74 # A tarball rather than a bare binary: it keeps the executable bit through GitHub's
75 # download path, and carries the licence with the thing it licenses.
76 - name: Package
77 shell: bash
78 run: |
79 staging="orgo-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}"
80 mkdir "$staging"
81 cp "target/${{ matrix.target }}/release/orgo" "$staging/"
82 cp README.md LICENSE "$staging/"
83 tar czf "$staging.tar.gz" "$staging"
84 shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256"
85
86 - name: Upload
87 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
88 with:
89 name: ${{ matrix.target }}
90 path: |
91 *.tar.gz
92 *.tar.gz.sha256
93
94 release:
95 name: publish the release
96 needs: build
97 runs-on: ubuntu-latest
98 permissions:
99 contents: write
100 steps:
101 - name: Download every build
102 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
103 with:
104 merge-multiple: true
105
106 # A draft, deliberately. The release notes are written by a person, and a release
107 # that publishes itself before anyone has read it cannot be edited quietly.
108 - name: Create the draft release
109 uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
110 with:
111 draft: true
112 tag_name: ${{ github.event.inputs.tag || github.ref_name }}
113 files: |
114 *.tar.gz
115 *.tar.gz.sha256
116
117 publish:
118 name: publish to crates.io
119 needs: build
120 runs-on: ubuntu-latest
121 # Named so it can be gated. Adding a required reviewer to this environment in the
122 # repository settings turns a tag push into "waiting for a human", which is the right
123 # shape for the one step in this workflow that cannot be undone: a published version
124 # can be yanked but never replaced.
125 environment: crates-io
126 permissions:
127 # The OIDC token this mints *is* the credential — there is no API token stored in
128 # this repository, and nothing to leak from a compromised job beyond a token that
129 # crates.io revokes when the job ends.
130 id-token: write
131 contents: read
132 steps:
133 - name: Checkout
134 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
135 with:
136 ref: ${{ github.event.inputs.tag || github.ref }}
137
138 - name: Install Rust
139 uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
140 with:
141 toolchain: stable
142
143 # Exchanges GitHub's OIDC token for a short-lived crates.io token, and revokes it
144 # when the job finishes. Configured on crates.io against this repository, this
145 # workflow's filename, and the environment above.
146 - name: Authenticate with crates.io
147 id: auth
148 uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
149
150 # `--locked` publishes exactly the dependency versions the tests ran against, rather
151 # than whatever resolves at publish time.
152 - name: Publish
153 run: cargo publish --locked
154 env:
155 CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
.gitignore +1
@@ -1,2 +1,3 @@
11/target
22_site/
3dist/
RELEASING.org +83 −41
@@ -3,30 +3,65 @@
33Read the content below for the release process.
44
55** Where the repository lives
6=origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues and
7merge requests are. It push-mirrors to =https://github.com/krazywarez/orgo=, tags
8included.
6=origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues, merge
7requests, builds and releases are. It push-mirrors to
8=https://github.com/krazywarez/orgo=, tags included, but nothing in a release depends on
9that mirror any more — it is a copy, not a step.
10
11** What runs where
12Three things happen automatically, and one of them happens on your Mac.
13
14| Trigger | Where | What |
15|--------------------+---------------------------+---------------------------------------------------|
16| Any branch push | gitbay CI (=test= job) | =cargo test=, clippy, and a =--strict= docs build |
17| A push to =main= | gitbay CI (=pages= job) | Publishes the site to the =pages= branch |
18| A =v*= tag push | Your Mac (=pre-push=) | Builds and packages the two darwin tarballs |
19| A =v*= tag push | gitbay CI (=release= job) | Linux tarballs, the release, then crates.io |
20
21The darwin binaries are the one part CI cannot do. The runner is Linux, and gitbay's
22=runner next= claims the oldest pending build with no platform targeting, so a second
23runner could not be aimed at macOS jobs. Building them in =pre-push= is the better place
24anyway: a build failure aborts the push, so a tag whose Mac binaries do not compile is
25never published.
26
27Uploading them has to wait, because =release asset add= needs a release and the release is
28created by the job the tag push triggers. =pre-push= hands that to a detached
29=.githooks/upload-macos=, which waits for the release and then attaches the tarballs. Its
30log and the tarballs stay in =dist/macos/<tag>/=.
931
10That mirror is what makes a release work. The automation is GitHub Actions and nothing
11triggers it directly: a tag pushed to gitbay reaches GitHub within a minute, and
12=.github/workflows/release.yml= runs there. =docs.yml= deploys the documentation site to
13Pages the same way, on a push to =main= that touches =docs/=, =src/=, =Cargo.toml= or
14=Cargo.lock=.
32** Before the first publish
33Once per clone:
1534
16Neither forge runs the tests. =ci.yml= is gone and there is no =.gitbay/ci.yml=, so the
17checks in step 2 are the only gate a release passes.
35#+begin_src sh
36git config core.hooksPath .githooks
37#+end_src
1838
19** Before the first publish
20Nothing to install and no token to store. The release workflow mints a short-lived
21crates.io token with OIDC, configured on crates.io against the GitHub repository, the
22=release.yml= workflow file and the =crates-io= environment that job runs in.
39Once on the Mac that cuts releases — Homebrew's =rust= cannot add targets, so the
40toolchain has to be rustup:
41
42#+begin_src sh
43brew install rustup && rustup default stable
44rustup target add x86_64-apple-darwin
45#+end_src
46
47Once on the VPS runner: a Rust toolchain with =x86_64-unknown-linux-musl= added and
48=musl-gcc= installed, and an SSH key registered on gitbay that can push to the repository
49(the =pages= job) and run =release create= (the =release= job).
50
51Once on the repository — the crates.io token, which is the one credential this setup
52stores:
2353
24=repository= in =Cargo.toml= points at gitbay; =homepage= points at the documentation
25site on Pages.
54#+begin_src sh
55gitbay repo secret set krz/orgo CARGO_REGISTRY_TOKEN
56#+end_src
57
58=repository= in =Cargo.toml= points at gitbay; =homepage= points at
59[[https://orgo.krz.sh]], the documentation site the =pages= job publishes.
2660
2761** Every release
28621. Bump the version in =Cargo.toml=, and build once so =Cargo.lock= follows.
292. Test and package the release.
632. Test and package the release. CI runs the first two on every push, but a release is
64 worth checking before you tag rather than after.
3065
3166 #+begin_src sh
3267 cargo test
@@ -37,49 +72,56 @@ site on Pages.
3772
38733. Build a site you know with =--no-cache= and diff the output against the previous
3974 version's.
404. Commit, tag, push.
754. Commit and push. The =test= and =pages= jobs run.
4176
4277 #+begin_src sh
43 git commit -am "0.18: <what changed>"
44 git tag -a v0.18.0 -m "0.18.0"
45 git push && git push --tags
78 git commit -am "0.24: <what changed>"
79 git push
4680 #+end_src
4781
485. The tag push is the release, by way of the mirror. It builds binaries for macOS
49 (arm64 and x86_64) and Linux (gnu and musl), opens a /draft/ GitHub release with them
50 attached, and runs =cargo publish --locked= — there is nothing to publish by hand, and
51 running =cargo publish= locally now only fails on a version crates.io already has. The
52 build checks the tag against =Cargo.toml= rather than trusting the two to match.
825. Write the release notes, then tag with them. The =release= job uses the tag's own
83 annotation as the release notes, so the notes are written at the moment you decide to
84 cut the release rather than afterwards.
5385
54 =gh= talks to the mirror, so it is how you watch the run:
86 #+begin_src sh
87 $EDITOR notes.md
88 git tag -a v0.24.0 -F notes.md
89 git push --tags
90 #+end_src
91
92 The push blocks while your Mac builds both darwin targets. If either fails, the push
93 is refused and nothing is tagged.
94
956. The tag push is the release. It checks the tag against =Cargo.toml= rather than
96 trusting the two to match, builds the gnu and musl tarballs, creates the release with
97 your notes, attaches the tarballs, and runs =cargo publish --locked=. The macOS
98 tarballs arrive a few minutes later from the detached uploader.
5599
56100 #+begin_src sh
57 gh run list -R krazywarez/orgo --limit 3
101 gitbay build list krz/orgo
102 gitbay release show krz/orgo v0.24.0
58103 #+end_src
59104
60105 Publishing is the one step that cannot be undone: a version can be yanked but never
61 replaced. The publish job runs in the =crates-io= environment so it can be held —
62 add a required reviewer to that environment in the GitHub repository settings and a
63 tag push waits for a human before it reaches crates.io.
106 replaced. It runs last, after the release exists and the binaries are attached, so a
107 failure earlier in the job costs you a tag rather than a version number.
64108
65 A release that fails halfway is re-run from the Actions tab: the workflow takes the
66 tag to build as an input, so it does not need a second tag.
67
686. Write the release notes and publish the draft GitHub release.
697. Release on gitbay, which has no automation of its own. Same notes, same binaries.
1097. If the macOS upload did not land — check =dist/macos/v0.24.0/upload.log= — rerun it.
110 The tarballs are already built, and uploading one twice replaces it.
70111
71112 #+begin_src sh
72 gitbay release create v0.18.0 --title 0.18.0 --file - < notes.md
73 gh release download v0.18.0 -R krazywarez/orgo -D dist
74 for f in dist/*; do gitbay release asset add v0.18.0 "$(basename "$f")" < "$f"; done
113 .githooks/upload-macos v0.24.0
75114 #+end_src
76115
77116** If a release goes wrong
78117Yank rather than delete, and ship a fix as a new version:
79118
80119#+begin_src sh
81cargo yank --version 0.18.0
120cargo yank --version 0.24.0
82121#+end_src
83122
84123Yanking stops new dependents from selecting it; anyone who already has it keeps working.
85Then release =0.18.1= with the fix.
124Then release =0.24.1= with the fix.
125
126A build that failed halfway is re-run with =gitbay build trigger=, which does not need a
127second tag.