Skip to content

Commit

Permalink
feat(git-node): auto-fetch comparison branch when preparing release (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
aduh95 authored Aug 28, 2024
1 parent 15ae401 commit a8529ed
Showing 1 changed file with 66 additions and 27 deletions.
93 changes: 66 additions & 27 deletions lib/prepare_release.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { promises as fs } from 'node:fs';
import semver from 'semver';
import { replaceInFile } from 'replace-in-file';

import { runAsync, runSync } from './run.js';
import { forceRunAsync, runAsync, runSync } from './run.js';
import { writeJson, readJson } from './file.js';
import Request from './request.js';
import auth from './auth.js';
Expand Down Expand Up @@ -171,7 +171,7 @@ export default class ReleasePreparation extends Session {
// Check the branch diff to determine if the releaser
// wants to backport any more commits before proceeding.
cli.startSpinner('Fetching branch-diff');
const raw = this.getBranchDiff({
const raw = await this.getBranchDiff({
onlyNotableChanges: false,
comparisonBranch: newVersion
});
Expand All @@ -181,10 +181,9 @@ export default class ReleasePreparation extends Session {

const outstandingCommits = diff.length - 1;
if (outstandingCommits !== 0) {
const staging = `v${semver.major(newVersion)}.x-staging`;
const proceed = await cli.prompt(
`There are ${outstandingCommits} commits that may be ` +
`backported to ${staging} - do you still want to proceed?`,
`backported to ${this.stagingBranch} - do you still want to proceed?`,
{ defaultAnswer: false });

if (!proceed) {
Expand Down Expand Up @@ -335,18 +334,8 @@ export default class ReleasePreparation extends Session {
return missing;
}

async calculateNewVersion(major) {
const { cli } = this;

cli.startSpinner(`Parsing CHANGELOG for most recent release of v${major}.x`);
const data = await fs.readFile(
path.resolve(`doc/changelogs/CHANGELOG_V${major}.md`),
'utf8'
);
const [,, minor, patch] = /<a href="#(\d+)\.(\d+)\.(\d+)">\1\.\2\.\3<\/a><br\/>/.exec(data);

cli.stopSpinner(`Latest release on ${major}.x line is ${major}.${minor}.${patch}`);
const changelog = this.getChangelog(`v${major}.${minor}.${patch}`);
async calculateNewVersion({ tagName, major, minor, patch }) {
const changelog = this.getChangelog(tagName);

const newVersion = { major, minor, patch };
if (changelog.includes('SEMVER-MAJOR')) {
Expand Down Expand Up @@ -478,7 +467,7 @@ export default class ReleasePreparation extends Session {
const data = await fs.readFile(majorChangelogPath, 'utf8');
const arr = data.split('\n');
const allCommits = this.getChangelog();
const notableChanges = this.getBranchDiff({ onlyNotableChanges: true });
const notableChanges = await this.getBranchDiff({ onlyNotableChanges: true });
let releaseHeader = `## ${date}, Version ${newVersion}` +
` ${releaseInfo}, @${username}\n`;
if (isSecurityRelease) {
Expand Down Expand Up @@ -532,14 +521,14 @@ export default class ReleasePreparation extends Session {
}

async createProposalBranch(base = this.stagingBranch) {
const { upstream, newVersion } = this;
const { newVersion } = this;
const proposalBranch = `v${newVersion}-proposal`;

await runAsync('git', [
'checkout',
'-b',
proposalBranch,
`${upstream}/${base}`
base
]);
return proposalBranch;
}
Expand Down Expand Up @@ -614,7 +603,7 @@ export default class ReleasePreparation extends Session {
messageBody.push('This is a security release.\n\n');
}

const notableChanges = this.getBranchDiff({
const notableChanges = await this.getBranchDiff({
onlyNotableChanges: true,
format: 'plaintext'
});
Expand All @@ -641,8 +630,9 @@ export default class ReleasePreparation extends Session {
return useMessage;
}

getBranchDiff(opts) {
async getBranchDiff(opts) {
const {
cli,
versionComponents = {},
upstream,
newVersion,
Expand Down Expand Up @@ -670,6 +660,10 @@ export default class ReleasePreparation extends Session {
'semver-minor'
];

await forceRunAsync('git', ['remote', 'set-branches', '--add', upstream, releaseBranch], {
ignoreFailures: false
});
await forceRunAsync('git', ['fetch', upstream, releaseBranch], { ignoreFailures: false });
branchDiffOptions = [
`${upstream}/${releaseBranch}`,
proposalBranch,
Expand All @@ -688,20 +682,43 @@ export default class ReleasePreparation extends Session {
'baking-for-lts'
];

let comparisonBranch = 'main';
let comparisonBranch = this.config.branch || 'main';
const isSemverMinor = versionComponents.patch === 0;
if (isLTS) {
const res = await fetch('https://nodejs.org/dist/index.json');
if (!res.ok) throw new Error('Failed to fetch', { cause: res });
const [latest] = await res.json();
// Assume Current branch matches tag with highest semver value.
const tags = runSync('git',
['tag', '-l', '--sort', '-version:refname']).trim();
const highestVersionTag = tags.split('\n')[0];
comparisonBranch = `v${semver.coerce(highestVersionTag).major}.x`;
comparisonBranch = `v${semver.coerce(latest.version).major}.x`;

if (!isSemverMinor) {
excludeLabels.push('semver-minor');
}
}

await forceRunAsync('git', ['fetch', upstream, comparisonBranch], { ignoreFailures: false });
const commits = await forceRunAsync('git', ['rev-parse', 'FETCH_HEAD', comparisonBranch], {
captureStdout: 'lines',
ignoreFailures: true
});
if (commits == null) {
const shouldCreateCompareBranch = await cli.prompt(
`No local branch ${comparisonBranch}, do you want to create it?`);
if (shouldCreateCompareBranch) {
await forceRunAsync('git', ['branch', comparisonBranch, 'FETCH_HEAD'], {
ignoreFailures: false
});
}
} else if (commits[0] !== commits[1]) {
const shouldUpBranch = cli.prompt(`Local ${comparisonBranch} branch is not in sync with ${
upstream}/${comparisonBranch}, do you want to update it?`);
if (shouldUpBranch) {
await forceRunAsync('git', ['branch', '-f', comparisonBranch, 'FETCH_HEAD'], {
ignoreFailures: false
});
}
}

branchDiffOptions = [
stagingBranch,
comparisonBranch,
Expand All @@ -718,6 +735,27 @@ export default class ReleasePreparation extends Session {
return runSync(branchDiff, branchDiffOptions);
}

async getLastRelease(major) {
const { cli } = this;

cli.startSpinner(`Parsing CHANGELOG for most recent release of v${major}.x`);
const data = await fs.readFile(
path.resolve(`doc/changelogs/CHANGELOG_V${major}.md`),
'utf8'
);
const [,, minor, patch] = /<a href="#(\d+)\.(\d+)\.(\d+)">\1\.\2\.\3<\/a><br\/>/.exec(data);
this.isLTS = data.includes('<th>LTS ');

cli.stopSpinner(`Latest release on ${major}.x line is ${major}.${minor}.${patch}${
this.isLTS ? ' (LTS)' : ''
}`);

return {
tagName: await this.getLastRef(`v${major}.${minor}.${patch}`),
major, minor, patch
};
}

async prepareLocalBranch() {
const { cli } = this;
if (this.newVersion) {
Expand All @@ -736,6 +774,7 @@ export default class ReleasePreparation extends Session {
this.stagingBranch = `v${newVersion.major}.x-staging`;
this.releaseBranch = `v${newVersion.major}.x`;
await this.tryResetBranch();
await this.getLastRelease(newVersion.major);
return;
}

Expand All @@ -751,7 +790,7 @@ export default class ReleasePreparation extends Session {
}
this.stagingBranch = currentBranch;
await this.tryResetBranch();
this.versionComponents = await this.calculateNewVersion(match[1]);
this.versionComponents = await this.calculateNewVersion(await this.getLastRelease(match[1]));
const { major, minor, patch } = this.versionComponents;
this.newVersion = `${major}.${minor}.${patch}`;
this.releaseBranch = `v${major}.x`;
Expand Down

0 comments on commit a8529ed

Please sign in to comment.