Skip to content

Commit a8529ed

Browse files
authored
feat(git-node): auto-fetch comparison branch when preparing release (#846)
1 parent 15ae401 commit a8529ed

File tree

1 file changed

+66
-27
lines changed

1 file changed

+66
-27
lines changed

lib/prepare_release.js

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { promises as fs } from 'node:fs';
44
import semver from 'semver';
55
import { replaceInFile } from 'replace-in-file';
66

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

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

190189
if (!proceed) {
@@ -335,18 +334,8 @@ export default class ReleasePreparation extends Session {
335334
return missing;
336335
}
337336

338-
async calculateNewVersion(major) {
339-
const { cli } = this;
340-
341-
cli.startSpinner(`Parsing CHANGELOG for most recent release of v${major}.x`);
342-
const data = await fs.readFile(
343-
path.resolve(`doc/changelogs/CHANGELOG_V${major}.md`),
344-
'utf8'
345-
);
346-
const [,, minor, patch] = /<a href="#(\d+)\.(\d+)\.(\d+)">\1\.\2\.\3<\/a><br\/>/.exec(data);
347-
348-
cli.stopSpinner(`Latest release on ${major}.x line is ${major}.${minor}.${patch}`);
349-
const changelog = this.getChangelog(`v${major}.${minor}.${patch}`);
337+
async calculateNewVersion({ tagName, major, minor, patch }) {
338+
const changelog = this.getChangelog(tagName);
350339

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

534523
async createProposalBranch(base = this.stagingBranch) {
535-
const { upstream, newVersion } = this;
524+
const { newVersion } = this;
536525
const proposalBranch = `v${newVersion}-proposal`;
537526

538527
await runAsync('git', [
539528
'checkout',
540529
'-b',
541530
proposalBranch,
542-
`${upstream}/${base}`
531+
base
543532
]);
544533
return proposalBranch;
545534
}
@@ -614,7 +603,7 @@ export default class ReleasePreparation extends Session {
614603
messageBody.push('This is a security release.\n\n');
615604
}
616605

617-
const notableChanges = this.getBranchDiff({
606+
const notableChanges = await this.getBranchDiff({
618607
onlyNotableChanges: true,
619608
format: 'plaintext'
620609
});
@@ -641,8 +630,9 @@ export default class ReleasePreparation extends Session {
641630
return useMessage;
642631
}
643632

644-
getBranchDiff(opts) {
633+
async getBranchDiff(opts) {
645634
const {
635+
cli,
646636
versionComponents = {},
647637
upstream,
648638
newVersion,
@@ -670,6 +660,10 @@ export default class ReleasePreparation extends Session {
670660
'semver-minor'
671661
];
672662

663+
await forceRunAsync('git', ['remote', 'set-branches', '--add', upstream, releaseBranch], {
664+
ignoreFailures: false
665+
});
666+
await forceRunAsync('git', ['fetch', upstream, releaseBranch], { ignoreFailures: false });
673667
branchDiffOptions = [
674668
`${upstream}/${releaseBranch}`,
675669
proposalBranch,
@@ -688,20 +682,43 @@ export default class ReleasePreparation extends Session {
688682
'baking-for-lts'
689683
];
690684

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

700694
if (!isSemverMinor) {
701695
excludeLabels.push('semver-minor');
702696
}
703697
}
704698

699+
await forceRunAsync('git', ['fetch', upstream, comparisonBranch], { ignoreFailures: false });
700+
const commits = await forceRunAsync('git', ['rev-parse', 'FETCH_HEAD', comparisonBranch], {
701+
captureStdout: 'lines',
702+
ignoreFailures: true
703+
});
704+
if (commits == null) {
705+
const shouldCreateCompareBranch = await cli.prompt(
706+
`No local branch ${comparisonBranch}, do you want to create it?`);
707+
if (shouldCreateCompareBranch) {
708+
await forceRunAsync('git', ['branch', comparisonBranch, 'FETCH_HEAD'], {
709+
ignoreFailures: false
710+
});
711+
}
712+
} else if (commits[0] !== commits[1]) {
713+
const shouldUpBranch = cli.prompt(`Local ${comparisonBranch} branch is not in sync with ${
714+
upstream}/${comparisonBranch}, do you want to update it?`);
715+
if (shouldUpBranch) {
716+
await forceRunAsync('git', ['branch', '-f', comparisonBranch, 'FETCH_HEAD'], {
717+
ignoreFailures: false
718+
});
719+
}
720+
}
721+
705722
branchDiffOptions = [
706723
stagingBranch,
707724
comparisonBranch,
@@ -718,6 +735,27 @@ export default class ReleasePreparation extends Session {
718735
return runSync(branchDiff, branchDiffOptions);
719736
}
720737

738+
async getLastRelease(major) {
739+
const { cli } = this;
740+
741+
cli.startSpinner(`Parsing CHANGELOG for most recent release of v${major}.x`);
742+
const data = await fs.readFile(
743+
path.resolve(`doc/changelogs/CHANGELOG_V${major}.md`),
744+
'utf8'
745+
);
746+
const [,, minor, patch] = /<a href="#(\d+)\.(\d+)\.(\d+)">\1\.\2\.\3<\/a><br\/>/.exec(data);
747+
this.isLTS = data.includes('<th>LTS ');
748+
749+
cli.stopSpinner(`Latest release on ${major}.x line is ${major}.${minor}.${patch}${
750+
this.isLTS ? ' (LTS)' : ''
751+
}`);
752+
753+
return {
754+
tagName: await this.getLastRef(`v${major}.${minor}.${patch}`),
755+
major, minor, patch
756+
};
757+
}
758+
721759
async prepareLocalBranch() {
722760
const { cli } = this;
723761
if (this.newVersion) {
@@ -736,6 +774,7 @@ export default class ReleasePreparation extends Session {
736774
this.stagingBranch = `v${newVersion.major}.x-staging`;
737775
this.releaseBranch = `v${newVersion.major}.x`;
738776
await this.tryResetBranch();
777+
await this.getLastRelease(newVersion.major);
739778
return;
740779
}
741780

@@ -751,7 +790,7 @@ export default class ReleasePreparation extends Session {
751790
}
752791
this.stagingBranch = currentBranch;
753792
await this.tryResetBranch();
754-
this.versionComponents = await this.calculateNewVersion(match[1]);
793+
this.versionComponents = await this.calculateNewVersion(await this.getLastRelease(match[1]));
755794
const { major, minor, patch } = this.versionComponents;
756795
this.newVersion = `${major}.${minor}.${patch}`;
757796
this.releaseBranch = `v${major}.x`;

0 commit comments

Comments
 (0)