diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fef00b09a902..8c4c7e7c1921 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,6 +77,7 @@ jobs: 'ci/github-script/bot.js', 'ci/github-script/check-target-branch.js', 'ci/github-script/commits.js', + 'ci/github-script/get-pr-commit-details.js', 'ci/github-script/lint-commits.js', 'ci/github-script/merge.js', 'ci/github-script/prepare.js', diff --git a/ci/github-script/get-pr-commit-details.js b/ci/github-script/get-pr-commit-details.js new file mode 100644 index 000000000000..07ac10faa44f --- /dev/null +++ b/ci/github-script/get-pr-commit-details.js @@ -0,0 +1,93 @@ +// @ts-check +const { promisify } = require('node:util') +const execFile = promisify(require('node:child_process').execFile) + +/** + * @param {{ + * args: string[] + * core: import('@actions/core'), + * quiet?: boolean, + * repoPath?: string, + * }} RunGitProps + */ +async function runGit({ args, repoPath, core, quiet }) { + if (repoPath) { + args = ['-C', repoPath, ...args] + } + + if (!quiet) { + core.info(`About to run \`git ${args.map((s) => `'${s}'`).join(' ')}\``) + } + + return await execFile('git', args) +} + +/** + * GitHub's API will return a maximum of 250 commits. + * We will use it if we can, but fall back to using git locally. + * + * @param {{ + * context: import('@actions/github/lib/context').Context, + * core: import('@actions/core'), + * github: InstanceType, + * pr: Awaited["rest"]["pulls"]["get"]>>["data"] + * repoPath?: string, + * }} GetCommitMessagesForPRProps + * + * @returns {Promise<{ + * message: string, + * sha: string, + * }[]>} + */ +async function getCommitDetailsForPR({ context, core, github, pr, repoPath }) { + if (pr.commits < 250) { + return ( + await github.paginate(github.rest.pulls.listCommits, { + ...context.repo, + pull_number: pr.number, + }) + ).map((commit) => ({ message: commit.commit.message, sha: commit.sha })) + } else { + await runGit({ + args: ['fetch', `--depth=1`, 'origin', pr.base.sha], + repoPath, + core, + }) + await runGit({ + args: ['fetch', `--depth=${pr.commits + 1}`, 'origin', pr.head.sha], + repoPath, + core, + }) + + const shas = ( + await runGit({ + args: [ + 'rev-list', + `--max-count=${pr.commits}`, + `${pr.base.sha}..${pr.head.sha}`, + ], + repoPath, + core, + }) + ).stdout + .split('\n') + .map((s) => s.trim()) + .filter(Boolean) + + return Promise.all( + shas.map(async (sha) => ({ + sha, + message: ( + await runGit({ + args: ['log', '--format=%s', '-1', sha], + repoPath, + core, + quiet: true, + }) + ).stdout, + })), + ) + } +} + +module.exports = { getCommitDetailsForPR } diff --git a/ci/github-script/lint-commits.js b/ci/github-script/lint-commits.js index 21d709cadcbe..ddc422f9114c 100644 --- a/ci/github-script/lint-commits.js +++ b/ci/github-script/lint-commits.js @@ -1,27 +1,6 @@ // @ts-check const { classify } = require('../supportedBranches.js') -const { promisify } = require('node:util') -const execFile = promisify(require('node:child_process').execFile) - -/** - * @param {{ - * args: string[] - * core: import('@actions/core'), - * quiet?: boolean, - * repoPath?: string, - * }} RunGitProps - */ -async function runGit({ args, repoPath, core, quiet }) { - if (repoPath) { - args = ['-C', repoPath, ...args] - } - - if (!quiet) { - core.info(`About to run \`git ${args.map((s) => `'${s}'`).join(' ')}\``) - } - - return await execFile('git', args) -} +const { getCommitDetailsForPR } = require('./get-pr-commit-details.js') /** * @param {{ @@ -67,65 +46,13 @@ async function checkCommitMessages({ github, context, core, repoPath }) { return } - /** - * GitHub's API will return a maximum of 250 commits. - * We will use it if we can, but fall back to using git locally. - * This type is used to abstract over the differences between the two. - * @type {{ - * message: string, - * sha: string, - * }[]} - */ - let commits - - if (pr.commits < 250) { - commits = ( - await github.paginate(github.rest.pulls.listCommits, { - ...context.repo, - pull_number, - }) - ).map((commit) => ({ message: commit.commit.message, sha: commit.sha })) - } else { - await runGit({ - args: ['fetch', `--depth=1`, 'origin', pr.base.sha], - repoPath, - core, - }) - await runGit({ - args: ['fetch', `--depth=${pr.commits + 1}`, 'origin', pr.head.sha], - repoPath, - core, - }) - - const shas = ( - await runGit({ - args: [ - 'rev-list', - `--max-count=${pr.commits}`, - `${pr.base.sha}..${pr.head.sha}`, - ], - repoPath, - core, - }) - ).stdout - .split('\n') - .map((s) => s.trim()) - .filter(Boolean) - - commits = await Promise.all( - shas.map(async (sha) => ({ - sha, - message: ( - await runGit({ - args: ['log', '--format=%s', '-1', sha], - repoPath, - core, - quiet: true, - }) - ).stdout, - })), - ) - } + const commits = await getCommitDetailsForPR({ + context, + core, + github, + pr, + repoPath, + }) const failures = new Set()