Skip to content

gen-changelog: add support for rebased PRs #2220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 15, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 80 additions & 46 deletions resources/gen-changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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;
}

Expand Down Expand Up @@ -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
}
}
}
}
}
Expand All @@ -227,13 +226,57 @@ 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) {
const associatedPRs = commit.associatedPullRequests.nodes.filter(
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}`,
);
Expand All @@ -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) {
Expand Down