name: Promote release candidate
permissions:
contents: write
on:
workflow_dispatch:
jobs:
promote:
name: Promote release candidate
runs-on: ubuntu-latest
steps:
- name: Refuse to run outside a release-candidate branch
run: |
# An RC branch is named `<version>-rc` (no numeric suffix). Guard
# against dispatching against main / stable / a v0.x line.
case "${{ github.ref_name }}" in
*-rc) echo "Promoting RC branch ${{ github.ref_name }}." ;;
*)
echo "::error::promote must be dispatched on a *-rc branch, not '${{ github.ref_name }}'."
exit 1
;;
esac
- name: Checkout RC branch
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with:
ref: ${{ github.ref_name }}
fetch-depth: 0
token: ${{ secrets.RELEASE_PLZ_ORG_TOKEN }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@d1031067263f94b142dd6c0ce24c5eb9d02d52a0 with:
toolchain: stable
- name: Compute promotion versions
id: ver
run: |
set -euo pipefail
# The candidate version currently on the RC branch, e.g. 0.29.0-rc.3.
# c2patool is a single-package (non-workspace) crate, so this is just
# the one package's version -- no need to select by name.
RC_C2PATOOL=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[0].version')
# The outgoing `stable` version is what is on crates.io (stable tracks
# the latest published release). It names the snapshot branch and is
# the version we reset to so release-plz recomputes the train's bump.
git fetch origin stable
OLD_C2PATOOL=$(git show origin/stable:Cargo.toml | sed -n 's/^version = "\(.*\)"/\1/p' | head -1)
if [ -z "$OLD_C2PATOOL" ]; then
echo "::error::Could not read the outgoing c2patool version from origin/stable:Cargo.toml."
exit 1
fi
# e.g. 0.28.3 -> v0.28.
OLD_LINE="v$(echo "$OLD_C2PATOOL" | cut -d. -f1,2)"
{
echo "rc_c2patool=$RC_C2PATOOL"
echo "old_c2patool=$OLD_C2PATOOL"
echo "old_line=$OLD_LINE"
} >> "$GITHUB_OUTPUT"
echo "Promoting ${{ github.ref_name }} ($RC_C2PATOOL) onto stable; resetting to $OLD_C2PATOOL so the release-PR job computes the train bump."
- name: Snapshot outgoing stable line
run: |
set -euo pipefail
LINE="${{ steps.ver.outputs.old_line }}"
if git ls-remote --exit-code --heads origin "$LINE" >/dev/null 2>&1; then
echo "Snapshot branch $LINE already exists; leaving it as-is."
else
# Point the retiring line at the current tip of stable so critical
# backports to the old release can still target it.
git push origin "refs/remotes/origin/stable:refs/heads/$LINE"
echo "Created snapshot branch $LINE from stable."
fi
- name: Reset candidate to the last published version
run: |
set -euo pipefail
# Move c2patool's own version back to the last published number.
# `stable` then sits level with crates.io, so the force-push below
# does not trigger an immediate publish; the bump is left for the
# release-PR job to compute and for a human to review.
#
# A plain string replace, not `cargo set-version` (from cargo-edit) --
# that tool refuses to ever move a version backward, with no
# override, so it cannot perform the reset this step exists to do.
# Only the FIRST `version = "..."` line in Cargo.toml is touched (the
# `[package]` version), so this can never collide with the `c2pa`
# dependency's own `version = "..."` entries further down the file.
RC_C2PATOOL="${{ steps.ver.outputs.rc_c2patool }}"
OLD_C2PATOOL="${{ steps.ver.outputs.old_c2patool }}"
if ! grep -q "^version = \"$RC_C2PATOOL\"" Cargo.toml; then
echo "::error::Expected to find version = \"$RC_C2PATOOL\" as the package version in Cargo.toml."
exit 1
fi
sed -i "0,/^version = \"$RC_C2PATOOL\"/s//version = \"$OLD_C2PATOOL\"/" Cargo.toml
- name: Force-push baked train onto stable
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# The reset above is a no-op when the candidate branch's version was
# never bumped away from the last published version in the first
# place (e.g. release-rc.yml didn't run when the branch was cut), so
# there is nothing to commit here. That's fine -- the candidate's
# baked commits are still promoted by force-pushing HEAD below.
if git diff --quiet; then
echo "No version reset needed; promoting ${{ github.ref_name }}'s existing commits as-is."
else
git commit -am "chore: promote ${{ github.ref_name }} onto stable (reset to ${{ steps.ver.outputs.old_c2patool }} for release-plz)"
fi
# Force-push rather than merge so stable's history becomes exactly the
# coherent set of changes baked on the RC branch (see
# docs/release-process.md). The push triggers release.yml, whose
# release-PR job opens the reviewed version + changelog PR. Pushed
# with a PAT so that the downstream workflow actually runs -- a push
# made with the default GITHUB_TOKEN does not cascade into other workflows.
git push --force origin "HEAD:stable"
- name: Summary
run: |
{
echo "## Release candidate promoted"
echo ""
echo "- Branch \`${{ github.ref_name }}\` (candidate \`${{ steps.ver.outputs.rc_c2patool }}\`) force-pushed onto \`stable\`, reset to \`${{ steps.ver.outputs.old_c2patool }}\`."
echo "- Outgoing \`stable\` snapshotted as \`${{ steps.ver.outputs.old_line }}\` for backports."
echo ""
echo "The release-PR job in \`release.yml\` will open the reviewed version + changelog PR on \`stable\`. **Merging that PR publishes to crates.io.**"
echo ""
echo "If release-plz under-bumps (the train is breaking by policy but \`cargo-semver-checks\` detected no break), force the final number on that PR with \`release-plz set-version\`."
} >> "$GITHUB_STEP_SUMMARY"