From 99b9b7f7b4c047962c24a34bdb947d7f7abfafa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 11 May 2022 16:40:55 +0200 Subject: [PATCH 01/21] First stab at the cherry-pick.js script that cherry picks all relevant changes with the backport label --- cherry-pick.mjs | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 cherry-pick.mjs diff --git a/cherry-pick.mjs b/cherry-pick.mjs new file mode 100644 index 00000000000000..7e19b307208811 --- /dev/null +++ b/cherry-pick.mjs @@ -0,0 +1,177 @@ +/** + * External dependencies + */ +import fetch from 'node-fetch'; + +import { spawnSync } from 'node:child_process'; + +async function main() { + const PRs = await fetchPRs(); + console.log( 'Trying to cherry-pick one by one...' ); + const [successes, failures] = cherryPickAll( PRs ); + report( successes, failures ); +} + +async function fetchPRs() { + const { items } = await GitHubFetch( + '/search/issues?q=is:pr state:closed sort:updated label:"Backport to WP Beta/RC" repo:WordPress/gutenberg', + ); + const PRs = items.map( ( { id, number, title } ) => ( { id, number, title } ) ); + console.log( 'Found the following PRs to cherry-pick: ' ); + PRs.forEach( ( { number, title } ) => console.log( indent( `#${ number } – ${ title }` ) ) ); + console.log( 'Fetching commit IDs...' ); + + const PRsWithMergeCommit = []; + for ( const PR of PRs ) { + const { merge_commit_sha } = await GitHubFetch( + '/repos/WordPress/Gutenberg/pulls/' + PR.number, + ); + PRsWithMergeCommit.push( { + ...PR, + mergeCommitHash: merge_commit_sha, + } ); + if ( !merge_commit_sha ) { + throw new Error( `Cannot fetch the merge commit sha for ${ prToString( PR ) }` ); + } + } + + console.log( 'Done!' ); + PRsWithMergeCommit + .forEach( ( msg ) => console.log( indent( `${ prToString( msg ) }` ) ) ); + return PRsWithMergeCommit; +} + +async function GitHubFetch( path ) { + const response = await fetch( + 'https://api.github.com' + path, + { + headers: { + Accept: 'application/vnd.github.v3+json', + }, + }, + ); + return await response.json(); +} + +function cherryPickAll( PRs ) { + let remainingPRs = [...PRs]; + let i = 1; + let allSuccesses = []; + while ( remainingPRs.length ) { + console.log( `Cherry-picking round ${ i ++ }: ` ); + const [successes, failures] = cherryPickRound( remainingPRs ); + allSuccesses = [...allSuccesses, successes]; + remainingPRs = failures; + if ( !successes.length ) { + console.log( 'Nothing merged cleanly in the last round, breaking.' ); + break; + } + } + console.log( allSuccesses ); + console.log( 'Cherry-picking finished!' ); + console.log( 'Summary:' ); + console.log( indent( `✅ ${ allSuccesses.length } PRs got cherry-picked cleanly` ) ); + console.log( indent( `❌ ${ remainingPRs.length } PRs failed` ) ); + console.log( '' ); + return [allSuccesses, remainingPRs]; +} + +function cherryPickRound( PRs ) { + const stack = [...PRs]; + const successes = []; + const failures = []; + while ( stack.length ) { + const PR = stack.shift(); + try { + const cherryPickHash = cherryPickOne( PR.mergeCommitHash ); + successes.push( { + ...PR, + cherryPickHash, + } ); + console.log( + indent( + `✅ cherry-pick commit: ${ cherryPickHash } for PR: ${ prToString( PR, false ) }` ) ); + } catch ( e ) { + failures.push( { + ...PR, + error: e.toString(), + } ); + console.log( indent( `❌ ${ prToString( PR ) }` ) ); + } + } + return [successes, failures]; +} + +const identity = x => x; + +function prToString( { number, mergeCommitHash, title }, withMergeCommitHash = true ) { + return [ + `#${ number }`, + withMergeCommitHash ? mergeCommitHash?.substr( 0, 20 ) : '', + `${ title?.substr( 0, 30 ) }${ title?.length > 30 ? '...' : '' }`, + ].filter( identity ).join( ' – ' ); +} + +function indent( text, width = 3 ) { + const indent = ' '.repeat( width ); + return text.split( "\n" ).map( line => indent + line ).join( "\n" ); +} + +function cherryPickOne( commit ) { + const result = spawnSync( 'git', ['cherry-pick', commit] ); + const message = result.stdout.toString().trim(); + if ( result.status !== 0 || !message.includes( 'Author: ' ) ) { + spawnSync( 'git', ['reset', '--hard'] ); + throw new Error( result.stderr.toString().trim() ); + } + const commitHashOutput = spawnSync( 'git', ['rev-parse', '--short', 'HEAD'] ); + return commitHashOutput.stdout.toString().trim(); +} + +function report( successes, failures ) { + const branch = getCurrentBranch(); + console.log( "Next steps:" ); + let n = 1; + if ( successes.length ) { + console.log( indent( `${ n ++ }. Push this branch` ) ); + console.log( indent( `${ n ++ }. Go to each of the cherry-picked Pull Requests` ) ); + console.log( indent( `${ n ++ }. Remove the Backport to WP Beta/RC label` ) ); + console.log( indent( `${ n ++ }. Request a backport to wordpress-develop if required` ) ); + console.log( indent( `${ n ++ }. Comment, say that PR just got cherry-picked` ) ); + } + if ( failures.length ) { + console.log( indent( `${ n ++ }. Manually cherry-pick the PRs that failed` ) ); + } + console.log( '' ); + if ( successes.length ) { + console.log( "Cherry-picked PRs with copy-able comments:" ); + for ( const { number, title, cherryPickHash } of successes ) { + console.log( indent( `https://github.com/WordPress/gutenberg/pulls/${ number } ` ) ); + console.log( indent( `#${ number } ${ title }` ) ); + console.log( '' ); + console.log( + indent( + `I just cherry-picked this PR to the ${ branch } branch to get it included in the next release: ${ cherryPickHash }` ) ); + console.log( '' ); + } + } + if ( failures.length ) { + console.log( "PRs that could not be cherry-picked automatically:" ); + console.log( '' ); + for ( const { number, title, mergeCommitHash, error } of failures ) { + console.log( indent( `https://github.com/WordPress/gutenberg/pulls/${ number } ` ) ); + console.log( indent( `#${ number } ${ title }` ) ); + console.log( indent( `git cherry-pick ${ mergeCommitHash }`, 6 ) ); + console.log( indent( `failed with:`, 6 ) ); + console.log( indent( `${ error }`, 6 ) ); + console.log( '' ); + } + } + console.log( `Done!` ); +} + +function getCurrentBranch() { + return spawnSync( 'git', ['rev-parse', '--abbrev-ref', 'HEAD'] ).stdout.toString(); +} + +main(); From 763fa89b865d681ae00e97f46838ed4c525f5dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 12 May 2022 14:07:09 +0200 Subject: [PATCH 02/21] Correctly spread successful runs --- cherry-pick.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 7e19b307208811..103e9b08223290 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -60,14 +60,13 @@ function cherryPickAll( PRs ) { while ( remainingPRs.length ) { console.log( `Cherry-picking round ${ i ++ }: ` ); const [successes, failures] = cherryPickRound( remainingPRs ); - allSuccesses = [...allSuccesses, successes]; + allSuccesses = [...allSuccesses, ...successes]; remainingPRs = failures; if ( !successes.length ) { console.log( 'Nothing merged cleanly in the last round, breaking.' ); break; } } - console.log( allSuccesses ); console.log( 'Cherry-picking finished!' ); console.log( 'Summary:' ); console.log( indent( `✅ ${ allSuccesses.length } PRs got cherry-picked cleanly` ) ); From 4b443b0f9b14d69b94a6f8b4afe3714359aa76c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 12 May 2022 14:08:34 +0200 Subject: [PATCH 03/21] Show green checkmark when no PRs failed --- cherry-pick.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 103e9b08223290..5975db05548819 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -70,7 +70,7 @@ function cherryPickAll( PRs ) { console.log( 'Cherry-picking finished!' ); console.log( 'Summary:' ); console.log( indent( `✅ ${ allSuccesses.length } PRs got cherry-picked cleanly` ) ); - console.log( indent( `❌ ${ remainingPRs.length } PRs failed` ) ); + console.log( indent( `${ remainingPRs.length > 0 ? '✅' : '❌' } ${ remainingPRs.length } PRs failed` ) ); console.log( '' ); return [allSuccesses, remainingPRs]; } From 3ce560525adce6e57ebfd5cb053766883dc000b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 12 May 2022 14:09:02 +0200 Subject: [PATCH 04/21] Show green checkmark when no PRs failed --- cherry-pick.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 5975db05548819..965ff116a43989 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -70,7 +70,7 @@ function cherryPickAll( PRs ) { console.log( 'Cherry-picking finished!' ); console.log( 'Summary:' ); console.log( indent( `✅ ${ allSuccesses.length } PRs got cherry-picked cleanly` ) ); - console.log( indent( `${ remainingPRs.length > 0 ? '✅' : '❌' } ${ remainingPRs.length } PRs failed` ) ); + console.log( indent( `${ remainingPRs.length > 0 ? '❌' : '✅' } ${ remainingPRs.length } PRs failed` ) ); console.log( '' ); return [allSuccesses, remainingPRs]; } From 55e08a4a9247ea4f9663dc1730794e84463aec6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 16 May 2022 11:58:51 +0200 Subject: [PATCH 05/21] Adjust the PR link from /pulls/ to /pull/ --- cherry-pick.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 965ff116a43989..aed89d20e631c6 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -145,7 +145,7 @@ function report( successes, failures ) { if ( successes.length ) { console.log( "Cherry-picked PRs with copy-able comments:" ); for ( const { number, title, cherryPickHash } of successes ) { - console.log( indent( `https://github.com/WordPress/gutenberg/pulls/${ number } ` ) ); + console.log( indent( `https://github.com/WordPress/gutenberg/pull/${ number } ` ) ); console.log( indent( `#${ number } ${ title }` ) ); console.log( '' ); console.log( @@ -153,6 +153,9 @@ function report( successes, failures ) { `I just cherry-picked this PR to the ${ branch } branch to get it included in the next release: ${ cherryPickHash }` ) ); console.log( '' ); } + // gh pr comment: + // https://cli.github.com/manual/gh_pr_comment + // Also remove the label } if ( failures.length ) { console.log( "PRs that could not be cherry-picked automatically:" ); From e74eac0251808ff589b524ff90ec93d4b43c87ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 16 May 2022 18:29:44 +0200 Subject: [PATCH 06/21] Draft support for auto-commenting and removing labels from cherry-picked PRs --- cherry-pick.mjs | 159 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 45 deletions(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index aed89d20e631c6..538f6363049505 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -5,16 +5,69 @@ import fetch from 'node-fetch'; import { spawnSync } from 'node:child_process'; +const LABEL = "Backport to WP Beta/RC"; +const BRANCH = getCurrentBranch(); +const GITHUB_CLI_AVAILABLE = spawnSync( 'gh', ['auth', 'status'] ) + ?.stderr + ?.toString() + .includes( '✓ Logged in to github.com as' ); +if ( !GITHUB_CLI_AVAILABLE ) { + // communicate the situation + // ask the user whether to proceed + // add a CLI option to explicitly disable the automatic GitHub handling + // add a CLI option to disable user interactions for CI use + // add a CLI option to override the label +} +const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; + async function main() { + console.log( `Running git pull origin ${ BRANCH } --rebase...` ); + spawnSync( 'git', ['pull', 'origin', BRANCH, '--rebase'], { + cwd: process.cwd(), + env: process.env, + stdio: 'pipe', + encoding: 'utf-8', + } ); + const PRs = await fetchPRs(); console.log( 'Trying to cherry-pick one by one...' ); - const [successes, failures] = cherryPickAll( PRs ); - report( successes, failures ); + const results = cherryPickAll( PRs ); + const successes = results[ 0 ].map( enrichWithGithubCommentAndUrl ); + const failures = results[ 1 ].map( enrichWithGithubCommentAndUrl ); + + console.log( 'Cherry-picking finished!' ); + reportSummaryNextSteps( successes, failures ); + + if ( successes.length ) { + if ( AUTO_PROPAGATE_RESULTS_TO_GITHUB ) { + console.log( `Pushing to origin/${ BRANCH }` ); + cli( 'git', ['push', 'origin', BRANCH] ); + + console.log( `Commenting and removing labels...` ); + successes.forEach( commentAndRemoveLabel ); + } else { + console.log( "Cherry-picked PRs with copy-able comments:" ); + successes.forEach( reportSuccessManual ); + } + } + if ( failures.length ) { + console.log( "PRs that could not be cherry-picked automatically:" ); + failures.forEach( reportFailure ); + } + console.log( `Done!` ); +} + +function cli( command, args ) { + const result = spawnSync( command, args ); + if ( result.status !== 0 ) { + throw new Error( result.stderr?.toString()?.trim() ); + } + return result.stdout.toString().trim(); } async function fetchPRs() { const { items } = await GitHubFetch( - '/search/issues?q=is:pr state:closed sort:updated label:"Backport to WP Beta/RC" repo:WordPress/gutenberg', + `/search/issues?q=is:pr state:closed sort:updated label:"${ LABEL }" repo:WordPress/gutenberg`, ); const PRs = items.map( ( { id, number, title } ) => ( { id, number, title } ) ); console.log( 'Found the following PRs to cherry-pick: ' ); @@ -67,11 +120,6 @@ function cherryPickAll( PRs ) { break; } } - console.log( 'Cherry-picking finished!' ); - console.log( 'Summary:' ); - console.log( indent( `✅ ${ allSuccesses.length } PRs got cherry-picked cleanly` ) ); - console.log( indent( `${ remainingPRs.length > 0 ? '❌' : '✅' } ${ remainingPRs.length } PRs failed` ) ); - console.log( '' ); return [allSuccesses, remainingPRs]; } @@ -127,49 +175,70 @@ function cherryPickOne( commit ) { return commitHashOutput.stdout.toString().trim(); } -function report( successes, failures ) { - const branch = getCurrentBranch(); - console.log( "Next steps:" ); - let n = 1; - if ( successes.length ) { - console.log( indent( `${ n ++ }. Push this branch` ) ); - console.log( indent( `${ n ++ }. Go to each of the cherry-picked Pull Requests` ) ); - console.log( indent( `${ n ++ }. Remove the Backport to WP Beta/RC label` ) ); - console.log( indent( `${ n ++ }. Request a backport to wordpress-develop if required` ) ); - console.log( indent( `${ n ++ }. Comment, say that PR just got cherry-picked` ) ); +function reportSummaryNextSteps( successes, failures ) { + console.log( 'Summary:' ); + console.log( indent( `✅ ${ successes.length } PRs got cherry-picked cleanly` ) ); + console.log( + indent( `${ failures.length > 0 ? '❌' : '✅' } ${ failures.length } PRs failed` ) ); + console.log( '' ); + + const nextSteps = []; + if ( successes.length && !AUTO_PROPAGATE_RESULTS_TO_GITHUB ) { + nextSteps.push( 'Push this branch' ); + nextSteps.push( 'Go to each of the cherry-picked Pull Requests' ); + nextSteps.push( 'Remove the Backport to WP Beta/RC label' ); + nextSteps.push( 'Request a backport to wordpress-develop if required' ); + nextSteps.push( 'Comment, say that PR just got cherry-picked' ); } if ( failures.length ) { - console.log( indent( `${ n ++ }. Manually cherry-pick the PRs that failed` ) ); + nextSteps.push( 'Manually cherry-pick the PRs that failed' ); } - console.log( '' ); - if ( successes.length ) { - console.log( "Cherry-picked PRs with copy-able comments:" ); - for ( const { number, title, cherryPickHash } of successes ) { - console.log( indent( `https://github.com/WordPress/gutenberg/pull/${ number } ` ) ); - console.log( indent( `#${ number } ${ title }` ) ); - console.log( '' ); - console.log( - indent( - `I just cherry-picked this PR to the ${ branch } branch to get it included in the next release: ${ cherryPickHash }` ) ); - console.log( '' ); + if ( nextSteps.length ) { + console.log( "Next steps:" ); + for ( let i = 0; i < nextSteps.length; i ++ ) { + console.log( indent( `${ i + 1 }. ${ nextSteps[ i ] }` ) ); } - // gh pr comment: - // https://cli.github.com/manual/gh_pr_comment - // Also remove the label - } - if ( failures.length ) { - console.log( "PRs that could not be cherry-picked automatically:" ); console.log( '' ); - for ( const { number, title, mergeCommitHash, error } of failures ) { - console.log( indent( `https://github.com/WordPress/gutenberg/pulls/${ number } ` ) ); - console.log( indent( `#${ number } ${ title }` ) ); - console.log( indent( `git cherry-pick ${ mergeCommitHash }`, 6 ) ); - console.log( indent( `failed with:`, 6 ) ); - console.log( indent( `${ error }`, 6 ) ); - console.log( '' ); - } } - console.log( `Done!` ); +} + +function commentAndRemoveLabel( pr ) { + const { number, comment } = pr; + try { + cli( 'gh', ['pr', 'comment', number, comment] ); + cli( 'gh', ['pr', 'edit', number, '--remove-label', LABEL] ); + console.log( `✅ ${ number }: ${ comment }` ); + } catch ( e ) { + console.log( `❌ ${ number }. ${ comment } ` ); + console.log( indent( 'Error: ' ) ); + console.error( e ); + console.log( indent( 'You will need to manually process this PR: ' ) ); + reportSuccessManual( pr ); + } +} + +function reportSuccessManual( { number, comment, url } ) { + console.log( indent( url ) ); + console.log( indent( `#${ number } ${ title }` ) ); + console.log( indent( comment ) ); + console.log( '' ); +} + +function reportFailure( { number, mergeCommitHash, url } ) { + console.log( indent( url ) ); + console.log( indent( `#${ number } ${ title }` ) ); + console.log( indent( `git cherry-pick ${ mergeCommitHash }`, 6 ) ); + console.log( indent( `failed with:`, 6 ) ); + console.log( indent( `${ error }`, 6 ) ); + console.log( '' ); +} + +function enrichWithGithubCommentAndUrl( pr ) { + return { + ...pr, + url: `https://github.com/WordPress/gutenberg/pull/${ pr.number } `, + comment: `I just cherry-picked this PR to the ${ BRANCH } branch to get it included in the next release: ${ pr.cherryPickHash }`, + }; } function getCurrentBranch() { From 71d1b7bd9db373b8048d51d8a08f1f472dbb08f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 16 May 2022 18:33:55 +0200 Subject: [PATCH 07/21] Remove enrichWithGithubCommentAndUrl --- cherry-pick.mjs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 538f6363049505..1d9744fea5c3f0 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -31,9 +31,7 @@ async function main() { const PRs = await fetchPRs(); console.log( 'Trying to cherry-pick one by one...' ); - const results = cherryPickAll( PRs ); - const successes = results[ 0 ].map( enrichWithGithubCommentAndUrl ); - const failures = results[ 1 ].map( enrichWithGithubCommentAndUrl ); + const [successes, failures] = cherryPickAll( PRs ); console.log( 'Cherry-picking finished!' ); reportSummaryNextSteps( successes, failures ); @@ -44,7 +42,7 @@ async function main() { cli( 'git', ['push', 'origin', BRANCH] ); console.log( `Commenting and removing labels...` ); - successes.forEach( commentAndRemoveLabel ); + successes.forEach( GHcommentAndRemoveLabel ); } else { console.log( "Cherry-picked PRs with copy-able comments:" ); successes.forEach( reportSuccessManual ); @@ -202,8 +200,9 @@ function reportSummaryNextSteps( successes, failures ) { } } -function commentAndRemoveLabel( pr ) { - const { number, comment } = pr; +function GHcommentAndRemoveLabel( pr ) { + const { number, cherryPickHash } = pr; + const comment = prComment( cherryPickHash ); try { cli( 'gh', ['pr', 'comment', number, comment] ); cli( 'gh', ['pr', 'edit', number, '--remove-label', LABEL] ); @@ -217,15 +216,15 @@ function commentAndRemoveLabel( pr ) { } } -function reportSuccessManual( { number, comment, url } ) { - console.log( indent( url ) ); +function reportSuccessManual( { number, cherryPickHash } ) { + console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); - console.log( indent( comment ) ); + console.log( indent( prComment( cherryPickHash ) ) ); console.log( '' ); } -function reportFailure( { number, mergeCommitHash, url } ) { - console.log( indent( url ) ); +function reportFailure( { number, mergeCommitHash } ) { + console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); console.log( indent( `git cherry-pick ${ mergeCommitHash }`, 6 ) ); console.log( indent( `failed with:`, 6 ) ); @@ -233,12 +232,12 @@ function reportFailure( { number, mergeCommitHash, url } ) { console.log( '' ); } -function enrichWithGithubCommentAndUrl( pr ) { - return { - ...pr, - url: `https://github.com/WordPress/gutenberg/pull/${ pr.number } `, - comment: `I just cherry-picked this PR to the ${ BRANCH } branch to get it included in the next release: ${ pr.cherryPickHash }`, - }; +function prUrl( number ) { + return `https://github.com/WordPress/gutenberg/pull/${ number } `; +} + +function prComment( cherryPickHash ) { + return `I just cherry-picked this PR to the ${ BRANCH } branch to get it included in the next release: ${ cherryPickHash }`; } function getCurrentBranch() { From a2d9f42acfaa4abce98eb0ff4bad8ee194cc3951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Mon, 16 May 2022 18:35:00 +0200 Subject: [PATCH 08/21] Reformat code --- cherry-pick.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cherry-pick.mjs b/cherry-pick.mjs index 1d9744fea5c3f0..5a450f08bbed09 100644 --- a/cherry-pick.mjs +++ b/cherry-pick.mjs @@ -32,8 +32,8 @@ async function main() { const PRs = await fetchPRs(); console.log( 'Trying to cherry-pick one by one...' ); const [successes, failures] = cherryPickAll( PRs ); - console.log( 'Cherry-picking finished!' ); + reportSummaryNextSteps( successes, failures ); if ( successes.length ) { From 0564aeb6505e9bdde1545b758dc605e30b069081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 May 2022 14:03:58 +0200 Subject: [PATCH 09/21] Add reportGhUnavailable() if gh CLI is not installed --- cherry-pick.mjs => bin/cherry-pick.mjs | 44 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) rename cherry-pick.mjs => bin/cherry-pick.mjs (87%) diff --git a/cherry-pick.mjs b/bin/cherry-pick.mjs similarity index 87% rename from cherry-pick.mjs rename to bin/cherry-pick.mjs index 5a450f08bbed09..c6963c2027feef 100644 --- a/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -2,6 +2,7 @@ * External dependencies */ import fetch from 'node-fetch'; +import readline from 'readline'; import { spawnSync } from 'node:child_process'; @@ -11,17 +12,15 @@ const GITHUB_CLI_AVAILABLE = spawnSync( 'gh', ['auth', 'status'] ) ?.stderr ?.toString() .includes( '✓ Logged in to github.com as' ); -if ( !GITHUB_CLI_AVAILABLE ) { - // communicate the situation - // ask the user whether to proceed - // add a CLI option to explicitly disable the automatic GitHub handling - // add a CLI option to disable user interactions for CI use - // add a CLI option to override the label -} + const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; async function main() { - console.log( `Running git pull origin ${ BRANCH } --rebase...` ); + if ( !GITHUB_CLI_AVAILABLE ) { + await reportGhUnavailable(); + } + + console.log( `$ git pull origin ${ BRANCH } --rebase...` ); spawnSync( 'git', ['pull', 'origin', BRANCH, '--rebase'], { cwd: process.cwd(), env: process.env, @@ -241,7 +240,34 @@ function prComment( cherryPickHash ) { } function getCurrentBranch() { - return spawnSync( 'git', ['rev-parse', '--abbrev-ref', 'HEAD'] ).stdout.toString(); + return spawnSync( 'git', ['rev-parse', '--abbrev-ref', 'HEAD'] ).stdout.toString().trim(); +} + +async function reportGhUnavailable() { + console.log( 'Github CLI is not setup. This script will not be able to automatically' ); + console.log( 'comment on the processed PRs and remove the backport label from them.' ); + console.log( 'Instead, you will see a detailed list of next steps to perform manually.' ); + console.log( '' ); + console.log( + 'To enable automatic handling, install the `gh` utility from https://cli.github.com/' ); + console.log( '' ); + + const rl = readline.createInterface( { + input: process.stdin, + output: process.stdout, + } ); + + const question = ( prompt ) => new Promise( ( resolve ) => rl.question( prompt, resolve ) ); + do { + const answer = await question( 'Do you want to proceed? (Y/n)' ); + if ( !answer || answer === 'Y' ) { + break; + } + if ( answer === 'n' ) { + process.exit( 0 ); + } + } while ( true ); + rl.close(); } main(); From 92066e71a15ecb893de280b3644ad1287b87bd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 17 May 2022 14:38:47 +0200 Subject: [PATCH 10/21] Fix missing variable declarations --- bin/cherry-pick.mjs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index c6963c2027feef..41b38c2e18b767 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -21,12 +21,10 @@ async function main() { } console.log( `$ git pull origin ${ BRANCH } --rebase...` ); - spawnSync( 'git', ['pull', 'origin', BRANCH, '--rebase'], { - cwd: process.cwd(), - env: process.env, - stdio: 'pipe', - encoding: 'utf-8', - } ); + cli( 'git', ['pull', 'origin', BRANCH, '--rebase'], true ); + + console.log( `$ git fetch origin trunk...` ); + cli( 'git', ['fetch', 'origin', 'trunk'], true ); const PRs = await fetchPRs(); console.log( 'Trying to cherry-pick one by one...' ); @@ -54,8 +52,14 @@ async function main() { console.log( `Done!` ); } -function cli( command, args ) { - const result = spawnSync( command, args ); +function cli( command, args, pipe = false ) { + const pipeOptions = { + cwd: process.cwd(), + env: process.env, + stdio: 'pipe', + encoding: 'utf-8', + }; + const result = spawnSync( command, args, ...( pipe ? pipeOptions : [] ) ); if ( result.status !== 0 ) { throw new Error( result.stderr?.toString()?.trim() ); } @@ -215,14 +219,14 @@ function GHcommentAndRemoveLabel( pr ) { } } -function reportSuccessManual( { number, cherryPickHash } ) { +function reportSuccessManual( { number, title, cherryPickHash } ) { console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); console.log( indent( prComment( cherryPickHash ) ) ); console.log( '' ); } -function reportFailure( { number, mergeCommitHash } ) { +function reportFailure( { number, title, error, mergeCommitHash } ) { console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); console.log( indent( `git cherry-pick ${ mergeCommitHash }`, 6 ) ); From 883045e4c45225dbc60b0e0a083e95beb3c867b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 20 May 2022 13:32:26 +0200 Subject: [PATCH 11/21] Fix auto reporting via gh --- bin/cherry-pick.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 41b38c2e18b767..92004d56a5aad4 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -59,7 +59,7 @@ function cli( command, args, pipe = false ) { stdio: 'pipe', encoding: 'utf-8', }; - const result = spawnSync( command, args, ...( pipe ? pipeOptions : [] ) ); + const result = spawnSync( command, args, ...( pipe ? [ pipeOptions ] : [] ) ); if ( result.status !== 0 ) { throw new Error( result.stderr?.toString()?.trim() ); } @@ -207,7 +207,7 @@ function GHcommentAndRemoveLabel( pr ) { const { number, cherryPickHash } = pr; const comment = prComment( cherryPickHash ); try { - cli( 'gh', ['pr', 'comment', number, comment] ); + cli( 'gh', ['pr', 'comment', number, '--body', comment] ); cli( 'gh', ['pr', 'edit', number, '--remove-label', LABEL] ); console.log( `✅ ${ number }: ${ comment }` ); } catch ( e ) { From 185d5f7a965231a87c9042d8069732d6170e986d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 24 May 2022 13:46:49 +0200 Subject: [PATCH 12/21] Add documentation strings --- bin/cherry-pick.mjs | 117 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 92004d56a5aad4..21c3f852a6256d 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -15,6 +15,14 @@ const GITHUB_CLI_AVAILABLE = spawnSync( 'gh', ['auth', 'status'] ) const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; +/** + * The main function of this script. It: + * * Retrieves the list of relevant PRs to cherry-pick + * * Tries to cherry-pick them + * * Pushes the changes comments on the successful cherry-picks + * * Reports the results + * * Suggests next steps + */ async function main() { if ( !GITHUB_CLI_AVAILABLE ) { await reportGhUnavailable(); @@ -52,6 +60,14 @@ async function main() { console.log( `Done!` ); } +/** + * Synchronously executes a CLI command and returns the result or throws an error on failure. + * + * @param {string} command A command to execute. + * @param {string[]} args CLI args. + * @param {boolean} pipe If true, pipes the output to this process's stdout and stderr. + * @return {string} Command's output. + */ function cli( command, args, pipe = false ) { const pipeOptions = { cwd: process.cwd(), @@ -66,6 +82,11 @@ function cli( command, args, pipe = false ) { return result.stdout.toString().trim(); } +/** + * Retrieves the details of PR we want to cherry-pick from GitHub API. + * + * @return {Promise} A list of relevant PR data objects. + */ async function fetchPRs() { const { items } = await GitHubFetch( `/search/issues?q=is:pr state:closed sort:updated label:"${ LABEL }" repo:WordPress/gutenberg`, @@ -95,6 +116,12 @@ async function fetchPRs() { return PRsWithMergeCommit; } +/** + * A utility functino for GET requesting GitHub API. + * + * @param {string} path The API path to request. + * @return {Promise} Parsed response JSON. + */ async function GitHubFetch( path ) { const response = await fetch( 'https://api.github.com' + path, @@ -107,6 +134,15 @@ async function GitHubFetch( path ) { return await response.json(); } +/** + * Attempts to cherry-pick given PRs using `git` CLI command. + * + * Retries failed cherry-picks if any other PR got successfully cherry-picked + * success since the last attempt. + * + * @param {Object[]} PRs The list of PRs to cherry-pick. + * @return {Array} A two-tuple containing a list of successful cherry-picks and a list of failed ones. + */ function cherryPickAll( PRs ) { let remainingPRs = [...PRs]; let i = 1; @@ -124,6 +160,14 @@ function cherryPickAll( PRs ) { return [allSuccesses, remainingPRs]; } +/** + * Attempts to cherry-pick given PRs using `git` CLI command. + * + * Processes every PR once. + * + * @param {Object[]} PRs The list of PRs to cherry-pick. + * @return {Array} A two-tuple containing a list of successful cherry-picks and a list of failed ones. + */ function cherryPickRound( PRs ) { const stack = [...PRs]; const successes = []; @@ -150,8 +194,21 @@ function cherryPickRound( PRs ) { return [successes, failures]; } +/** + * Identity function + * + * @param {*} x Input. + * @return {*} Input + */ const identity = x => x; +/** + * Formats a PR object in a human readable way. + * + * @param {Object} PR PR details. + * @param {boolean} withMergeCommitHash Should include the commit hash in the output? + * @return {string} Formatted text + */ function prToString( { number, mergeCommitHash, title }, withMergeCommitHash = true ) { return [ `#${ number }`, @@ -160,11 +217,23 @@ function prToString( { number, mergeCommitHash, title }, withMergeCommitHash = t ].filter( identity ).join( ' – ' ); } +/** + * Indents a block of text with {width} spaces + * + * @param {string} text The text to indent. + * @param {number} width Number of spaces to use. + * @return {string} Indented text. + */ function indent( text, width = 3 ) { const indent = ' '.repeat( width ); return text.split( "\n" ).map( line => indent + line ).join( "\n" ); } +/** + * Attempts to cherry-pick a given commit into the current branch, + * + * @return {string} Branch name. + */ function cherryPickOne( commit ) { const result = spawnSync( 'git', ['cherry-pick', commit] ); const message = result.stdout.toString().trim(); @@ -176,6 +245,13 @@ function cherryPickOne( commit ) { return commitHashOutput.stdout.toString().trim(); } +/** + * When the cherry-picking phase is over, this function outputs the stats + * and informs about the next steps to take. + * + * @param {Array} successes Successful cherry-picks. + * @param {Array} failures Failed cherry-picks. + */ function reportSummaryNextSteps( successes, failures ) { console.log( 'Summary:' ); console.log( indent( `✅ ${ successes.length } PRs got cherry-picked cleanly` ) ); @@ -203,6 +279,14 @@ function reportSummaryNextSteps( successes, failures ) { } } +/** + * Comment on a given PR to tell the author it's been cherry-picked into a release branch + * Also, removes the backport label (or any other label used to mark this PR for backporting). + * + * Uses the `gh` CLI utility. + * + * @param {Object} pr PR details. + */ function GHcommentAndRemoveLabel( pr ) { const { number, cherryPickHash } = pr; const comment = prComment( cherryPickHash ); @@ -219,6 +303,11 @@ function GHcommentAndRemoveLabel( pr ) { } } +/** + * When cherry-pick succeeds, this function outputs the manual next steps to take. + * + * @param {Object} PR PR details. + */ function reportSuccessManual( { number, title, cherryPickHash } ) { console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); @@ -226,6 +315,11 @@ function reportSuccessManual( { number, title, cherryPickHash } ) { console.log( '' ); } +/** + * When cherry-pick fails, this function outputs the details. + * + * @param {Object} PR PR details. + */ function reportFailure( { number, title, error, mergeCommitHash } ) { console.log( indent( prUrl( number ) ) ); console.log( indent( `#${ number } ${ title }` ) ); @@ -235,18 +329,41 @@ function reportFailure( { number, title, error, mergeCommitHash } ) { console.log( '' ); } +/** + * Returns the URL of the Gutenberg PR given its number. + * + * @return {string} PR URL. + */ function prUrl( number ) { return `https://github.com/WordPress/gutenberg/pull/${ number } `; } +/** + * Returns the comment informing that a PR was just cherry-picked to the + * release branch. + * + * @param {string} cherryPickHash + * @return {string} Comment contents. + */ function prComment( cherryPickHash ) { return `I just cherry-picked this PR to the ${ BRANCH } branch to get it included in the next release: ${ cherryPickHash }`; } +/** + * Returns the current git branch. + * + * @return {string} Branch name. + */ function getCurrentBranch() { return spawnSync( 'git', ['rev-parse', '--abbrev-ref', 'HEAD'] ).stdout.toString().trim(); } +/** + * Reports when the gh CLI tool is missing, describes the consequences, asks + * whether to proceed. + * + * @return {Promise} + */ async function reportGhUnavailable() { console.log( 'Github CLI is not setup. This script will not be able to automatically' ); console.log( 'comment on the processed PRs and remove the backport label from them.' ); From d768da2db7b86b22aba414b799b76ce5af7faf17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 24 May 2022 13:53:51 +0200 Subject: [PATCH 13/21] Add the confirmation prompt before running the script --- bin/cherry-pick.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 21c3f852a6256d..81b1972dc71a51 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -24,6 +24,15 @@ const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; * * Suggests next steps */ async function main() { + console.log( `You are on branch "${BRANCH}".` ); + console.log( `This script will:` ); + console.log( `• Cherry-pick the merged PRs labeled as "${LABEL}" to this branch` ); + console.log( `• Push this branch` ); + console.log( `• Comment on each PR` ); + console.log( `• Remove the label from each PR` ); + console.log( `` ); + await promptDoYouWantToProceed(); + if ( !GITHUB_CLI_AVAILABLE ) { await reportGhUnavailable(); } @@ -372,7 +381,16 @@ async function reportGhUnavailable() { console.log( 'To enable automatic handling, install the `gh` utility from https://cli.github.com/' ); console.log( '' ); + await promptDoYouWantToProceed(); +} +/** + * Asks a CLI prompt whether the user wants to proceed. + * Exits if not. + * + * @return {Promise} + */ +async function promptDoYouWantToProceed() { const rl = readline.createInterface( { input: process.stdin, output: process.stdout, From a77fed67f384cf1474c4a30226e0a849a9b9f095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 24 May 2022 13:54:05 +0200 Subject: [PATCH 14/21] Add the script to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b41787810ac797..fdf6c4da6dbe2b 100755 --- a/package.json +++ b/package.json @@ -250,6 +250,7 @@ "build:plugin-zip": "bash ./bin/build-plugin-zip.sh", "build": "npm run build:packages && wp-scripts build", "changelog": "node ./bin/plugin/cli.js changelog", + "cherry-pick": "node ./bin/cherry-pick.mjs", "check-licenses": "concurrently \"wp-scripts check-licenses --prod --gpl2 --ignore=@react-native-community/cli,@react-native-community/cli-platform-ios\" \"wp-scripts check-licenses --dev\"", "precheck-local-changes": "npm run docs:build", "check-local-changes": "node ./bin/check-local-changes.js", From 24dc39dadea8e1f3058d0fce2a38260e6f4b829f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 17 Jun 2022 11:21:40 +0200 Subject: [PATCH 15/21] Add node-fetch as a dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fdf6c4da6dbe2b..3734b5bb2b6311 100755 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "metro-react-native-babel-transformer": "0.66.2", "mkdirp": "0.5.1", "nock": "12.0.3", + "node-fetch": "3.2.4", "node-watch": "0.7.0", "npm-run-all": "4.1.5", "patch-package": "6.2.2", From 3e88fc4b8445e5c959533d6e9acb7052742fb395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 24 May 2022 14:09:33 +0200 Subject: [PATCH 16/21] Add more documentation to the main() function --- bin/cherry-pick.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 81b1972dc71a51..a5530d7da841a2 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -17,11 +17,15 @@ const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; /** * The main function of this script. It: - * * Retrieves the list of relevant PRs to cherry-pick - * * Tries to cherry-pick them - * * Pushes the changes comments on the successful cherry-picks + * * Confirms with the developer the current branch aligns with the expectations + * * Gets local branches in sync with the remote ones + * * Requests the list of PRs to cherry-pick from GitHub API (closed, label=`Backport to WP Beta/RC`) + * * Runs `git cherry-pick {commitHash}` for each PR + * * It keeps track of the failed cherry-picks and then retries them + * * Retrying keeps going as long as at least one cherry-pick succeeds + * * Pushes the local branch to `origin` + * * (optional) Uses the [`gh` console utility](https://cli.github.com/) to comment on the remote PRs and remove the labels * * Reports the results - * * Suggests next steps */ async function main() { console.log( `You are on branch "${BRANCH}".` ); From b87a2cfcb8fc28fc9bce6ea631c31150c64b43ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 17 Jun 2022 11:14:32 +0200 Subject: [PATCH 17/21] Clearly explain how the script will use your GitHub account --- bin/cherry-pick.mjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index a5530d7da841a2..3d2ffe21707560 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -28,19 +28,21 @@ const AUTO_PROPAGATE_RESULTS_TO_GITHUB = GITHUB_CLI_AVAILABLE; * * Reports the results */ async function main() { + if ( !GITHUB_CLI_AVAILABLE ) { + await reportGhUnavailable(); + } + console.log( `You are on branch "${BRANCH}".` ); console.log( `This script will:` ); console.log( `• Cherry-pick the merged PRs labeled as "${LABEL}" to this branch` ); console.log( `• Push this branch` ); console.log( `• Comment on each PR` ); console.log( `• Remove the label from each PR` ); + console.log( `The last two actions will be performed USING YOUR GITHUB ACCOUNT that` ) + console.log( `you've linked to your GitHub CLI (gh command)` ) console.log( `` ); await promptDoYouWantToProceed(); - if ( !GITHUB_CLI_AVAILABLE ) { - await reportGhUnavailable(); - } - console.log( `$ git pull origin ${ BRANCH } --rebase...` ); cli( 'git', ['pull', 'origin', BRANCH, '--rebase'], true ); From 325f6d10fe06abef8ed2d26d6b5bc5dd4d117153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 17 Jun 2022 11:26:31 +0200 Subject: [PATCH 18/21] Fix a typo in comment --- bin/cherry-pick.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 3d2ffe21707560..9be71cf9a0dc7e 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -132,7 +132,7 @@ async function fetchPRs() { } /** - * A utility functino for GET requesting GitHub API. + * A utility function for GET requesting GitHub API. * * @param {string} path The API path to request. * @return {Promise} Parsed response JSON. From aa3b8ef00305b285d5dc534fe32c81250e1a2d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 17 Jun 2022 12:39:02 +0200 Subject: [PATCH 19/21] Fix package.json --- package-lock.json | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c378d34d0edf4..79b8004b0ae76f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18861,7 +18861,7 @@ "app-root-dir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", + "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", "dev": true }, "app-root-path": { @@ -27040,7 +27040,7 @@ "babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", - "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", + "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=", "dev": true }, "babel-plugin-apply-mdx-type-prop": { @@ -27463,7 +27463,7 @@ "batch-processor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", - "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==", + "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=", "dev": true }, "bcrypt-pbkdf": { @@ -30773,7 +30773,7 @@ "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", "dev": true }, "cssesc": { @@ -36972,7 +36972,7 @@ "has-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g==", + "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", "dev": true, "requires": { "is-glob": "^3.0.0" @@ -36981,7 +36981,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { "is-extglob": "^2.1.0" @@ -38782,7 +38782,7 @@ "is-window": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", - "integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==", + "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=", "dev": true }, "is-windows": { @@ -42159,7 +42159,7 @@ "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", "dev": true }, "js-tokens": { @@ -43688,7 +43688,7 @@ "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", - "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=", "dev": true }, "macos-release": { @@ -46989,7 +46989,7 @@ "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, "number-is-nan": { @@ -48068,7 +48068,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-event": { @@ -49400,7 +49400,7 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, "prismjs": { @@ -51700,7 +51700,7 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, "remark": { diff --git a/package.json b/package.json index 3734b5bb2b6311..c382b9b107df3f 100755 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ "metro-react-native-babel-transformer": "0.66.2", "mkdirp": "0.5.1", "nock": "12.0.3", - "node-fetch": "3.2.4", + "node-fetch": "2.6.1", "node-watch": "0.7.0", "npm-run-all": "4.1.5", "patch-package": "6.2.2", From f1b3043c52138f93fa227050b2f74d788f24debb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 21 Jun 2022 15:55:48 +0200 Subject: [PATCH 20/21] Add CLI option to specify the GitHub label used. Add additional prompt to confirm the git push command --- bin/cherry-pick.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 9be71cf9a0dc7e..7fbdeb91263477 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -6,7 +6,7 @@ import readline from 'readline'; import { spawnSync } from 'node:child_process'; -const LABEL = "Backport to WP Beta/RC"; +const LABEL = process.argv[2] || "Backport to WP Minor Release"; const BRANCH = getCurrentBranch(); const GITHUB_CLI_AVAILABLE = spawnSync( 'gh', ['auth', 'status'] ) ?.stderr @@ -35,7 +35,7 @@ async function main() { console.log( `You are on branch "${BRANCH}".` ); console.log( `This script will:` ); console.log( `• Cherry-pick the merged PRs labeled as "${LABEL}" to this branch` ); - console.log( `• Push this branch` ); + console.log( `• Ask whether you want to push this branch` ); console.log( `• Comment on each PR` ); console.log( `• Remove the label from each PR` ); console.log( `The last two actions will be performed USING YOUR GITHUB ACCOUNT that` ) @@ -58,7 +58,8 @@ async function main() { if ( successes.length ) { if ( AUTO_PROPAGATE_RESULTS_TO_GITHUB ) { - console.log( `Pushing to origin/${ BRANCH }` ); + console.log( `About to push to origin/${ BRANCH }` ); + await promptDoYouWantToProceed(); cli( 'git', ['push', 'origin', BRANCH] ); console.log( `Commenting and removing labels...` ); From e21286c438df5dc6932351f6f2591f3f877b3ad1 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Tue, 28 Jun 2022 17:35:57 +0200 Subject: [PATCH 21/21] Update bin/cherry-pick.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Greg Ziółkowski --- bin/cherry-pick.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cherry-pick.mjs b/bin/cherry-pick.mjs index 7fbdeb91263477..0bf1d7926b5bdb 100644 --- a/bin/cherry-pick.mjs +++ b/bin/cherry-pick.mjs @@ -279,7 +279,7 @@ function reportSummaryNextSteps( successes, failures ) { if ( successes.length && !AUTO_PROPAGATE_RESULTS_TO_GITHUB ) { nextSteps.push( 'Push this branch' ); nextSteps.push( 'Go to each of the cherry-picked Pull Requests' ); - nextSteps.push( 'Remove the Backport to WP Beta/RC label' ); + nextSteps.push( `Remove the ${ LABEL } label` ); nextSteps.push( 'Request a backport to wordpress-develop if required' ); nextSteps.push( 'Comment, say that PR just got cherry-picked' ); }