From 2ffa10a09aa0ba7b6b01868336ffda67b288b3d1 Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Tue, 15 Oct 2019 17:17:04 +0300 Subject: [PATCH] gen-changelog: add support for rebased PRs (#2220) --- resources/gen-changelog.js | 126 +++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 46 deletions(-) diff --git a/resources/gen-changelog.js b/resources/gen-changelog.js index 2670f32f60..d29dd17bd9 100644 --- a/resources/gen-changelog.js +++ b/resources/gen-changelog.js @@ -47,14 +47,14 @@ if (!packageJSON.repository || typeof packageJSON.repository.url !== 'string') { process.exit(1); } -const match = /https:\/\/github.com\/([^/]+)\/([^/]+).git/.exec( +const repoURLMatch = /https:\/\/github.com\/([^/]+)\/([^/]+).git/.exec( packageJSON.repository.url, ); -if (match == null) { +if (repoURLMatch == null) { console.error('Cannot extract organisation and repo name from repo URL!'); process.exit(1); } -const [, githubOrg, githubRepo] = match; +const [, githubOrg, githubRepo] = repoURLMatch; getChangeLog() .then(changelog => process.stdout.write(changelog)) @@ -73,22 +73,35 @@ function getChangeLog() { } const date = exec('git log -1 --format=%cd --date=short'); - return getCommitsInfo(commitsList.split('\n')).then(commitsInfo => - genChangeLog(tag, date, commitsInfo), - ); + return getCommitsInfo(commitsList.split('\n')) + .then(commitsInfo => getPRsInfo(commitsInfoToPRs(commitsInfo))) + .then(prsInfo => genChangeLog(tag, date, prsInfo)); } -function genChangeLog(tag, date, commitsInfo) { - const allPRs = commitsInfoToPRs(commitsInfo); +function genChangeLog(tag, date, allPRs) { const byLabel = {}; const commitersByLogin = {}; for (const pr of allPRs) { - if (!labelsConfig[pr.label]) { - throw new Error('Unknown label: ' + pr.label + pr.number); + const labels = pr.labels.nodes + .map(label => label.name) + .filter(label => label.startsWith('PR: ')); + + if (labels.length === 0) { + throw new Error(`PR #${pr.number} missing label`); } - byLabel[pr.label] = byLabel[pr.label] || []; - byLabel[pr.label].push(pr); + if (labels.length > 1) { + throw new Error( + `PR #${pr.number} has conflicting labels: ` + labels.join('\n'), + ); + } + + const label = labels[0]; + if (!labelsConfig[label]) { + throw new Error('Unknown label: ' + label + pr.number); + } + byLabel[label] = byLabel[label] || []; + byLabel[label].push(pr); commitersByLogin[pr.author.login] = pr.author; } @@ -188,23 +201,9 @@ async function batchCommitInfo(commits) { associatedPullRequests(first: 10) { nodes { number - title - url - author { - login - url - ... on User { - name - } - } repository { nameWithOwner } - labels(first: 10) { - nodes { - name - } - } } } } @@ -227,6 +226,45 @@ async function batchCommitInfo(commits) { return commitsInfo; } +async function batchPRInfo(prs) { + let prsSubQuery = ''; + for (const number of prs) { + prsSubQuery += ` + pr_${number}: pullRequest(number: ${number}) { + number + title + url + author { + login + url + ... on User { + name + } + } + labels(first: 10) { + nodes { + name + } + } + } + `; + } + + const response = await graphqlRequest(` + { + repository(owner: "${githubOrg}", name: "${githubRepo}") { + ${prsSubQuery} + } + } + `); + + const prsInfo = []; + for (const number of prs) { + prsInfo.push(response.repository['pr_' + number]); + } + return prsInfo; +} + function commitsInfoToPRs(commits) { const prs = {}; for (const commit of commits) { @@ -234,6 +272,11 @@ function commitsInfoToPRs(commits) { pr => pr.repository.nameWithOwner === `${githubOrg}/${githubRepo}`, ); if (associatedPRs.length === 0) { + const match = / \(#([0-9]+)\)$/m.exec(commit.message); + if (match) { + prs[parseInt(match[1], 10)] = true; + continue; + } throw new Error( `Commit ${commit.oid} has no associated PR: ${commit.message}`, ); @@ -244,30 +287,21 @@ function commitsInfoToPRs(commits) { ); } - const pr = associatedPRs[0]; - const labels = pr.labels.nodes - .map(label => label.name) - .filter(label => label.startsWith('PR: ')); + prs[associatedPRs[0].number] = true; + } - if (labels.length === 0) { - throw new Error(`PR #${pr.number} missing label`); - } - if (labels.length > 1) { - throw new Error( - `PR #${pr.number} has conflicting labels: ` + labels.join('\n'), - ); - } + return Object.keys(prs); +} - prs[pr.number] = { - number: pr.number, - title: pr.title, - url: pr.url, - author: pr.author, - label: labels[0], - }; +async function getPRsInfo(commits) { + // Split pr into batches of 50 to prevent timeouts + const prInfoPromises = []; + for (let i = 0; i < commits.length; i += 50) { + const batch = commits.slice(i, i + 50); + prInfoPromises.push(batchPRInfo(batch)); } - return Object.values(prs); + return (await Promise.all(prInfoPromises)).flat(); } async function getCommitsInfo(commits) {