-
Notifications
You must be signed in to change notification settings - Fork 30.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a `release-helper` script that orchestrates the multiple tools used during the cherry picking steps when working on a release line branch. Aiming to simplify and speed up the manual cherry-pick / commit review process of each release.
- Loading branch information
Showing
3 changed files
with
220 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const {execSync} = require('child_process') | ||
const {readFileSync, writeFileSync} = require('fs') | ||
|
||
const CONFLICT_INFO_FILENAME = '.cherry-pick-conflict-info.json' | ||
|
||
async function main () { | ||
const cherryPickConflictInfoJson = readFileSync(CONFLICT_INFO_FILENAME, { encoding: 'utf8' }) | ||
const backportCommitInfo = JSON.parse(cherryPickConflictInfoJson).backport | ||
|
||
if (backportCommitInfo) { | ||
const {id, labelName, msg, sha, url} = backportCommitInfo | ||
|
||
const ghOpts = { | ||
encoding: 'utf8', | ||
stdio: 'inherit', | ||
} | ||
execSync(`gh pr comment ${id} --body '${msg}'`, ghOpts) | ||
execSync(`gh pr edit ${id} --add-label '${labelName}'`, ghOpts) | ||
|
||
console.log('REQUESTED BACKPORT:') | ||
console.log(sha, url) | ||
writeFileSync(CONFLICT_INFO_FILENAME, JSON.stringify({ | ||
backport: null | ||
}, null, 2)) | ||
} else { | ||
console.error(`No backport commit info found in ${CONFLICT_INFO_FILENAME}`) | ||
} | ||
} | ||
|
||
main(); |
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 |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!/bin/bash | ||
|
||
# Requirements: | ||
# The release-helper script requires having branch-diff, changelog-maker and | ||
# the GitHub CLI installed and properly authorized in the current machine. | ||
if [[ -z "${NODEJS_RELEASE_LINE}" ]]; then | ||
printf '%s\n' "NODEJS_RELEASE_LINE env var needs to be defined." >&2 | ||
exit 1 | ||
else | ||
CURRENT="${NODEJS_RELEASE_LINE}" | ||
fi | ||
|
||
# startcherrypick will paginate and parse through the list of commits in | ||
# chunks of 20, so that it's more convenient for releasers to work in batches | ||
# having time to update the staging branch in between working sessions. | ||
function startcherrypick() { | ||
head -n 20 .commit_list | xargs git cherry-pick 2>&1 | node ./tools/release/watch-cherry-pick.js | ||
tail -n +21 .commit_list > .commit_list_next | ||
mv .commit_list_next .commit_list | ||
} | ||
|
||
function endcherrypick() { | ||
rm .commit_list | ||
} | ||
|
||
function helpmsg() { | ||
cat <<EOF | ||
Usage: release-helper <cmd> | ||
To start working on a new release begin with either of the startup commands: | ||
release-helper cherry-pick | ||
OR | ||
release-helper prepare | ||
Commands: | ||
release-helper cherry-pick Starts cherry-picking from branch-diff without using a local cache file | ||
release-helper prepare Uses branch-diff to cache a local file with a list of commits to work with | ||
release-helper start Starts cherry picking commits from main into the release line branch | ||
release-helper backport During cherry pick, asks original PR author for a backport using gh cli | ||
release-helper skip During cherry pick, skips a commit that has conflicts | ||
release-helper continue During cherry pick, after amending a commit resume the cherry picking | ||
release-helper end Stop cherry picking commits | ||
release-helper notable Retrieves notable changes, requires being in the proposal branch | ||
release-helper notable-md Markdown notable changes, requires being in the proposal branch | ||
release-helper changelog When in a proposal branch generates the changelog using changelog-maker | ||
EOF | ||
} | ||
|
||
case $1 in | ||
help|--help|-h) | ||
helpmsg | ||
;; | ||
changelog) | ||
changelog-maker --start-ref=$1 --group --filter-release --markdown | ||
;; | ||
notable) | ||
branch-diff upstream/$CURRENT.x $(git cb) --require-label=notable-change --plaintext | ||
;; | ||
notable-md) | ||
branch-diff upstream/$CURRENT.x $(git cb) --require-label=notable-change --markdown | ||
;; | ||
cherry-pick) | ||
branch-diff $CURRENT.x-staging upstream/main --exclude-label=semver-major,dont-land-on-$CURRENT.x,backport-requested-$CURRENT.x,backported-to-$CURRENT.x,backport-blocked-$CURRENT.x,backport-open-$CURRENT.x --filter-release --format=sha --reverse --cache | head | xargs git cherry-pick 2>&1 | node ./tools/release/watch-cherry-pick.js | ||
;; | ||
start) | ||
startcherrypick | ||
;; | ||
end) | ||
endcherrypick; git cherry-pick --quit | ||
;; | ||
# prepare will run branch-diff only once and store the retrieved metadata in | ||
# a `.commit_list` file, which is a pratical way to avoid hitting GitHub API | ||
# rate limits. | ||
prepare) | ||
branch-diff $CURRENT.x-staging upstream/main --exclude-label=semver-major,dont-land-on-$CURRENT.x,backport-requested-$CURRENT.x,backported-to-$CURRENT.x,backport-blocked-$CURRENT.x,backport-open-$CURRENT.x --filter-release --format=sha --reverse > .commit_list | ||
;; | ||
backport) | ||
node ./tools/release/backport.js | ||
;; | ||
continue) | ||
git -c core.editor=true cherry-pick --continue 2>&1 | node ./tools/release/watch-cherry-pick.js | ||
;; | ||
skip) | ||
git cherry-pick --skip 2>&1 | node ./tools/release/watch-cherry-pick.js | ||
;; | ||
* ) | ||
helpmsg | ||
;; | ||
esac |
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 |
---|---|---|
@@ -0,0 +1,99 @@ | ||
const {execSync} = require('child_process') | ||
const {writeFileSync} = require('fs') | ||
|
||
const LOG_FILE = '.watch-cherry-pick.log' | ||
const CONFLICT_INFO_FILENAME = '.cherry-pick-conflict-info.json' | ||
const CURRENT_RELEASE = process.env.NODEJS_RELEASE_LINE | ||
const BRANCH_NAME = `${CURRENT_RELEASE}.x-staging` | ||
const msg = 'This commit does not land cleanly on `' + BRANCH_NAME + | ||
'` and will need manual backport in case we want it in **' + | ||
CURRENT_RELEASE + '**.' | ||
const labelName = `backport-requested-${CURRENT_RELEASE}.x` | ||
|
||
function getCommitTitle(body) { | ||
const re = body.match(/^\ \ \ \ (?<title>\w*\:.*$)/m) | ||
if (re && re.groups) { | ||
return re.groups.title | ||
} | ||
} | ||
|
||
function getInfoFromCommit(sha) { | ||
const body = execSync(`git show -s ${sha}`, { encoding: 'utf8' }) | ||
const title = getCommitTitle(body) | ||
const url = body.match(/^.*(PR-URL:).?(?<url>.*)/im).groups.url | ||
const [id] = url.split('/').slice(-1) | ||
const labelsJson = execSync(`gh pr view ${id} --json=labels`, { encoding: 'utf8' }) | ||
const labels = JSON.parse(labelsJson).labels.map(i => i.name) | ||
return { sha, title, url, id, msg, labelName, labels, body } | ||
} | ||
|
||
function getConflictCommitMsg(commitInfo) { | ||
const {body, labels} = commitInfo | ||
return `CONFLICT APPLYING COMMIT: | ||
${body} | ||
labels: ${labels} | ||
` | ||
} | ||
|
||
function getSuccessCommitSha(cherryPickResult) { | ||
const re = cherryPickResult.match(/^\[v20\.x\-staging\ (?<sha>\b[0-9a-f]{7,40}\b)\]/) | ||
if (re && re.groups) { | ||
return re.groups.sha | ||
} | ||
} | ||
|
||
function getConflictCommitSha(cherryPickResult) { | ||
const re = cherryPickResult.match(/^error\:.*\ (?<sha>\b[0-9a-f]{7,40}\b)\.\.\./m) | ||
if (re && re.groups) { | ||
return re.groups.sha | ||
} | ||
} | ||
|
||
const pickedCommits = [] | ||
let conflictCommitInfo; | ||
let conflictCommitMsg; | ||
async function main() { | ||
for await (const data of process.stdin) { | ||
const cherryPickResult = String(data) | ||
|
||
writeFileSync(LOG_FILE, cherryPickResult, { flag: 'a' }) | ||
|
||
// handles commits that were successfully picked | ||
let sha = getSuccessCommitSha(cherryPickResult) | ||
if (sha) { | ||
pickedCommits.push(sha) | ||
} else { | ||
// handles a current conflict that needs manual action | ||
sha = getConflictCommitSha(cherryPickResult) | ||
if (sha) { | ||
conflictCommitInfo = getInfoFromCommit(sha) | ||
conflictCommitMsg = getConflictCommitMsg(conflictCommitInfo) | ||
} | ||
} | ||
} | ||
|
||
if (pickedCommits.length) { | ||
console.log('SUCCESSFULLY PICKED COMMITS REPORT') | ||
console.log('---') | ||
pickedCommits | ||
.map(i => getInfoFromCommit(i)) | ||
.forEach(({ sha, title, url, labels }) => { | ||
console.log(sha, url) | ||
console.log(title) | ||
console.log('labels:', labels.join(', ')) | ||
console.log('---') | ||
}) | ||
} else { | ||
console.log('NO ADDITIONAL COMMIT PICKED') | ||
} | ||
console.log('\n') | ||
|
||
if (conflictCommitInfo) { | ||
console.error(conflictCommitMsg) | ||
writeFileSync(CONFLICT_INFO_FILENAME, JSON.stringify({ | ||
backport: conflictCommitInfo | ||
}, null, 2)) | ||
} | ||
} | ||
|
||
main(); |