Skip to content
Merged
Show file tree
Hide file tree
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
29 changes: 28 additions & 1 deletion .github/scripts/__tests__/release-ready-plan.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { buildPlan, summarize } = require('../release-ready-plan');
const { buildPlan, summarize, isHotfixTrain } = require('../release-ready-plan');

const entries = list => list.map(([key, version]) => ({ key, version }));
const branches = (...list) => (repo, branch) => list.includes(`${repo}:${branch}`);
Expand Down Expand Up @@ -70,6 +70,33 @@ describe('buildPlan', () => {
});
});

describe('hotfix trains', () => {
const all = () => true;
const list = entries([
['spring-cloud-config', '5.0.6.1-SNAPSHOT'],
['spring-cloud-commons', '5.0.4-SNAPSHOT'],
]);

it('recognises a 4-segment train', () => {
expect(isHotfixTrain('2025.1.4.1')).toBe(true);
expect(isHotfixTrain('2025.1.4')).toBe(false);
expect(isHotfixTrain('2026.0.0-RC1')).toBe(false);
});

it('plans only the hotfix versions', () => {
const { todo, skipped } = buildPlan({
train: '2025.1.4.1', entries: list, branchExists: all, tagExists: noTags,
});
expect(todo.map(t => t.project)).toEqual(['spring-cloud-config']);
expect(skipped).toEqual([{ project: 'spring-cloud-commons', reason: "not a hotfix version '5.0.4-SNAPSHOT'" }]);
});

it('plans the non-hotfix versions for a regular train', () => {
const { todo } = buildPlan({ train: '2025.1.4', entries: list, branchExists: all, tagExists: noTags });
expect(todo).toHaveLength(2);
});
});

describe('summarize', () => {
it('lists both sections', () => {
const out = summarize({
Expand Down
19 changes: 16 additions & 3 deletions .github/scripts/release-ready-plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@
//
// A project the train is not releasing still has an entry, naming its previous release. That
// release is tagged, or its branch is gone, so the same two checks leave it out.
//
// A hotfix train (2025.1.4.1) is the exception. Its file also lists the projects that are not
// getting a hotfix, at the versions they have in the regular train. Those have no tag yet and
// do have a branch, so the checks above would pick them up - but the regular train's run
// marks them ready, so only the 4-segment (x.y.z.h) versions are planned for a hotfix train.

const fs = require('fs');
const { fetchReleaserConfig } = require('./releaser-config');
const { NON_PROJECT_KEYS, ORG, ghBranchExists, ghTagExists } = require('./release-branch-plan');

// A hotfix train has four segments, like the hotfix versions in it.
const isHotfixTrain = train => /^\d+\.\d+\.\d+\.\d+$/.test(String(train).trim());

// Pure: entries in, plan out. `branchExists(repo, branch)` and `tagExists(repo, tag)` are
// injected so tests need no network.
const buildPlan = ({ entries, branchExists, tagExists }) => {
const buildPlan = ({ train, entries, branchExists, tagExists }) => {
const hotfix = isHotfixTrain(train);
const todo = [];
const skipped = [];
for (const { key, version: raw } of entries) {
Expand All @@ -30,6 +39,10 @@ const buildPlan = ({ entries, branchExists, tagExists }) => {
skipped.push({ project: key, reason: `unusable version '${raw}'` });
continue;
}
if (hotfix && !/^\d+\.\d+\.\d+\.\d+$/.test(version)) {
skipped.push({ project: key, reason: `not a hotfix version '${raw}'` });
continue;
}
const branch = `release/${version}`;
const ossRepo = `${ORG}/${key}`;
const commercialRepo = `${ossRepo}-commercial`;
Expand Down Expand Up @@ -63,7 +76,7 @@ const summarize = ({ train, file, todo, skipped }) => [
'',
].join('\n');

module.exports = { buildPlan, summarize };
module.exports = { buildPlan, summarize, isHotfixTrain };

// CLI: node release-ready-plan.js <spring-cloud-release-train>
//
Expand All @@ -77,7 +90,7 @@ if (require.main === module) {
}
try {
const { file, entries } = fetchReleaserConfig(train.trim());
const plan = buildPlan({ entries, branchExists: ghBranchExists, tagExists: ghTagExists });
const plan = buildPlan({ train, entries, branchExists: ghBranchExists, tagExists: ghTagExists });
const report = summarize({ train, file, ...plan });

process.stdout.write(report + '\n');
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/README-mark-release-train-ready.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ The properties file is read as given, with no `-INTERNAL-SNAPSHOT` filter, so a
the train is not releasing is left out only because its entry names a release that is already
tagged or whose branch is gone. Check the *Skipped* list on an unfamiliar train.

## Hotfix trains

A 4-segment train (e.g. `2025.1.4.1`) lists every project, including those without a hotfix, at
the versions they have in the regular train. Those have a branch and no tag yet, but the regular
train's run marks them ready, so for a hotfix train only the 4-segment (`x.y.z.h`) versions are
planned. The rest are skipped as "not a hotfix version".

## Concurrent `projects.json` updates

Each child ends by retiring its branch from `config/projects.json`, so the children push to
Expand Down
Loading