woocommerce/.github/workflows/cherry-pick.yml

334 lines
13 KiB
YAML

name: Cherry Pick Tool
on:
issues:
types: [milestoned, labeled]
pull_request:
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.
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: ''
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 }}" )
}
check-release-branch-exists:
name: Check for existence of release branch
runs-on: ubuntu-20.04
needs: prep
steps:
- name: Check for release branch
id: release-breanch-check
uses: actions/github-script@v6
with:
script: |
// This will throw an error for non-200 responses, which prevents subsequent jobs from completing, as desired.
await github.request( 'GET /repos/{owner}/{repo}/branches/{branch}', {
owner: context.repo.owner,
repo: context.repo.repo,
branch: '${{ needs.prep.outputs.release }}',
} );
cherry-pick-run:
name: Run cherry pick tool
runs-on: ubuntu-20.04
needs: [ prep, check-release-branch-exists ]
if: success()
steps:
- 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 }}