| # Copyright (C) 2026 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # Opens a PR that merges `canary` into `stable` using the same |
| # tree-replacing merge pattern as cut-canary.yml. Manual trigger via |
| # the GitHub Actions UI; a maintainer reviews and merges the PR to |
| # update stable. |
| # |
| # Reads the target version from the top of CHANGELOG (which must be |
| # a released version header 'vX.Y - YYYY-MM-DD:' before running) and |
| # verifies the matching git tag does not already exist. Tag creation |
| # is handled by tag-on-stable-push.yml when the PR is merged. |
| # |
| # Authenticates with an installation token minted from the |
| # `perfetto-automation` GitHub App so the PR open / push events fan |
| # out to other workflow runs (GITHUB_TOKEN pushes do not). |
| |
| name: Promote to stable (open PR merging canary → stable) |
| |
| on: |
| workflow_dispatch: |
| |
| permissions: {} |
| |
| jobs: |
| promote-stable: |
| runs-on: ubuntu-latest |
| environment: perfetto-automation |
| steps: |
| - name: Mint app token |
| id: app-token |
| uses: actions/create-github-app-token@v2 |
| with: |
| app-id: ${{ secrets.PERFETTO_BOT_APP_ID }} |
| private-key: ${{ secrets.PERFETTO_BOT_PRIVATE_KEY }} |
| |
| - name: Checkout stable |
| uses: actions/checkout@v4 |
| with: |
| ref: stable |
| fetch-depth: 0 |
| token: ${{ steps.app-token.outputs.token }} |
| |
| - name: Fetch canary |
| run: git fetch origin canary |
| |
| - name: Short-circuit if stable already matches canary |
| run: | |
| if [[ "$(git rev-parse HEAD^{tree})" == \ |
| "$(git rev-parse origin/canary^{tree})" ]]; then |
| echo "::error::stable tree already matches canary; nothing to promote. Cut canary first." |
| exit 1 |
| fi |
| |
| - name: Extract version from CHANGELOG |
| id: version |
| run: | |
| # First non-whitespace line must be a released version header. |
| # Anything else (e.g. still 'Unreleased:') means the release |
| # hasn't been marked ready. Update CHANGELOG on main, re-cut |
| # canary, and retry. |
| FIRST=$(git show "origin/canary:CHANGELOG" \ |
| | grep -m1 -E '^[^[:space:]]' || true) |
| if ! [[ "$FIRST" =~ ^(v[0-9]+\.[0-9]+)[[:space:]]+-[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}: ]]; then |
| echo "::error::CHANGELOG top entry is not a released version header (got: '$FIRST'). Update CHANGELOG to 'vX.Y - YYYY-MM-DD:' on main, re-cut canary, and retry." |
| exit 1 |
| fi |
| VERSION="${BASH_REMATCH[1]}" |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" |
| echo "::notice::Promoting as $VERSION" |
| |
| - name: Verify tag does not already exist |
| run: | |
| VERSION="${{ steps.version.outputs.version }}" |
| if git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then |
| echo "::error::Tag $VERSION already exists. Bump CHANGELOG to the next version on main and re-cut canary." |
| exit 1 |
| fi |
| |
| - name: Configure git |
| run: | |
| # Email must match the CLA-approved address for the automation app. |
| git config user.name "perfetto-automation[bot]" |
| git config user.email "perfetto-automation-github-app@google.com" |
| |
| - name: Merge canary into stable (replace tree with canary) |
| run: | |
| git merge -s ours origin/canary --no-commit |
| git read-tree -m -u origin/canary |
| git commit -m "Promote canary to stable (${{ steps.version.outputs.version }}, automated)" |
| |
| # Reuse a stable branch name so stale PRs don't pile up when a |
| # promotion is superseded before it's merged. Close any existing |
| # PR and delete the remote branch before pushing the fresh one. |
| - name: Close stale PR and delete stale branch |
| env: |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| run: | |
| BRANCH="dev/github-bot/promote-stable" |
| for N in $(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number'); do |
| echo "Closing stale PR #$N" |
| gh pr close "$N" --comment "Superseded by a newer stable promotion." |
| done |
| git push origin --delete "$BRANCH" || true |
| |
| - name: Push branch and open PR |
| env: |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} |
| run: | |
| VERSION="${{ steps.version.outputs.version }}" |
| BRANCH="dev/github-bot/promote-stable" |
| git push origin "HEAD:refs/heads/$BRANCH" |
| gh pr create \ |
| --base stable \ |
| --head "$BRANCH" \ |
| --title "Promote canary to stable ($VERSION)" \ |
| --body "$(cat <<EOF |
| Automated stable promotion for **$VERSION** from \`canary\`. |
| On merge, \`tag-on-stable-push.yml\` tags \`$VERSION\` and |
| creates the draft release. |
| |
| **Merge with "Create a merge commit"** so the two-parent |
| merge commit survives into \`stable\`'s history. |
| EOF |
| )" |
| echo "::notice::Opened PR from $BRANCH → stable." |