Commit 3a94b76167 for woocommerce
commit 3a94b761675c60f371504f517f9b8af82fe1632e
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Thu Dec 18 18:00:51 2025 +0000
Ensure PRs generated as part of the release process are milestoned (#62344)
diff --git a/.github/workflows/cherry-pick-milestoned-prs.yml b/.github/workflows/cherry-pick-milestoned-prs.yml
index c9a3c5942a..66eb1d2a1c 100644
--- a/.github/workflows/cherry-pick-milestoned-prs.yml
+++ b/.github/workflows/cherry-pick-milestoned-prs.yml
@@ -23,6 +23,7 @@ jobs:
next_branch: ${{ steps.get-branches.outputs.next_branch }}
pr_number: ${{ steps.set-vars.outputs.pr_number }}
milestone: ${{ steps.set-vars.outputs.milestone }}
+ should_cherry_pick: ${{ steps.check-cherry-pick.outputs.should_cherry_pick }}
steps:
- name: debug
run: |
@@ -37,6 +38,25 @@ jobs:
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
echo "milestone=${{ github.event.pull_request.milestone.title }}" >> $GITHUB_OUTPUT
+ # Check if cherry-pick should proceed.
+ # Behavior can be overridden using a hidden comment in HTML. By default, cherry-pick is disabled for PRs with the 'Release' label.
+ - name: Check if cherry-pick should proceed
+ id: check-cherry-pick
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
+ with:
+ script: |
+ const body = context.payload.pull_request.body || '';
+
+ // Check for override comment first (takes precedence over everything)
+ // Format: <!-- cherry-pick-milestoned-prs: yes --> or <!-- cherry-pick-milestoned-prs: no -->
+ const match = body.match(/^<!--\s*cherry-pick-milestoned-prs:\s*(yes|no)\s*-->$/m);
+ if ( match ) {
+ core.setOutput( 'should_cherry_pick', match[1] === 'yes' ? 'true' : 'false' );
+ return;
+ }
+
+ core.setOutput( 'should_cherry_pick', ${{ ! contains(github.event.pull_request.labels.*.name, 'Release') }} ? 'true' : 'false' );
+
# Extract base version from milestone (e.g., 9.9 from 9.9.0)
- name: Extract version from milestone
id: extract-version
@@ -93,11 +113,11 @@ jobs:
core.setOutput('milestoned_branch', milestonedExists ? milestonedBranch : '');
core.setOutput('next_branch', nextExists ? nextBranch : '');
-
+
# Cherry-pick to milestoned branch
cherry-pick-milestoned:
needs: prepare
- if: needs.prepare.outputs.milestoned_branch != ''
+ if: needs.prepare.outputs.milestoned_branch != '' && needs.prepare.outputs.should_cherry_pick == 'true'
uses: ./.github/workflows/shared-cherry-pick.yml
with:
pr_number: ${{ needs.prepare.outputs.pr_number }}
@@ -107,7 +127,7 @@ jobs:
# Cherry-pick to next branch
cherry-pick-next:
needs: prepare
- if: needs.prepare.outputs.next_branch != ''
+ if: needs.prepare.outputs.next_branch != '' && needs.prepare.outputs.should_cherry_pick == 'true'
uses: ./.github/workflows/shared-cherry-pick.yml
with:
pr_number: ${{ needs.prepare.outputs.pr_number }}
@@ -167,7 +187,7 @@ jobs:
issue_number: parseInt('${{ needs.prepare.outputs.pr_number }}'),
body
});
-
+
# Notify Slack about successful cherry-pick to milestoned branch
- name: Notify Slack on success
if: always()
@@ -237,7 +257,7 @@ jobs:
issue_number: parseInt('${{ needs.prepare.outputs.pr_number }}'),
body
});
-
+
# Notify Slack about successful cherry-pick to next branch
- name: Notify Slack on success
if: always()
@@ -299,7 +319,7 @@ jobs:
const targetBranch = process.env.TARGET_BRANCH;
const workflowLink = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
- const body =
+ const body =
(merger ? merger + ' ' : '') +
'❌ **Cherry-pick to `' + targetBranch + '` failed.**\n\n' +
'**Error:** ' + errorMsg + '\n\n' +
@@ -329,8 +349,8 @@ jobs:
slack-optional-unfurl_links: false
slack-channel: ${{ secrets.WOO_RELEASE_SLACK_NOTIFICATION_CHANNEL }}
slack-text: |
- :warning: *Cherry pick from `trunk` to `${{ env.TARGET_BRANCH }}` <${{ env.WORKFLOW_RUN_URL }}|failed>*
- Please resolve before releasing.
+ :warning: *Cherry pick from `trunk` to `${{ env.TARGET_BRANCH }}` <${{ env.WORKFLOW_RUN_URL }}|failed>*
+ Please resolve before releasing.
Source PR: _<${{ env.SOURCE_PR_URL }}|#${{ env.SOURCE_PR_NUMBER }} - ${{ env.SOURCE_PR_TITLE }}>_.
# Handle failed cherry-pick to next branch
@@ -379,7 +399,7 @@ jobs:
const targetBranch = process.env.TARGET_BRANCH;
const workflowLink = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
- const body =
+ const body =
(merger ? merger + ' ' : '') +
'❌ **Cherry-pick to `' + targetBranch + '` failed.**\n\n' +
'**Error:** ' + errorMsg + '\n\n' +
@@ -409,6 +429,6 @@ jobs:
slack-optional-unfurl_links: false
slack-channel: ${{ secrets.WOO_RELEASE_SLACK_NOTIFICATION_CHANNEL }}
slack-text: |
- :warning: *Cherry pick from `trunk` to `${{ env.TARGET_BRANCH }}` <${{ env.WORKFLOW_RUN_URL }}|failed>*
- Please resolve before releasing.
+ :warning: *Cherry pick from `trunk` to `${{ env.TARGET_BRANCH }}` <${{ env.WORKFLOW_RUN_URL }}|failed>*
+ Please resolve before releasing.
Source PR: _<${{ env.SOURCE_PR_URL }}|#${{ env.SOURCE_PR_NUMBER }} - ${{ env.SOURCE_PR_TITLE }}>_.
diff --git a/.github/workflows/release-bump-version.yml b/.github/workflows/release-bump-version.yml
index f598b5520b..033db20514 100644
--- a/.github/workflows/release-bump-version.yml
+++ b/.github/workflows/release-bump-version.yml
@@ -27,10 +27,6 @@ on:
description: Type of version bump to perform
type: string
required: true
- milestone:
- description: Milestone to set on the PR (optional)
- type: string
- required: false
permissions:
contents: write
@@ -119,6 +115,7 @@ jobs:
core.setOutput( 'nextVersion', newVersion );
core.setOutput( 'nextStable', newVersion.replace( /-(dev|(beta|rc)\.\d+)/, '' ) );
core.setOutput( 'clearChangelog', '' === prerelease );
+ core.setOutput( 'prMilestone', `${ baseVersion }.0` );
- name: Bump version in files
env:
@@ -175,16 +172,19 @@ jobs:
reviewer_arg="--reviewer ${{ github.actor }}"
fi
- # Set milestone argument (empty if not provided).
+ # Check milestone exists.
+ milestone="${{ steps.compute-new-version.outputs.prMilestone }}"
milestone_arg=""
- if [[ -n "${{ inputs.milestone }}" ]]; then
- milestone_arg="--milestone ${{ inputs.milestone }}"
+ if [[ -n "$milestone" ]]; then
+ if [ "$(gh api repos/${{ github.repository }}/milestones --jq "any(.[]; .title==\"$milestone\")")" = "true" ]; then
+ milestone_arg="--milestone $milestone"
+ fi
fi
# Create PR
gh pr create \
- --title 'Bump WooCommerce version to ${{ steps.compute-new-version.outputs.nextVersion }}' \
- --body 'This PR updates the versions in ${{ inputs.branch }} to ${{ steps.compute-new-version.outputs.nextVersion }}.' \
+ --title 'Bump WooCommerce version to `${{ steps.compute-new-version.outputs.nextVersion }}` on `${{ inputs.branch }}`' \
+ --body 'This PR updates the versions in ${{ inputs.branch }} to ${{ steps.compute-new-version.outputs.nextVersion }}.'$'\n\n''<!-- [x] This Pull Request does not require a changelog -->' \
--base ${{ inputs.branch }} \
--head ${branch_name} \
--label Release \
diff --git a/.github/workflows/release-code-freeze.yml b/.github/workflows/release-code-freeze.yml
index b9cd27ffef..1062dca44f 100644
--- a/.github/workflows/release-code-freeze.yml
+++ b/.github/workflows/release-code-freeze.yml
@@ -210,7 +210,6 @@ jobs:
with:
branch: trunk
bump-type: dev
- milestone: ${{ needs.prepare-for-feature-freeze.outputs.nextReleaseVersion }}
build-dev-release:
name: 'Build WooCommerce -dev release'
diff --git a/.github/workflows/release-new-release-published.yml b/.github/workflows/release-new-release-published.yml
index 068362b84e..7dd4a1b6a5 100644
--- a/.github/workflows/release-new-release-published.yml
+++ b/.github/workflows/release-new-release-published.yml
@@ -56,10 +56,10 @@ jobs:
git fetch origin ${BRANCH_NAME}
git checkout ${BRANCH_NAME}
current_version=$( cat plugins/woocommerce/woocommerce.php | grep -oP '(?<=Version: )(.+)' | head -n1 )
-
+
echo "Current version from woocommerce.php: '$current_version'"
echo "Release tag version: '$RELEASE_TAG'"
-
+
if [[ "$current_version" != "$RELEASE_TAG" ]]; then
echo "::warning::The version in woocommerce.php ($current_version) does not match the release tag version ($RELEASE_TAG). Skipping notification."
echo "versions_match=false" >> $GITHUB_OUTPUT
@@ -82,6 +82,10 @@ jobs:
name: 'Update changelog.txt after any stable release'
runs-on: ${{ ( github.repository == 'woocommerce/woocommerce' && 'blacksmith-2vcpu-ubuntu-2404' ) || 'ubuntu-latest' }}
if: ${{ github.event.action == 'published' && ! ( contains( inputs.release_tag_name, '-dev' ) || contains( inputs.release_tag_name, '-beta' ) || contains( inputs.release_tag_name, '-rc' ) ) }}
+ permissions:
+ contents: write
+ pull-requests: write
+ issues: write
steps:
- name: 'Fetch release readme.txt'
env:
@@ -110,12 +114,15 @@ jobs:
php ./trunk/.github/workflows/scripts/release-readme-to-changelog.php ./woocommerce/readme.txt ./trunk/changelog.txt
- name: 'Push changes and open PR'
env:
- GH_TOKEN: ${{ github.token }}
+ GH_TOKEN: ${{ secrets.WC_BOT_PR_CREATE_TOKEN || secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
+ # Determine milestone from release version.
+ milestone=$( cat ./woocommerce/woocommerce.php | grep -oP '(?<=Version: )(.+)' | head -n1 | sed 's/\.[0-9]\+$/.0/' )
+
# Configure Git.
- git config --global user.name "github-actions[bot]"
- git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "woocommercebot"
+ git config --global user.email "woocommercebot@users.noreply.github.com"
# Push new changelog.txt.
branch_name="update-changelog-${{ inputs.release_tag_name }}-$(date +%s)"
@@ -132,4 +139,6 @@ jobs:
--body 'This PR updates the global changelog.txt file following the release of version ${{ inputs.release_tag_name }}.' \
--base trunk \
--head "$branch_name" \
- --reviewer "${{ github.actor }}"
+ --reviewer "${{ github.actor }}" \
+ --label "Release" \
+ --milestone "$milestone"
diff --git a/.github/workflows/release-update-stable-tag.yml b/.github/workflows/release-update-stable-tag.yml
index 715f622d21..b07a5edc60 100644
--- a/.github/workflows/release-update-stable-tag.yml
+++ b/.github/workflows/release-update-stable-tag.yml
@@ -275,15 +275,20 @@ jobs:
git commit -m "Update stable tag to $TARGET_VERSION on $BRANCH_NAME"
git push origin "$UPDATE_BRANCH"
+ # Determine milestone from woocommerce.php version.
+ milestone=$( cat ./plugins/woocommerce/woocommerce.php | grep -oP '(?<=Version: )(.+)' | head -n1 | sed 's/\.[0-9]\+\(-dev\)\?$/.0/' )
+ echo "Using milestone: '$milestone'."
+
gh pr create \
--title "Update stable tag to $TARGET_VERSION ($BRANCH_NAME)" \
--body "This PR updates the stable tag to $TARGET_VERSION." \
--base "$BRANCH_NAME" \
--head "$UPDATE_BRANCH" \
--reviewer ${{ github.actor }} \
- --label Release
+ --label Release \
+ --milestone "$milestone"
- echo "✅ Successfully created PR to update stable tag to $TARGET_VERSION on $BRANCH_NAME"
+ echo "✅ Successfully created PR to update stable tag to $TARGET_VERSION on $BRANCH_NAME with milestone $milestone"
fi
notify-release:
diff --git a/tools/monorepo-utils/src/code-freeze/commands/changelog/lib/index.ts b/tools/monorepo-utils/src/code-freeze/commands/changelog/lib/index.ts
index 5c5e385f33..23bef09f4a 100644
--- a/tools/monorepo-utils/src/code-freeze/commands/changelog/lib/index.ts
+++ b/tools/monorepo-utils/src/code-freeze/commands/changelog/lib/index.ts
@@ -14,6 +14,7 @@ import { Logger } from '../../../../core/logger';
import { checkoutRemoteBranch } from '../../../../core/git';
import {
addLabelsToIssue,
+ addMilestoneToIssue,
createPullRequest,
} from '../../../../core/github/repo';
import { Options } from '../types';
@@ -296,6 +297,18 @@ export const updateReleaseBranchChangelogs = async (
);
}
+ try {
+ await addMilestoneToIssue(
+ options,
+ pullRequest.number,
+ `${ mainVersion }.0`
+ );
+ } catch {
+ Logger.warn(
+ `Could not add milestone "${ mainVersion }.0" to PR ${ pullRequest.number }`
+ );
+ }
+
return {
deletionCommitHash,
prNumber: pullRequest.number,
@@ -350,6 +363,18 @@ export const updateBranchChangelog = async (
[ branch ]: null,
} );
+ // Read plugin file version in branch to determine milestone.
+ let milestone = '';
+ const pluginFile = readFileSync(
+ path.join( tmpRepoPath, 'plugins/woocommerce/woocommerce.php' ),
+ 'utf8'
+ );
+ const m = pluginFile.match( /\*\s+Version:\s+(\d+\.\d+)\.\d+/ );
+
+ if ( m ) {
+ milestone = `${ m[ 1 ] }.0`;
+ }
+
try {
await git.raw( [ 'cherry-pick', deletionCommitHash ] );
} catch ( e ) {
@@ -390,6 +415,14 @@ export const updateBranchChangelog = async (
);
}
+ try {
+ await addMilestoneToIssue( options, pullRequest.number, milestone );
+ } catch {
+ Logger.warn(
+ `Could not add milestone "${ milestone }" to PR ${ pullRequest.number }`
+ );
+ }
+
return pullRequest.number;
} catch ( e ) {
if ( e.message.includes( `No commits between ${ releaseBranch }` ) ) {
diff --git a/tools/monorepo-utils/src/core/github/repo.ts b/tools/monorepo-utils/src/core/github/repo.ts
index 25dca3ec1c..3d14897d41 100644
--- a/tools/monorepo-utils/src/core/github/repo.ts
+++ b/tools/monorepo-utils/src/core/github/repo.ts
@@ -249,18 +249,55 @@ export const addLabelsToIssue = async (
);
};
+export const addMilestoneToIssue = async (
+ options: {
+ owner?: string;
+ name?: string;
+ },
+ issueNumber: number,
+ milestoneName: string
+): Promise< void > => {
+ const { owner, name } = options;
+
+ // Try to find milestone by name.
+ const { data } = await octokitWithAuth().request(
+ 'GET /repos/{owner}/{repo}/milestones',
+ {
+ owner,
+ repo: name,
+ state: 'all',
+ direction: 'desc',
+ per_page: 100,
+ }
+ );
+
+ const milestone = data.find( ( m ) => m.title === milestoneName );
+
+ if ( milestone ) {
+ await octokitWithAuth().request(
+ 'PATCH /repos/{owner}/{repo}/issues/{issue_number}',
+ {
+ owner,
+ repo: name,
+ issue_number: issueNumber,
+ milestone: milestone.number,
+ }
+ );
+ }
+};
+
/**
* Create a pull request from branches on GitHub.
*
- * @param {Object} options pull request options.
- * @param {string} options.head branch name containing the changes you want to merge.
- * @param {string} options.base branch name you want the changes pulled into.
- * @param {string} options.owner repository owner.
- * @param {string} options.name repository name.
- * @param {string} options.title pull request title.
- * @param {string} options.body pull request body.
- * @return {Promise<object>} pull request data.
+ * @param {Object} options pull request options.
+ * @param {string} options.head branch name containing the changes you want to merge.
+ * @param {string} options.base branch name you want the changes pulled into.
+ * @param {string} options.owner repository owner.
+ * @param {string} options.name repository name.
+ * @param {string} options.title pull request title.
+ * @param {string} options.body pull request body.
* @param {string[]} options.reviewers list of GitHub usernames to request a review from.
+ * @return {Promise<object>} pull request data.
*/
export const createPullRequest = async ( options: {
head: string;
@@ -284,14 +321,18 @@ export const createPullRequest = async ( options: {
}
);
- if ( reviewers && reviewers.length > 0 ) {
+ const filteredReviewers = reviewers?.filter(
+ ( reviewer ) => reviewer !== pullRequest.data.user.login
+ );
+
+ if ( filteredReviewers && filteredReviewers.length > 0 ) {
await octokitWithAuth().request(
'POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers',
{
owner,
repo: name,
pull_number: pullRequest.data.number,
- reviewers,
+ reviewers: filteredReviewers,
}
);
}