Skip to content

Commit

Permalink
feat(git-node): auto-fetch latest release tag when preparing release (#…
Browse files Browse the repository at this point in the history
…842)

And suggest fetching the upstream staging branch at the start of the session.
  • Loading branch information
aduh95 committed Aug 27, 2024
1 parent 4e8ec9c commit 15ae401
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 57 deletions.
2 changes: 2 additions & 0 deletions components/git/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ async function main(state, argv, cli, dir) {
if (state === PREPARE) {
const prep = new ReleasePreparation(argv, cli, dir);

await prep.prepareLocalBranch();

if (prep.warnForWrongBranch()) return;

// If the new version was automatically calculated, confirm it.
Expand Down
135 changes: 78 additions & 57 deletions lib/prepare_release.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { promises as fs } from 'node:fs';
import semver from 'semver';
import { replaceInFile } from 'replace-in-file';

import { getMergedConfig } from './config.js';
import { runAsync, runSync } from './run.js';
import { writeJson, readJson } from './file.js';
import Request from './request.js';
Expand All @@ -15,58 +14,25 @@ import {
updateTestProcessRelease
} from './release/utils.js';
import CherryPick from './cherry_pick.js';
import Session from './session.js';

const isWindows = process.platform === 'win32';

export default class ReleasePreparation {
export default class ReleasePreparation extends Session {
constructor(argv, cli, dir) {
this.cli = cli;
this.dir = dir;
super(cli, dir);
this.isSecurityRelease = argv.security;
this.isLTS = false;
this.isLTSTransition = argv.startLTS;
this.runBranchDiff = !argv.skipBranchDiff;
this.ltsCodename = '';
this.date = '';
this.config = getMergedConfig(this.dir);
this.filterLabels = argv.filterLabel && argv.filterLabel.split(',');
this.newVersion = argv.newVersion;
}

// Ensure the preparer has set an upstream and username.
if (this.warnForMissing()) {
cli.error('Failed to begin the release preparation process.');
return;
}

// Allow passing optional new version.
if (argv.newVersion) {
const newVersion = semver.clean(argv.newVersion);
if (!semver.valid(newVersion)) {
cli.warn(`${newVersion} is not a valid semantic version.`);
return;
}
this.newVersion = newVersion;
} else {
this.newVersion = this.calculateNewVersion();
}

const { upstream, owner, repo, newVersion } = this;

this.versionComponents = {
major: semver.major(newVersion),
minor: semver.minor(newVersion),
patch: semver.patch(newVersion)
};

this.stagingBranch = `v${this.versionComponents.major}.x-staging`;
this.releaseBranch = `v${this.versionComponents.major}.x`;

const upstreamHref = runSync('git', [
'config', '--get',
`remote.${upstream}.url`]).trim();
if (!new RegExp(`${owner}/${repo}(?:.git)?$`).test(upstreamHref)) {
cli.warn('Remote repository URL does not point to the expected ' +
`repository ${owner}/${repo}`);
}
get branch() {
return this.stagingBranch;
}

warnForNonMergeablePR(pr) {
Expand Down Expand Up @@ -369,24 +335,29 @@ export default class ReleasePreparation {
return missing;
}

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

const lastTagVersion = semver.clean(this.getLastRef());
const lastTag = {
major: semver.major(lastTagVersion),
minor: semver.minor(lastTagVersion),
patch: semver.patch(lastTagVersion)
};
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);

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

const newVersion = { major, minor, patch };
if (changelog.includes('SEMVER-MAJOR')) {
newVersion = `${lastTag.major + 1}.0.0`;
newVersion.major++;
newVersion.minor = 0;
newVersion.patch = 0;
} else if (changelog.includes('SEMVER-MINOR') || this.isLTSTransition) {
newVersion = `${lastTag.major}.${lastTag.minor + 1}.0`;
newVersion.minor++;
newVersion.patch = 0;
} else {
newVersion = `${lastTag.major}.${lastTag.minor}.${lastTag.patch + 1}`;
newVersion.patch++;
}

return newVersion;
Expand All @@ -396,11 +367,22 @@ export default class ReleasePreparation {
return runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
}

getLastRef() {
return runSync('git', ['describe', '--abbrev=0', '--tags']).trim();
getLastRef(tagName) {
if (!tagName) {
return runSync('git', ['describe', '--abbrev=0', '--tags']).trim();
}

try {
runSync('git', ['rev-parse', tagName]);
} catch {
this.cli.startSpinner(`Error parsing git ref ${tagName}, attempting fetching it as a tag`);
runSync('git', ['fetch', this.upstream, 'tag', '-n', tagName]);
this.cli.stopSpinner(`Tag fetched: ${tagName}`);
}
return tagName;
}

getChangelog() {
getChangelog(tagName) {
const changelogMaker = new URL(
'../node_modules/.bin/changelog-maker' + (isWindows ? '.cmd' : ''),
import.meta.url
Expand All @@ -411,7 +393,7 @@ export default class ReleasePreparation {
'--markdown',
'--filter-release',
'--start-ref',
this.getLastRef()
this.getLastRef(tagName)
]).trim();
}

Expand Down Expand Up @@ -736,6 +718,45 @@ export default class ReleasePreparation {
return runSync(branchDiff, branchDiffOptions);
}

async prepareLocalBranch() {
const { cli } = this;
if (this.newVersion) {
// If the CLI asked for a specific version:
const newVersion = semver.parse(this.newVersion);
if (!newVersion) {
cli.warn(`${this.newVersion} is not a valid semantic version.`);
return;
}
this.newVersion = newVersion.version;
this.versionComponents = {
major: newVersion.major,
minor: newVersion.minor,
patch: newVersion.patch
};
this.stagingBranch = `v${newVersion.major}.x-staging`;
this.releaseBranch = `v${newVersion.major}.x`;
await this.tryResetBranch();
return;
}

// Otherwise, we need to figure out what's the next version number for the
// release line of the branch that's currently checked out.
const currentBranch = this.getCurrentBranch();
const match = /^v(\d+)\.x-staging$/.exec(currentBranch);

if (!match) {
cli.warn(`Cannot prepare a release from ${currentBranch
}. Switch to a staging branch before proceeding.`);
return;
}
this.stagingBranch = currentBranch;
await this.tryResetBranch();
this.versionComponents = await this.calculateNewVersion(match[1]);
const { major, minor, patch } = this.versionComponents;
this.newVersion = `${major}.${minor}.${patch}`;
this.releaseBranch = `v${major}.x`;
}

warnForWrongBranch() {
const {
cli,
Expand Down

0 comments on commit 15ae401

Please sign in to comment.