Skip to content

Commit d75274b

Browse files
reuzelantongolub
authored andcommitted
fix: support merge workflows in generateNotes
1 parent 01b28e3 commit d75274b

File tree

4 files changed

+93
-19
lines changed

4 files changed

+93
-19
lines changed

lib/createInlinePluginCreator.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const debug = require("debug")("msr:inlinePlugin");
22
const getCommitsFiltered = require("./getCommitsFiltered");
3+
const { getTagHead } = require("./git");
34
const { updateManifestDeps, resolveReleaseType } = require("./updateDeps");
45

56
/**
@@ -40,11 +41,6 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
4041
);
4142
};
4243

43-
/**
44-
* @var {Commit[]} List of _filtered_ commits that only apply to this package.
45-
*/
46-
let commits;
47-
4844
/**
4945
* @param {object} pluginOptions Options to configure this plugin.
5046
* @param {object} context The semantic-release context.
@@ -85,12 +81,18 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
8581
* @internal
8682
*/
8783
const analyzeCommits = async (pluginOptions, context) => {
88-
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
8984
pkg._preRelease = context.branch.prerelease || null;
9085
pkg._branch = context.branch.name;
9186

9287
// Filter commits by directory.
93-
commits = await getCommitsFiltered(cwd, dir, context.lastRelease.gitHead, firstParentBranch);
88+
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
89+
const commits = await getCommitsFiltered(
90+
cwd,
91+
dir,
92+
context.lastRelease ? context.lastRelease.gitHead : undefined,
93+
context.nextRelease ? context.nextRelease.gitHead : undefined,
94+
firstParentBranch
95+
);
9496

9597
// Set context.commits so analyzeCommits does correct analysis.
9698
context.commits = commits;
@@ -151,8 +153,27 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
151153
// Vars.
152154
const notes = [];
153155

154-
// Set context.commits so analyzeCommits does correct analysis.
155-
// We need to redo this because context is a different instance each time.
156+
//get SHA of lastRelease if not already there (should have been done by Semantic Release...)
157+
if (context.lastRelease && context.lastRelease.gitTag) {
158+
if (!context.lastRelease.gitHead || context.lastRelease.gitHead === context.lastRelease.gitTag) {
159+
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitTag, {
160+
cwd: context.cwd,
161+
env: context.env,
162+
});
163+
}
164+
}
165+
166+
// Filter commits by directory (and release range)
167+
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
168+
const commits = await getCommitsFiltered(
169+
cwd,
170+
dir,
171+
context.lastRelease ? context.lastRelease.gitHead : undefined,
172+
context.nextRelease ? context.nextRelease.gitHead : undefined,
173+
firstParentBranch
174+
);
175+
176+
// Set context.commits so generateNotes does correct analysis.
156177
context.commits = commits;
157178

158179
// Get subnotes and add to list.

lib/getCommitsFiltered.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@ const debug = require("debug")("msr:commitsFilter");
1414
*
1515
* @param {string} cwd Absolute path of the working directory the Git repo is in.
1616
* @param {string} dir Path to the target directory to filter by. Either absolute, or relative to cwd param.
17-
* @param {string|void} lastHead The SHA of the previous release
17+
* @param {string|void} lastRelease The SHA of the previous release (default to start of all commits if undefined)
18+
* @param {string|void} nextRelease The SHA of the next release (default to HEAD if undefined)
1819
* @param {string|void} firstParentBranch first-parent to determine which merges went into master
1920
* @return {Promise<Array<Commit>>} The list of commits on the branch `branch` since the last release.
2021
*/
21-
async function getCommitsFiltered(cwd, dir, lastHead = undefined, firstParentBranch) {
22+
async function getCommitsFiltered(cwd, dir, lastRelease, nextRelease, firstParentBranch) {
2223
// Clean paths and make sure directories exist.
2324
check(cwd, "cwd: directory");
2425
check(dir, "dir: path");
2526
cwd = cleanPath(cwd);
2627
dir = cleanPath(dir, cwd);
2728
check(dir, "dir: directory");
28-
check(lastHead, "lastHead: alphanumeric{40}?");
29+
check(lastRelease, "lastRelease: alphanumeric{40}?");
30+
check(nextRelease, "nextRelease: alphanumeric{40}?");
2931

3032
// target must be inside and different than cwd.
3133
if (dir.indexOf(cwd) !== 0) throw new ValueError("dir: Must be inside cwd", dir);
@@ -45,7 +47,8 @@ async function getCommitsFiltered(cwd, dir, lastHead = undefined, firstParentBra
4547
// Use git-log-parser to get the commits.
4648
const relpath = relative(root, dir);
4749
const firstParentBranchFilter = firstParentBranch ? ["--first-parent", firstParentBranch] : [];
48-
const gitLogFilterQuery = [...firstParentBranchFilter, lastHead ? `${lastHead}..HEAD` : "HEAD", "--", relpath];
50+
const range = (lastRelease ? `${lastRelease}..` : "") + (nextRelease || "HEAD");
51+
const gitLogFilterQuery = [...firstParentBranchFilter, range, "--", relpath];
4952
const stream = gitLogParser.parse({ _: gitLogFilterQuery }, { cwd, env: process.env });
5053
const commits = await getStream.array(stream);
5154

lib/git.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ function getTags(branch, execaOptions, filters) {
2525
return tags.filter((tag) => validateSubstr(tag, filters));
2626
}
2727

28+
/**
29+
* Get the commit sha for a given tag.
30+
*
31+
* @param {String} tagName Tag name for which to retrieve the commit sha.
32+
* @param {Object} [execaOptions] Options to pass to `execa`.
33+
*
34+
* @return {Promise<String>} The commit sha of the tag in parameter or `null`.
35+
*/
36+
async function getTagHead(tagName, execaOptions) {
37+
return (await execa("git", ["rev-list", "-1", tagName], execaOptions)).stdout;
38+
}
39+
2840
module.exports = {
2941
getTags,
42+
getTagHead,
3043
};

test/lib/getCommitsFiltered.test.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const { gitInit, gitCommitAll, gitGetCommits } = require("../helpers/git");
66

77
// Tests.
88
describe("getCommitsFiltered()", () => {
9-
test("Works correctly (no lastHead)", async () => {
9+
test("Works correctly (no lastRelease)", async () => {
1010
// Create Git repo with copy of Yarn workspaces fixture.
1111
const cwd = await gitInit();
1212
writeFileSync(`${cwd}/AAA.txt`, "AAA");
@@ -24,7 +24,7 @@ describe("getCommitsFiltered()", () => {
2424
expect(commits[0].hash).toBe(sha2);
2525
expect(commits[0].subject).toBe("Commit 2");
2626
});
27-
test("Works correctly (with lastHead)", async () => {
27+
test("Works correctly (with lastRelease)", async () => {
2828
// Create Git repo with copy of Yarn workspaces fixture.
2929
const cwd = await gitInit();
3030
writeFileSync(`${cwd}/AAA.txt`, "AAA");
@@ -40,6 +40,26 @@ describe("getCommitsFiltered()", () => {
4040
const commits = await getCommitsFiltered(cwd, "bbb/", sha3);
4141
expect(commits.length).toBe(0);
4242
});
43+
44+
test("Works correctly (with lastRelease and nextRelease)", async () => {
45+
// Create Git repo with copy of Yarn workspaces fixture.
46+
const cwd = await gitInit();
47+
writeFileSync(`${cwd}/AAA.txt`, "AAA");
48+
const sha1 = await gitCommitAll(cwd, "Commit 1");
49+
mkdirSync(`${cwd}/bbb`);
50+
writeFileSync(`${cwd}/bbb/BBB.txt`, "BBB");
51+
const sha2 = await gitCommitAll(cwd, "Commit 2");
52+
writeFileSync(`${cwd}/bbb/BBB2.txt`, "BBB2");
53+
const sha3 = await gitCommitAll(cwd, "Commit 3");
54+
mkdirSync(`${cwd}/ccc`);
55+
writeFileSync(`${cwd}/ccc/CCC.txt`, "CCC");
56+
const sha4 = await gitCommitAll(cwd, "Commit 4");
57+
58+
// Filter a single directory from sha2 (lastRelease) to sha3 (nextRelease)
59+
const commits = await getCommitsFiltered(cwd, "bbb/", sha2, sha3);
60+
expect(commits.length).toBe(1);
61+
expect(commits[0].hash).toBe(sha3);
62+
});
4363
test("Works correctly (initial commit)", async () => {
4464
// Create Git repo with copy of Yarn workspaces fixture.
4565
const cwd = await gitInit();
@@ -108,20 +128,37 @@ describe("getCommitsFiltered()", () => {
108128
message: expect.stringMatching("dir: Must be inside cwd"),
109129
});
110130
});
111-
test("TypeError if lastHead is not 40char alphanumeric Git SHA hash", async () => {
131+
test("TypeError if lastRelease is not 40char alphanumeric Git SHA hash", async () => {
112132
const cwd = tempy.directory();
113133
mkdirSync(join(cwd, "dir"));
114134
await expect(getCommitsFiltered(cwd, "dir", false)).rejects.toBeInstanceOf(TypeError);
115135
await expect(getCommitsFiltered(cwd, "dir", false)).rejects.toMatchObject({
116-
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
136+
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
117137
});
118138
await expect(getCommitsFiltered(cwd, "dir", 123)).rejects.toBeInstanceOf(TypeError);
119139
await expect(getCommitsFiltered(cwd, "dir", 123)).rejects.toMatchObject({
120-
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
140+
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
121141
});
122142
await expect(getCommitsFiltered(cwd, "dir", "nottherightlength")).rejects.toBeInstanceOf(TypeError);
123143
await expect(getCommitsFiltered(cwd, "dir", "nottherightlength")).rejects.toMatchObject({
124-
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
144+
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
145+
});
146+
});
147+
148+
test("TypeError if nextRelease is not 40char alphanumeric Git SHA hash", async () => {
149+
const cwd = tempy.directory();
150+
mkdirSync(join(cwd, "dir"));
151+
await expect(getCommitsFiltered(cwd, "dir", undefined, false)).rejects.toBeInstanceOf(TypeError);
152+
await expect(getCommitsFiltered(cwd, "dir", undefined, false)).rejects.toMatchObject({
153+
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
154+
});
155+
await expect(getCommitsFiltered(cwd, "dir", undefined, 123)).rejects.toBeInstanceOf(TypeError);
156+
await expect(getCommitsFiltered(cwd, "dir", undefined, 123)).rejects.toMatchObject({
157+
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
158+
});
159+
await expect(getCommitsFiltered(cwd, "dir", undefined, "nottherightlength")).rejects.toBeInstanceOf(TypeError);
160+
await expect(getCommitsFiltered(cwd, "dir", undefined, "nottherightlength")).rejects.toMatchObject({
161+
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
125162
});
126163
});
127164
});

0 commit comments

Comments
 (0)