diff --git a/branch-suggestion/scripts/common/__tests__/match-branches.test.js b/branch-suggestion/scripts/common/__tests__/match-branches.test.js index af0e512..2840b4b 100644 --- a/branch-suggestion/scripts/common/__tests__/match-branches.test.js +++ b/branch-suggestion/scripts/common/__tests__/match-branches.test.js @@ -11,13 +11,13 @@ describe('generateBranchCandidates', () => { it('should generate candidates for patch release', () => { const parsed = { major: 5, minor: 8, patch: 1 }; const candidates = generateBranchCandidates(parsed); - expect(candidates).toEqual(['release-5.8', 'release-5', 'master']); + expect(candidates).toEqual(['release-5.8.1', 'release-5.8', 'release-5', 'master']); }); it('should generate candidates for minor release', () => { const parsed = { major: 5, minor: 8, patch: 0 }; const candidates = generateBranchCandidates(parsed); - expect(candidates).toEqual(['release-5.8', 'release-5', 'master']); + expect(candidates).toEqual(['release-5.8.0', 'release-5.8', 'release-5', 'master']); }); it('should generate candidates for major release', () => { @@ -91,6 +91,11 @@ describe('getBranchPriority', () => { expect(priority).toBe('required'); }); + it('should return required for exact patch version branch', () => { + const priority = getBranchPriority('release-5.8.1', { parsed: { major: 5, minor: 8, patch: 1 } }); + expect(priority).toBe('required'); + }); + it('should return required for patch release branch', () => { const priority = getBranchPriority('release-5.8', { parsed: { major: 5, minor: 8, patch: 1 } }); expect(priority).toBe('required'); @@ -113,6 +118,15 @@ describe('getBranchReason', () => { expect(reason).toContain('Main development branch'); }); + it('should return reason for exact patch version branch', () => { + const reason = getBranchReason('release-5.8.1', { + name: '5.8.1', + parsed: { major: 5, minor: 8, patch: 1 } + }); + expect(reason).toContain('Exact version branch'); + expect(reason).toContain('5.8.1'); + }); + it('should return reason for patch release', () => { const reason = getBranchReason('release-5.8', { name: '5.8.1', @@ -192,4 +206,38 @@ describe('matchBranches', () => { expect(results[0].branches).toHaveLength(1); expect(results[0].branches[0].branch).toBe('master'); }); + + it('should prefer exact version match when available', () => { + const fixVersions = [ + { name: '5.10.1', parsed: { major: 5, minor: 10, patch: 1, component: [] } } + ]; + const repoBranches = [ + { name: 'master' }, + { name: 'release-5.10' }, + { name: 'release-5.10.1' } + ]; + + const results = matchBranches(fixVersions, repoBranches); + expect(results[0].branches).toHaveLength(3); + expect(results[0].branches[0].branch).toBe('release-5.10.1'); + expect(results[0].branches[0].priority).toBe('required'); + expect(results[0].branches[1].branch).toBe('release-5.10'); + expect(results[0].branches[1].priority).toBe('required'); + expect(results[0].branches[2].branch).toBe('master'); + }); + + it('should fallback to minor version when exact match not available', () => { + const fixVersions = [ + { name: '5.10.1', parsed: { major: 5, minor: 10, patch: 1, component: [] } } + ]; + const repoBranches = [ + { name: 'master' }, + { name: 'release-5.10' } + ]; + + const results = matchBranches(fixVersions, repoBranches); + expect(results[0].branches).toHaveLength(2); + expect(results[0].branches[0].branch).toBe('release-5.10'); + expect(results[0].branches[0].priority).toBe('required'); + }); }); diff --git a/branch-suggestion/scripts/common/match-branches.js b/branch-suggestion/scripts/common/match-branches.js index 2dd6537..08d14db 100644 --- a/branch-suggestion/scripts/common/match-branches.js +++ b/branch-suggestion/scripts/common/match-branches.js @@ -20,17 +20,22 @@ function generateBranchCandidates(parsedVersion) { const { major, minor, patch } = parsedVersion; const candidates = []; - // For patch releases (X.Y.Z where Z > 0): need release-X.Y + // First priority: exact match for the full version (X.Y.Z) + if (patch !== null && minor !== null) { + candidates.push(`release-${major}.${minor}.${patch}`); + } + + // Second priority: minor version branch (X.Y) for patch releases if (patch !== null && patch > 0 && minor !== null) { candidates.push(`release-${major}.${minor}`); } - // For minor releases (X.Y.0 or X.Y): need release-X.Y + // Third priority: minor version branch for minor releases (X.Y.0 or X.Y) if (minor !== null) { candidates.push(`release-${major}.${minor}`); } - // For any version: might need release-X (major version branch) + // Fourth priority: major version branch (X) if (major !== null) { candidates.push(`release-${major}`); } @@ -133,6 +138,12 @@ function getBranchReason(branch, fixVersion) { return 'Main development branch - ensures fix is in all future releases'; } + // Exact patch version match (e.g., release-5.10.1) + if (patch !== null && branch === `release-${major}.${minor}.${patch}`) { + return `Exact version branch for ${fixVersion.name} - specific patch release`; + } + + // Minor version branch (e.g., release-5.10) if (branch === `release-${major}.${minor}`) { if (patch > 0) { return `Minor version branch for ${major}.${minor}.x patches - required for creating ${fixVersion.name}`; @@ -141,6 +152,7 @@ function getBranchReason(branch, fixVersion) { } } + // Major version branch (e.g., release-5) if (branch === `release-${major}`) { return `Major version branch for all ${major}.x releases`; } @@ -162,6 +174,11 @@ function getBranchPriority(branch, fixVersion) { return 'required'; } + // Exact patch version match is required (e.g., release-5.10.1 for version 5.10.1) + if (patch !== null && branch === `release-${major}.${minor}.${patch}`) { + return 'required'; + } + // For patch releases (5.8.1), release-5.8 is required if (patch > 0 && branch === `release-${major}.${minor}`) { return 'required'; @@ -233,10 +250,31 @@ function formatBranchSuggestions(matchResults, jiraTicket = {}) { lines.push('---'); lines.push(''); lines.push('### 📋 Workflow'); - lines.push('1. Merge this PR to `master` first'); - lines.push('2. After merging, comment on the **merged PR** with `/release to ` to cherry-pick to release branches'); - lines.push('3. Example: `/release to release-5.8`'); - lines.push('4. The bot will automatically create a backport PR to the specified release branch'); + lines.push(''); + lines.push('1. **Merge this PR to `master` first**'); + lines.push(''); + + // Collect all non-master branches that need cherry-picking using Set for O(1) operations + const releaseBranchesSet = new Set(); + for (const result of matchResults) { + for (const branch of result.branches) { + if (branch.branch !== 'master') { + releaseBranchesSet.add(branch.branch); + } + } + } + const releaseBranches = Array.from(releaseBranchesSet); + + if (releaseBranches.length > 0) { + lines.push('2. **Cherry-pick to release branches** by commenting on the **merged PR**:'); + lines.push(''); + for (const branch of releaseBranches) { + lines.push(` - \`/release to ${branch}\``); + } + lines.push(''); + lines.push('3. **Automated backport** - The bot will automatically create backport PRs to the specified release branches'); + } + lines.push(''); lines.push('');