Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/backport_create_releases.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: "Backport: Create Releases"

on:
workflow_dispatch:
inputs:
issue:
description: 'The Backport Tracking Issue Number'
required: true
type: string
workflow_call:
inputs:
issue:
description: 'The Backport Tracking Issue Number'
required: true
type: string

permissions:
contents: write
issues: write
pull-requests: write

jobs:
backport_create_releases:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-version: 1.20.0

- name: Configure Git Identity
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Run Backport Create Releases Pipeline
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE: ${{ inputs.issue }}
run: |
bazel run //tools/private/release -- \
backport-create-releases --issue=$ISSUE --no-dry-run
76 changes: 76 additions & 0 deletions .github/workflows/backport_prepare.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: "Backport: Prepare"

on:
workflow_dispatch:
inputs:
issue:
description: 'The Backport Tracking Issue Number'
required: false
type: string
pr:
description: 'PR to backport (e.g. #123) (if no issue)'
required: false
type: string
from_minor:
description: 'From minor version (e.g. 1.7) (if no issue)'
required: false
type: string
to_minor:
description: 'To minor version (e.g. 1.9) (if no issue, optional)'
required: false
type: string
workflow_call:
inputs:
issue:
description: 'The Backport Tracking Issue Number'
required: false
type: string

permissions:
contents: write
issues: write
pull-requests: write

jobs:
backport_prepare:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup Bazel
uses: bazel-contrib/setup-bazel@0.19.0
with:
bazelisk-version: 1.20.0

- name: Configure Git Identity
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Run Backport Preparation Pipeline
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE: ${{ inputs.issue }}
PR: ${{ inputs.pr }}
FROM_MINOR: ${{ inputs.from_minor }}
TO_MINOR: ${{ inputs.to_minor }}
run: |
ARGS=()
if [ -n "$ISSUE" ]; then
ARGS+=("--issue=$ISSUE")
else
if [ -n "$PR" ]; then
ARGS+=("--pr=$PR")
fi
if [ -n "$FROM_MINOR" ]; then
ARGS+=("--from-minor=$FROM_MINOR")
fi
if [ -n "$TO_MINOR" ]; then
ARGS+=("--to-minor=$TO_MINOR")
fi
fi
bazel run //tools/private/release -- \
backport-prepare "${ARGS[@]}" --no-dry-run
98 changes: 68 additions & 30 deletions .github/workflows/on_comment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,45 +36,67 @@ jobs:
IS_PR: "${{ github.event.issue.pull_request != null }}"
EVENT_NUMBER: "${{ github.event.issue.number }}"
HAS_RELEASE_LABEL: "${{ contains(github.event.issue.labels.*.name, 'type: release') }}"
HAS_BACKPORT_LABEL: "${{ contains(github.event.issue.labels.*.name, 'type: backport-pr') }}"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "$IS_PR" = "false" ] && [ "$HAS_RELEASE_LABEL" = "true" ]; then
if [ "$IS_PR" = "false" ]; then
# Set issue number for non-PR issues (release or backport tracking issues)
issue_number=$EVENT_NUMBER
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/create-rc([[:space:]]|$)'; then
echo "command=create-rc" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/prepare([[:space:]]|$)'; then
echo "command=prepare" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/process-backports([[:space:]]|$)'; then
echo "command=process-backports" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/add-backports([[:space:]]|$)'; then
args=$(echo "$COMMENT_BODY" | grep -E '^[[:space:]]*/add-backports([[:space:]]|$)' | sed -E 's/^[[:space:]]*\/add-backports[[:space:]]*//')
args=$(echo "$args" | sed -e 's/^[[:space:],]*//' -e 's/[[:space:],]*$//')
csv=$(echo "$args" | sed -E 's/[[:space:],]+/ /g' | tr ' ' ',')
if [ -n "$csv" ]; then
echo "command=add-backports" >> "$GITHUB_OUTPUT"
echo "backports=$csv" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_ENV"

# Check if it's a release tracking issue
if [ "$HAS_RELEASE_LABEL" = "true" ]; then
# Handle /create-rc comment
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/create-rc([[:space:]]|$)'; then
echo "command=create-rc" >> "$GITHUB_OUTPUT"
# Handle /prepare comment
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/prepare([[:space:]]|$)'; then
echo "command=prepare" >> "$GITHUB_OUTPUT"
# Handle /process-backports comment
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/process-backports([[:space:]]|$)'; then
echo "command=process-backports" >> "$GITHUB_OUTPUT"
# Handle /add-backports comment
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/add-backports([[:space:]]|$)'; then
args=$(echo "$COMMENT_BODY" | grep -E '^[[:space:]]*/add-backports([[:space:]]|$)' | sed -E 's/^[[:space:]]*\/add-backports[[:space:]]*//')
args=$(echo "$args" | sed -e 's/^[[:space:],]*//' -e 's/[[:space:],]*$//')
csv=$(echo "$args" | sed -E 's/[[:space:],]+/ /g' | tr ' ' ',')
if [ -n "$csv" ]; then
echo "command=add-backports" >> "$GITHUB_OUTPUT"
echo "backports=$csv" >> "$GITHUB_OUTPUT"
else
echo "command=none" >> "$GITHUB_OUTPUT"
echo "Error: No PRs specified for add-backports." >&2
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f "content=-1"
fi
# Handle /promote comment
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/promote([[:space:]]|$)'; then
echo "command=promote" >> "$GITHUB_OUTPUT"
else
echo "command=none" >> "$GITHUB_OUTPUT"
fi
# Check if it's a backport tracking issue
elif [ "$HAS_BACKPORT_LABEL" = "true" ]; then
# Handle /prepare comment for backports
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/prepare([[:space:]]|$)'; then
echo "command=backport-prepare" >> "$GITHUB_OUTPUT"
# Handle /create-releases comment for backports
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/create-releases([[:space:]]|$)'; then
echo "command=backport-create-releases" >> "$GITHUB_OUTPUT"
else
echo "command=none" >> "$GITHUB_OUTPUT"
echo "Error: No PRs specified for add-backports." >&2
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f "content=-1"
fi
elif echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/promote([[:space:]]|$)'; then
echo "command=promote" >> "$GITHUB_OUTPUT"
echo "issue_number=$issue_number" >> "$GITHUB_OUTPUT"
else
echo "command=none" >> "$GITHUB_OUTPUT"
fi
elif [ "$IS_PR" = "true" ]; then
pr_number=$EVENT_NUMBER
# Handle /backport comment on PR
if echo "$COMMENT_BODY" | grep -qE '^[[:space:]]*/backport([[:space:]]|$)'; then
echo "command=pr-backport" >> "$GITHUB_OUTPUT"
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -134,7 +156,23 @@ jobs:
call_promote:
needs: parse_comment
if: needs.parse_comment.outputs.command == 'promote'
uses: ./.github/workflows/release_promote_rc.yaml
uses: ./.github/workflows/release_promote.yaml
with:
issue: ${{ needs.parse_comment.outputs.issue_number }}
secrets: inherit
secrets: inherit

call_backport_prepare:
needs: parse_comment
if: needs.parse_comment.outputs.command == 'backport-prepare'
uses: ./.github/workflows/backport_prepare.yaml
with:
issue: ${{ needs.parse_comment.outputs.issue_number }}
secrets: inherit

call_backport_create_releases:
needs: parse_comment
if: needs.parse_comment.outputs.command == 'backport-create-releases'
uses: ./.github/workflows/backport_create_releases.yaml
with:
issue: ${{ needs.parse_comment.outputs.issue_number }}
secrets: inherit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Release: Promote RC"
name: "Release: Promote"

on:
workflow_dispatch:
Expand Down Expand Up @@ -60,7 +60,7 @@ jobs:
ARGS+=("--remote=origin")
ARGS+=("--no-dry-run")

bazel run //tools/private/release -- promote-rc "${ARGS[@]}"
bazel run //tools/private/release -- promote "${ARGS[@]}"

publish:
needs: promote
Expand Down
64 changes: 63 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,69 @@ If a backport fails to process (e.g., due to cherry-pick conflicts):
cherry-pick the PR, resolve conflicts, push to remote, and manually check
the box on the tracking issue checklist with `status=done` metadata.

## Patch release with cherry picks
## Automated patch releases to multiple versions

If you need to backport a PR to multiple older active release branches (e.g.,
backporting a fix to `1.7`, `1.8`, and `1.9` when the latest release is
`2.2.0`), you can automate the creation of release tracking issues and
verification of cherry-picks using a Backport Tracking Issue.

### Steps

1. **Create a Backport Tracking Issue**: Create a new issue on GitHub with
the label `type: backport-pr`.
* The title should describe the backport, e.g., `Backport: #1234`
(referencing the PR to backport).
* The body must contain the target range in the following format:
```markdown
* PR: #1234
* From version: 1.7
* To version: 2.2
```
This tells the tool to target all active release branches between
`release/1.7` and `release/2.2` inclusive.

2. **Prepare the Backports**: Trigger the preparation by commenting `/prepare`
on the backport tracking issue, or by manually running the [Backport:
Prepare](https://github.com/bazel-contrib/rules_python/actions/workflows/backport_prepare.yaml)
workflow with the issue number.
* The workflow will checkout each release branch in the range, attempt to
cherry-pick the PR, and verify if the changelog can be updated.
* It will then update the backport tracking issue body with a list of
tasks:
* `Verify apply <minor_version>`: Automatically checked if the
cherry-pick succeeded, or left unchecked with
`status=failed-conflict` or `status=failed-changelog` if it failed.
* `Release issue <next_version>`: A task to initiate the release for
each target version.

3. **Resolve Conflicts (if any)**: If any `Verify apply` task failed:
* You must resolve the conflict manually on that specific release branch
(checkout branch, cherry-pick, resolve conflicts, push to remote).
* Once resolved, manually check the corresponding `Verify apply` task box
on the tracking issue and set its metadata to `status=success` (e.g.,
`- [x] Verify apply 1.8 | status=success`).

4. **Initiate Releases**: Once all `Verify apply` tasks are successful
(either automatically or after manual resolution), comment
`/create-releases` on the backport tracking issue, or manually run the
[Backport: Create
Releases](https://github.com/bazel-contrib/rules_python/actions/workflows/backport_create_releases.yaml)
workflow.
* This will automatically create a standard Release Tracking Issue for
each target version (e.g., `Release 1.7.1`, `Release 1.8.1`, etc.).
* For patch releases, the created release tracking issues will have `Tag
RC` tasks automatically removed, as release candidates are not
required for patch releases.
* The backport PR will be automatically added to the checklist of each
created release tracking issue.

5. **Execute Releases**: Follow the standard release process for each created
release tracking issue. Since these are patch releases, you can skip the
`/create-rc` step and comment `/promote` directly to tag and publish the
release from the release branch head.

## Manual patch release with cherry picks

If a patch release from head would contain changes that aren't appropriate for
a patch release, then the patch release needs to be based on the original
Expand Down
23 changes: 21 additions & 2 deletions tests/tools/private/release/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ py_library(
name = "release_test_helper",
srcs = ["release_test_helper.py"],
deps = [
"//tools/private/release:mock_gh",
"//tools/private/release:release_lib",
],
)
Expand Down Expand Up @@ -97,8 +98,8 @@ py_test(
)

py_test(
name = "promote_rc_test",
srcs = ["promote_rc_test.py"],
name = "promote_test",
srcs = ["promote_test.py"],
deps = [
":release_test_helper",
"//tools/private/release:release_lib",
Expand Down Expand Up @@ -130,3 +131,21 @@ py_test(
"@dev_pip//packaging",
],
)

py_test(
name = "backport_prepare_test",
srcs = ["backport_prepare_test.py"],
deps = [
":release_test_helper",
"//tools/private/release:release_lib",
],
)

py_test(
name = "backport_create_releases_test",
srcs = ["backport_create_releases_test.py"],
deps = [
":release_test_helper",
"//tools/private/release:release_lib",
],
)
Loading