Skip to content

Commit fea3eb7

Browse files
committed
Fix incorrect commit range when tags are created out of order
Sort tags by semver version instead of git creation date when determining the previous tag for release notes. This fixes the issue where hotfix tags (e.g., v1.0.1 released after v1.2.0) would incorrectly be used as the starting point for the commit range. Fixes #758
1 parent 15040c8 commit fea3eb7

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

source/git-util.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'node:path';
22
import {execa} from 'execa';
33
import escapeStringRegexp from 'escape-string-regexp';
44
import ignoreWalker from 'ignore-walk';
5+
import semver from 'semver';
56
import * as util from './util.js';
67

78
export const latestTag = async () => {
@@ -39,19 +40,27 @@ export const readFileFromLastRelease = async file => {
3940
return oldFile;
4041
};
4142

42-
/** Returns an array of tags, sorted by creation date in ascending order. */
43+
/** Returns an array of all tags. */
4344
const tagList = async () => {
44-
const {stdout} = await execa('git', ['tag', '--sort=creatordate']);
45+
const {stdout} = await execa('git', ['tag']);
4546
return stdout ? stdout.split('\n') : [];
4647
};
4748

49+
/** Returns an array of tags sorted by semver in ascending order. Non-semver tags are excluded. */
50+
const tagListSortedBySemver = async () => {
51+
const tags = await tagList();
52+
return tags
53+
.filter(tag => semver.valid(tag))
54+
.sort((a, b) => semver.compare(a, b));
55+
};
56+
4857
const firstCommit = async () => {
4958
const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
5059
return stdout;
5160
};
5261

5362
export const previousTagOrFirstCommit = async () => {
54-
const tags = await tagList();
63+
const tags = await tagListSortedBySemver();
5564

5665
if (tags.length === 0) {
5766
return;
@@ -62,9 +71,14 @@ export const previousTagOrFirstCommit = async () => {
6271
}
6372

6473
try {
65-
// Return the tag before the latest one.
74+
// Return the tag before the latest one (sorted by semver).
6675
const latest = await latestTag();
6776
const index = tags.indexOf(latest);
77+
78+
if (index === -1 || index === 0) {
79+
return firstCommit();
80+
}
81+
6882
return tags[index - 1];
6983
} catch {
7084
// Fallback to the first commit.

test/git-util/previous-tag-or-first-commit.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,22 @@ test('multiple tags', createFixture, async ({t, $$}) => {
4242
t.is(result, 'v3.0.0');
4343
});
4444

45+
test('tags created out of order - should sort by semver not creation date', createFixture, async ({t, $$}) => {
46+
// Create tags out of semver order (simulating a hotfix scenario)
47+
await $$`git tag v1.0.0`;
48+
await t.context.commitNewFile();
49+
await $$`git tag v1.2.0`;
50+
await t.context.commitNewFile();
51+
await $$`git tag v1.2.1`;
52+
await t.context.commitNewFile();
53+
// Create a hotfix tag for an older version (created after v1.2.1 but semver is lower)
54+
await $$`git tag v1.0.1`;
55+
await t.context.commitNewFile();
56+
await $$`git tag v1.2.2`;
57+
}, async ({t, testedModule: {previousTagOrFirstCommit}}) => {
58+
// Should return v1.2.1 (semver previous), not v1.0.1 (creation date previous)
59+
const result = await previousTagOrFirstCommit();
60+
t.is(result, 'v1.2.1');
61+
});
62+
4563
test.todo('test fallback case');

0 commit comments

Comments
 (0)