forked from woocommerce/woocommerce
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add cherry pick tool workflow (woocommerce#34160)
* Add cherry pick tool workflow * Update cherry pick workflow * Add Slack ping overrides * Update .github/workflows/cherry-pick.yml Co-authored-by: jonathansadowski <jonathansadowski@users.noreply.github.com> * Update logic to be more clear Co-authored-by: jonathansadowski <jonathansadowski@users.noreply.github.com>
- Loading branch information
1 parent
ed912cb
commit 42deef4
Showing
1 changed file
with
298 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,320 @@ | ||
name: Cherry Pick Tool | ||
on: | ||
issues: | ||
types: [milestoned, labeled] | ||
pull_request: | ||
types: [closed, edited] | ||
types: [closed] | ||
workflow_dispatch: | ||
inputs: | ||
release_branch: | ||
description: Provide the release branch you want to cherry pick into. Example release/6.9 | ||
default: '' | ||
required: true | ||
pull_requests: | ||
description: The pull request number. Separate pull request numbers with a comma (no space) if more than one. | ||
description: The pull request number. | ||
default: '' | ||
required: true | ||
skipSlackPing: | ||
description: "Skip Slack Ping: If true, the Slack ping will be skipped (useful for testing)" | ||
type: boolean | ||
required: false | ||
default: false | ||
slackChannelOverride: | ||
description: "Slack Channel Override: The channel ID to send the Slack ping about the code freeze violation" | ||
required: false | ||
default: '' | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
env: | ||
GIT_COMMITTER_NAME: 'WooCommerce Bot' | ||
GIT_COMMITTER_EMAIL: 'no-reply@woocommerce.com' | ||
GIT_AUTHOR_NAME: 'WooCommerce Bot' | ||
GIT_AUTHOR_EMAIL: 'no-reply@woocommerce.com' | ||
|
||
jobs: | ||
verify: | ||
name: Verify | ||
runs-on: ubuntu-20.04 | ||
outputs: | ||
run: ${{ steps.check.outputs.run }} | ||
steps: | ||
- name: check | ||
id: check | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
let run = false; | ||
const isManualTrigger = context.payload.inputs && context.payload.inputs.release_branch && context.payload.inputs.release_branch != null; | ||
const isMergedMilestonedIssue = context.payload.issue && context.payload.issue.pull_request != null && context.payload.issue.pull_request.merged_at != null && context.payload.issue.milestone != null; | ||
|
||
const isMergedMilestonedPR = context.payload.pull_request && context.payload.pull_request != null && context.payload.pull_request.merged == true && context.payload.pull_request.milestone != null; | ||
|
||
const isBot = context.payload.pull_request && ( context.payload.pull_request.user.login == 'github-actions[bot]' || context.payload.pull_request.user.type == 'Bot' ); | ||
|
||
if ( !isBot && ( isManualTrigger || isMergedMilestonedIssue || isMergedMilestonedPR ) ) { | ||
console.log( "::set-output name=run::true" ); | ||
} else { | ||
console.log( "::set-output name=run::false" ); | ||
} | ||
prep: | ||
name: Prep inputs | ||
runs-on: ubuntu-20.04 | ||
needs: verify | ||
if: needs.verify.outputs.run == 'true' | ||
outputs: | ||
release: ${{ steps.prep-inputs.outputs.release }} | ||
pr: ${{ steps.prep-inputs.outputs.pr }} | ||
version: ${{ steps.prep-inputs.outputs.version }} | ||
steps: | ||
- name: Prep inputs | ||
id: prep-inputs | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
const event = ${{ toJSON( github.event ) }} | ||
// Means this workflow was triggered manually. | ||
if ( event.inputs && event.inputs.release_branch ) { | ||
const releaseBranch = '${{ inputs.release_branch }}' | ||
const version = releaseBranch.replace( 'release/', '' ); | ||
console.log( "::set-output name=version::" + version ) | ||
console.log( "::set-output name=release::${{ inputs.release_branch }}" ) | ||
} else { | ||
const version = '${{ github.event.pull_request.milestone.title }}' | ||
console.log( "::set-output name=version::" + version ) | ||
console.log( "::set-output name=release::release/${{ github.event.pull_request.milestone.title }}" ) | ||
} | ||
// Means this workflow was triggered manually. | ||
if ( event.inputs && event.inputs.pull_requests ) { | ||
console.log( "::set-output name=pr::${{ inputs.pull_requests }}" ) | ||
} else { | ||
console.log( "::set-output name=pr::${{ github.event.pull_request.number }}" ) | ||
} | ||
cherry-pick-run: | ||
name: Run cherry pick tool | ||
runs-on: ubuntu-20.04 | ||
needs: prep | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- run: echo ${{ github.event }} | ||
- name: Checkout release branch | ||
uses: actions/checkout@v3 | ||
with: | ||
fetch-depth: 100 | ||
|
||
- name: Git fetch the release branch | ||
run: git fetch origin ${{ needs.prep.outputs.release }} | ||
|
||
- name: Checkout release branch | ||
run: git checkout ${{ needs.prep.outputs.release }} | ||
|
||
- name: Create a cherry pick branch based on release branch | ||
run: git checkout -b cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} | ||
|
||
- name: Get commit sha from PR | ||
id: commit-sha | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
const pr = await github.rest.pulls.get({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
pull_number: '${{ needs.prep.outputs.pr }}' | ||
}) | ||
console.log( `::set-output name=sha::${ pr.data.merge_commit_sha }` ) | ||
- name: Cherry pick | ||
run: | | ||
git cherry-pick ${{ steps.commit-sha.outputs.sha }} | ||
- name: Generate changelog | ||
id: changelog | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
const fs = require( 'node:fs' ); | ||
const changelogsToBeDeleted = [] | ||
let changelogTxt = ''; | ||
const commit = await github.rest.repos.getCommit({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
ref: '${{ steps.commit-sha.outputs.sha }}' | ||
}) | ||
for ( const file of commit.data.files ) { | ||
if ( file.filename.match( 'plugins/woocommerce/changelog/' ) ) { | ||
if ( changelogsToBeDeleted.indexOf( file.filename ) === -1 ) { | ||
changelogsToBeDeleted.push( file.filename ); | ||
} | ||
let changelogEntry = ''; | ||
let changelogEntryType = ''; | ||
fs.readFile( './' + file.filename, 'utf-8', function( err, data ) { | ||
if ( err ) { | ||
console.error( err ); | ||
} | ||
const changelogEntryArr = data.split( "\n" ); | ||
changelogEntryType = data.match( /Type: (.+)/i ); | ||
changelogEntryType = changelogEntryType[ 1 ].charAt( 0 ).toUpperCase() + changelogEntryType[ 1 ].slice( 1 ); | ||
changelogEntry = changelogEntryArr.filter( el => { | ||
return el !== null && typeof el !== 'undefined' && el !== ''; | ||
} ); | ||
changelogEntry = changelogEntry[ changelogEntry.length - 1 ]; | ||
// Check if changelogEntry is what we want. | ||
if ( changelogEntry.length < 1 ) { | ||
changelogEntry = false; | ||
} | ||
if ( changelogEntry.match( /significance:/i ) ) { | ||
changelogEntry = false; | ||
} | ||
if ( changelogEntry.match( /type:/i ) ) { | ||
changelogEntry = false; | ||
} | ||
if ( changelogEntry.match( /comment:/i ) ) { | ||
changelogEntry = false; | ||
} | ||
} ); | ||
if ( changelogEntry === false ) { | ||
continue; | ||
} | ||
fs.readFile( './plugins/woocommerce/readme.txt', 'utf-8', function( err, data ) { | ||
if ( err ) { | ||
console.error( err ); | ||
} | ||
changelogTxt = data.split( "\n" ); | ||
let isInRange = false; | ||
let newChangelogTxt = []; | ||
for ( const line of changelogTxt ) { | ||
if ( isInRange === false && line === '== Changelog ==' ) { | ||
isInRange = true; | ||
} | ||
if ( isInRange === true && line.match( /\*\*WooCommerce Blocks/ ) ) { | ||
isInRange = false; | ||
} | ||
// Find the first match of the entry "Type". | ||
if ( isInRange && line.match( `\\* ${changelogEntryType} -` ) ) { | ||
newChangelogTxt.push( '* ' + changelogEntryType + ' - ' + changelogEntry + ` [#${{ needs.prep.outputs.pr }}](https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }})` ); | ||
newChangelogTxt.push( line ); | ||
isInRange = false; | ||
continue; | ||
} | ||
newChangelogTxt.push( line ); | ||
} | ||
fs.writeFile( './plugins/woocommerce/readme.txt', newChangelogTxt.join( "\n" ), err => { | ||
if ( err ) { | ||
console.error( `Unable to generate the changelog entry for PR ${{ needs.prep.outputs.pr }}` ); | ||
} | ||
} ); | ||
} ); | ||
} | ||
} | ||
console.log( `::set-output name=changelogsToBeDeleted::${ changelogsToBeDeleted }` ) | ||
- name: Delete changelog files from cherry pick branch | ||
run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} | ||
|
||
- name: Commit changes for cherry pick | ||
run: git commit --no-verify -am "Prep for cherry pick ${{ needs.prep.outputs.pr }}" | ||
|
||
- name: Push cherry pick branch up | ||
run: git push origin cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }} | ||
|
||
- name: Create the PR for cherry pick branch | ||
id: cherry-pick-pr | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
let cherryPickPRBody = "This PR cherry-picks the following PRs into the release branch:\n"; | ||
cherryPickPRBody = `${cherryPickPRBody}` + `* #${{ needs.prep.outputs.pr }}` + "\n"; | ||
const pr = await github.rest.pulls.create({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
title: "Cherry pick ${{ needs.prep.outputs.pr }} into ${{ needs.prep.outputs.release }}", | ||
head: "cherry-pick-${{ needs.prep.outputs.version }}/${{ needs.prep.outputs.pr }}", | ||
base: "${{ needs.prep.outputs.release }}", | ||
body: cherryPickPRBody | ||
}) | ||
console.log( `::set-output name=cherry-pick-pr::${ pr.data.html_url }` ) | ||
- name: Checkout trunk branch | ||
run: git checkout trunk | ||
|
||
- name: Create a branch based on trunk branch | ||
run: git checkout -b delete-changelogs/${{ needs.prep.outputs.pr }} | ||
|
||
- name: Delete changelogs from trunk | ||
run: git rm ${{ steps.changelog.outputs.changelogsToBeDeleted }} | ||
|
||
- name: Commit changes for deletion | ||
run: git commit --no-verify -am "Delete changelog files for ${{ needs.prep.outputs.pr }}" | ||
|
||
- name: Push deletion branch up | ||
run: git push origin delete-changelogs/${{ needs.prep.outputs.pr }} | ||
|
||
- name: Create the PR for deletion branch | ||
id: deletion-pr | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
const pr = await github.rest.pulls.create({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
title: "Delete changelog files based on PR ${{ needs.prep.outputs.pr }}", | ||
head: "delete-changelogs/${{ needs.prep.outputs.pr }}", | ||
base: "trunk", | ||
body: "Delete changelog files based on PR #${{ needs.prep.outputs.pr }}" | ||
}) | ||
console.log( `::set-output name=deletion-pr::${ pr.data.html_url }` ) | ||
- name: Notify Slack on failure | ||
if: ${{ failure() && inputs.skipSlackPing != true }} | ||
uses: archive/github-actions-slack@v2.0.0 | ||
with: | ||
slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} | ||
slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} | ||
slack-text: | | ||
:warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: | ||
An attempt to cherry pick PR(s) into outgoing release '${{ needs.prep.outputs.release }}' has failed. This could be due to a merge conflict or something else that requires manual attention. Please check: https://github.com/woocommerce/woocommerce/pull/${{ needs.prep.outputs.pr }} | ||
- name: Notify Slack on success | ||
if: ${{ success() && inputs.skipSlackPing != true }} | ||
uses: archive/github-actions-slack@v2.0.0 | ||
with: | ||
slack-bot-user-oauth-access-token: ${{ secrets.CODE_FREEZE_BOT_TOKEN }} | ||
slack-channel: ${{ inputs.slackChannelOverride || secrets.WOO_RELEASE_SLACK_CHANNEL }} | ||
slack-text: | | ||
:warning-8c: Code freeze violation. PR(s) created that breaks the Code Freeze for '${{ needs.prep.outputs.release }}' :ice_cube: | ||
Release lead please review: | ||
${{ steps.cherry-pick-pr.outputs.cherry-pick-pr }} | ||
${{ steps.deletion-pr.outputs.deletion-pr }} |