From 7f6ed62db886d8079119fbef2e871da0db0722de Mon Sep 17 00:00:00 2001 From: Darwin Wu Date: Sat, 29 Aug 2026 08:20:35 +0000 Subject: [PATCH 1/3] ci: preserve release PR history --- .github/workflows/release-pr.yml | 68 +++++++++++++++++++++++++++++--- docs/releasing.md | 4 +- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 82db1081..b1f461e2 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -40,6 +40,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.event.repository.default_branch }} token: ${{ steps.app-token.outputs.token }} - name: Skip release commits @@ -139,16 +140,71 @@ jobs: if: steps.skip.outputs.skip == 'false' && steps.changes.outputs.ahead != '0' && steps.version-check.outputs.skip == 'false' id: release-branch env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} RELEASE_TAG: ${{ steps.next-version.outputs.content }} GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | branch="release/next" + source_commit="$(git rev-parse HEAD)" + + git fetch origin "${DEFAULT_BRANCH}" + current_source_commit="$(git rev-parse FETCH_HEAD)" + if [ "${source_commit}" != "${current_source_commit}" ]; then + echo "${DEFAULT_BRANCH} advanced during this run; a newer workflow will update the release PR" + exit 0 + fi - git checkout -B "${branch}" bot_name='${{ steps.app-token.outputs.app-slug }}[bot]' bot_id="$(gh api "/users/${bot_name}" --jq .id)" git config user.name "${bot_name}" git config user.email "${bot_id}+${bot_name}@users.noreply.github.com" + + generated_dir="$(mktemp -d)" + trap 'rm -rf "${generated_dir}"' EXIT + cp CHANGELOG.md Cargo.toml Cargo.lock RELEASE_NOTES.md "${generated_dir}/" + git restore CHANGELOG.md Cargo.toml Cargo.lock + rm RELEASE_NOTES.md + + if git ls-remote --exit-code --heads origin "${branch}" >/dev/null; then + git fetch origin "${branch}" + git checkout -B "${branch}" FETCH_HEAD + else + previous_pr="$(gh pr list \ + --base "${DEFAULT_BRANCH}" \ + --head "${branch}" \ + --state all \ + --limit 1 \ + --json number,updatedAt \ + --jq 'sort_by(.updatedAt) | last | .number // empty')" + + if [ -n "${previous_pr}" ]; then + echo "Recovering ${branch} from pull request #${previous_pr}" + git fetch origin "refs/pull/${previous_pr}/head" + git checkout -b "${branch}" FETCH_HEAD + else + git checkout -b "${branch}" "${source_commit}" + fi + fi + + if ! git merge --no-edit "${source_commit}"; then + if ! git rev-parse --verify --quiet MERGE_HEAD >/dev/null; then + exit 1 + fi + + conflicts="$(git diff --name-only --diff-filter=U)" + unexpected_conflicts="$(printf '%s\n' "${conflicts}" | grep -Ev '^(CHANGELOG\.md|Cargo\.toml|Cargo\.lock)$' || true)" + if [ -n "${unexpected_conflicts}" ]; then + echo "Unexpected conflicts while merging ${source_commit}:" + printf '%s\n' "${unexpected_conflicts}" + exit 1 + fi + + git checkout "${source_commit}" -- CHANGELOG.md Cargo.toml Cargo.lock + git add CHANGELOG.md Cargo.toml Cargo.lock + git commit --no-edit + fi + + cp "${generated_dir}"/* . git add CHANGELOG.md Cargo.toml Cargo.lock if git diff --cached --quiet; then @@ -157,7 +213,7 @@ jobs: git commit -m "chore(release): ${RELEASE_TAG}" fi - git push --force-with-lease origin "${branch}" + git push origin "${branch}" echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - name: Mark release build as pending @@ -174,7 +230,7 @@ jobs: -f description='Building installable release packages' - name: Create PR body - if: steps.skip.outputs.skip == 'false' && steps.changes.outputs.ahead != '0' && steps.version-check.outputs.skip == 'false' + if: steps.release-branch.outputs.commit != '' env: RELEASE_TAG: ${{ steps.next-version.outputs.content }} AHEAD: ${{ steps.changes.outputs.ahead }} @@ -195,7 +251,7 @@ jobs: EOF - name: Find existing release PR - if: steps.skip.outputs.skip == 'false' && steps.changes.outputs.ahead != '0' && steps.version-check.outputs.skip == 'false' + if: steps.release-branch.outputs.commit != '' id: existing-pr env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -210,7 +266,7 @@ jobs: echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" - name: Create release PR - if: steps.skip.outputs.skip == 'false' && steps.changes.outputs.ahead != '0' && steps.version-check.outputs.skip == 'false' && steps.existing-pr.outputs.pr_number == '' + if: steps.release-branch.outputs.commit != '' && steps.existing-pr.outputs.pr_number == '' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} RELEASE_TAG: ${{ steps.next-version.outputs.content }} @@ -222,7 +278,7 @@ jobs: --body-file /tmp/release-pr-body.md - name: Update existing release PR - if: steps.skip.outputs.skip == 'false' && steps.changes.outputs.ahead != '0' && steps.version-check.outputs.skip == 'false' && steps.existing-pr.outputs.pr_number != '' + if: steps.release-branch.outputs.commit != '' && steps.existing-pr.outputs.pr_number != '' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} RELEASE_TAG: ${{ steps.next-version.outputs.content }} diff --git a/docs/releasing.md b/docs/releasing.md index 0534cf11..0fbea418 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -5,7 +5,7 @@ Shosai releases are prepared from Conventional Commit titles and reviewed throug ## Release flow 1. Each pull request title must use an allowed Conventional Commit type. The repository must use squash merges with the pull request title as the resulting commit subject. -2. A push to `main` updates `release/next`. Git-cliff calculates the next semantic version, updates the workspace version and lockfile, regenerates `CHANGELOG.md`, and creates or refreshes the release pull request. +2. A push to `main` updates `release/next`. The workflow merges the new `main` revision into the existing release branch, then appends a release commit instead of rewriting branch history. If the branch was deleted after an earlier pull request, the workflow recovers its last pull-request head before appending. Git-cliff calculates the next semantic version, updates the workspace version and lockfile, regenerates `CHANGELOG.md`, and creates or refreshes the release pull request. 3. The exact `release/next` commit is dry-run through the full three-platform package matrix with publishing disabled. The release PR receives a `Release build dry run` commit status, so it cannot be mistaken for a releasable revision while packages are pending or failing. Ordinary feature pull requests do not run this release matrix. 4. Merging that pull request into the default branch validates that its `chore(release): vX.Y.Z` title matches the workspace version and creates the tag on the merge commit. 5. Release jobs repeat the same builds, bundle PDFium, and attach installable packages and checksums to the GitHub release. A manually pushed `vX.Y.Z` tag runs the same publisher after validating the tag against the workspace version. @@ -49,7 +49,7 @@ The workflows rely on these GitHub settings: - Require release pull requests to be up to date before merging, or use a merge queue, so the reviewed changelog covers every commit in the tagged merge. - Install the private `chaba2-bot` GitHub App on this repository with `Contents: write`, `Pull requests: write`, and `Commit statuses: write` repository permissions. - Create the `RELEASE` Actions environment, store the App ID in its `RELEASE_APP_ID` variable, and store the PEM private key in its `RELEASE_APP_SECRET` secret. -- Allow `chaba2-bot` to update the `release/next` branch with force-with-lease. +- Allow `chaba2-bot` to push updates to the `release/next` branch. Only mutation jobs enter the `RELEASE` environment and receive its credentials. Release package builds, including dry runs, remain credential-free. Mutation jobs exchange the credentials for short-lived, repository-scoped `chaba2-bot` installation tokens: release commits and branch pushes, release pull-request creation and updates, dry-run statuses, tags, and GitHub releases. The built-in `GITHUB_TOKEN` is read-only. App-authored branch and tag events are not suppressed by GitHub's recursive-workflow protection, so the normal PR checks and tag workflow can run. From 7514ebb2019dffa0a4adea562ebed8e9e358106a Mon Sep 17 00:00:00 2001 From: Darwin Wu Date: Sat, 29 Aug 2026 19:47:35 +0000 Subject: [PATCH 2/3] ci: trigger checks From 8f456d44dcd3e1dd25191d5e5ade842c46ef52a9 Mon Sep 17 00:00:00 2001 From: Darwin Wu Date: Mon, 31 Aug 2026 18:44:55 +0000 Subject: [PATCH 3/3] ci: run checks for release workflow changes --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 569c1e44..27cebe76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ on: - "flake.nix" - "flake.lock" - ".github/workflows/ci.yml" + - ".github/workflows/release-pr.yml" pull_request: branches: [main] paths: @@ -29,6 +30,7 @@ on: - "flake.nix" - "flake.lock" - ".github/workflows/ci.yml" + - ".github/workflows/release-pr.yml" jobs: fmt: