Skip to content

Commit

Permalink
fix: support merge workflows in generateNotes
Browse files Browse the repository at this point in the history
  • Loading branch information
reuzel authored and antongolub committed Jun 1, 2021
1 parent 01b28e3 commit d75274b
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 19 deletions.
39 changes: 30 additions & 9 deletions lib/createInlinePluginCreator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const debug = require("debug")("msr:inlinePlugin");
const getCommitsFiltered = require("./getCommitsFiltered");
const { getTagHead } = require("./git");
const { updateManifestDeps, resolveReleaseType } = require("./updateDeps");

/**
Expand Down Expand Up @@ -40,11 +41,6 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
);
};

/**
* @var {Commit[]} List of _filtered_ commits that only apply to this package.
*/
let commits;

/**
* @param {object} pluginOptions Options to configure this plugin.
* @param {object} context The semantic-release context.
Expand Down Expand Up @@ -85,12 +81,18 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
* @internal
*/
const analyzeCommits = async (pluginOptions, context) => {
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
pkg._preRelease = context.branch.prerelease || null;
pkg._branch = context.branch.name;

// Filter commits by directory.
commits = await getCommitsFiltered(cwd, dir, context.lastRelease.gitHead, firstParentBranch);
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
const commits = await getCommitsFiltered(
cwd,
dir,
context.lastRelease ? context.lastRelease.gitHead : undefined,
context.nextRelease ? context.nextRelease.gitHead : undefined,
firstParentBranch
);

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

// Set context.commits so analyzeCommits does correct analysis.
// We need to redo this because context is a different instance each time.
//get SHA of lastRelease if not already there (should have been done by Semantic Release...)
if (context.lastRelease && context.lastRelease.gitTag) {
if (!context.lastRelease.gitHead || context.lastRelease.gitHead === context.lastRelease.gitTag) {
context.lastRelease.gitHead = await getTagHead(context.lastRelease.gitTag, {
cwd: context.cwd,
env: context.env,
});
}
}

// Filter commits by directory (and release range)
const firstParentBranch = flags.firstParent ? context.branch.name : undefined;
const commits = await getCommitsFiltered(
cwd,
dir,
context.lastRelease ? context.lastRelease.gitHead : undefined,
context.nextRelease ? context.nextRelease.gitHead : undefined,
firstParentBranch
);

// Set context.commits so generateNotes does correct analysis.
context.commits = commits;

// Get subnotes and add to list.
Expand Down
11 changes: 7 additions & 4 deletions lib/getCommitsFiltered.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ const debug = require("debug")("msr:commitsFilter");
*
* @param {string} cwd Absolute path of the working directory the Git repo is in.
* @param {string} dir Path to the target directory to filter by. Either absolute, or relative to cwd param.
* @param {string|void} lastHead The SHA of the previous release
* @param {string|void} lastRelease The SHA of the previous release (default to start of all commits if undefined)
* @param {string|void} nextRelease The SHA of the next release (default to HEAD if undefined)
* @param {string|void} firstParentBranch first-parent to determine which merges went into master
* @return {Promise<Array<Commit>>} The list of commits on the branch `branch` since the last release.
*/
async function getCommitsFiltered(cwd, dir, lastHead = undefined, firstParentBranch) {
async function getCommitsFiltered(cwd, dir, lastRelease, nextRelease, firstParentBranch) {
// Clean paths and make sure directories exist.
check(cwd, "cwd: directory");
check(dir, "dir: path");
cwd = cleanPath(cwd);
dir = cleanPath(dir, cwd);
check(dir, "dir: directory");
check(lastHead, "lastHead: alphanumeric{40}?");
check(lastRelease, "lastRelease: alphanumeric{40}?");
check(nextRelease, "nextRelease: alphanumeric{40}?");

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

Expand Down
13 changes: 13 additions & 0 deletions lib/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ function getTags(branch, execaOptions, filters) {
return tags.filter((tag) => validateSubstr(tag, filters));
}

/**
* Get the commit sha for a given tag.
*
* @param {String} tagName Tag name for which to retrieve the commit sha.
* @param {Object} [execaOptions] Options to pass to `execa`.
*
* @return {Promise<String>} The commit sha of the tag in parameter or `null`.
*/
async function getTagHead(tagName, execaOptions) {
return (await execa("git", ["rev-list", "-1", tagName], execaOptions)).stdout;
}

module.exports = {
getTags,
getTagHead,
};
49 changes: 43 additions & 6 deletions test/lib/getCommitsFiltered.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { gitInit, gitCommitAll, gitGetCommits } = require("../helpers/git");

// Tests.
describe("getCommitsFiltered()", () => {
test("Works correctly (no lastHead)", async () => {
test("Works correctly (no lastRelease)", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = await gitInit();
writeFileSync(`${cwd}/AAA.txt`, "AAA");
Expand All @@ -24,7 +24,7 @@ describe("getCommitsFiltered()", () => {
expect(commits[0].hash).toBe(sha2);
expect(commits[0].subject).toBe("Commit 2");
});
test("Works correctly (with lastHead)", async () => {
test("Works correctly (with lastRelease)", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = await gitInit();
writeFileSync(`${cwd}/AAA.txt`, "AAA");
Expand All @@ -40,6 +40,26 @@ describe("getCommitsFiltered()", () => {
const commits = await getCommitsFiltered(cwd, "bbb/", sha3);
expect(commits.length).toBe(0);
});

test("Works correctly (with lastRelease and nextRelease)", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = await gitInit();
writeFileSync(`${cwd}/AAA.txt`, "AAA");
const sha1 = await gitCommitAll(cwd, "Commit 1");
mkdirSync(`${cwd}/bbb`);
writeFileSync(`${cwd}/bbb/BBB.txt`, "BBB");
const sha2 = await gitCommitAll(cwd, "Commit 2");
writeFileSync(`${cwd}/bbb/BBB2.txt`, "BBB2");
const sha3 = await gitCommitAll(cwd, "Commit 3");
mkdirSync(`${cwd}/ccc`);
writeFileSync(`${cwd}/ccc/CCC.txt`, "CCC");
const sha4 = await gitCommitAll(cwd, "Commit 4");

// Filter a single directory from sha2 (lastRelease) to sha3 (nextRelease)
const commits = await getCommitsFiltered(cwd, "bbb/", sha2, sha3);
expect(commits.length).toBe(1);
expect(commits[0].hash).toBe(sha3);
});
test("Works correctly (initial commit)", async () => {
// Create Git repo with copy of Yarn workspaces fixture.
const cwd = await gitInit();
Expand Down Expand Up @@ -108,20 +128,37 @@ describe("getCommitsFiltered()", () => {
message: expect.stringMatching("dir: Must be inside cwd"),
});
});
test("TypeError if lastHead is not 40char alphanumeric Git SHA hash", async () => {
test("TypeError if lastRelease is not 40char alphanumeric Git SHA hash", async () => {
const cwd = tempy.directory();
mkdirSync(join(cwd, "dir"));
await expect(getCommitsFiltered(cwd, "dir", false)).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", false)).rejects.toMatchObject({
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
});
await expect(getCommitsFiltered(cwd, "dir", 123)).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", 123)).rejects.toMatchObject({
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
});
await expect(getCommitsFiltered(cwd, "dir", "nottherightlength")).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", "nottherightlength")).rejects.toMatchObject({
message: expect.stringMatching("lastHead: Must be alphanumeric string with size 40 or empty"),
message: expect.stringMatching("lastRelease: Must be alphanumeric string with size 40 or empty"),
});
});

test("TypeError if nextRelease is not 40char alphanumeric Git SHA hash", async () => {
const cwd = tempy.directory();
mkdirSync(join(cwd, "dir"));
await expect(getCommitsFiltered(cwd, "dir", undefined, false)).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", undefined, false)).rejects.toMatchObject({
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
});
await expect(getCommitsFiltered(cwd, "dir", undefined, 123)).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", undefined, 123)).rejects.toMatchObject({
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
});
await expect(getCommitsFiltered(cwd, "dir", undefined, "nottherightlength")).rejects.toBeInstanceOf(TypeError);
await expect(getCommitsFiltered(cwd, "dir", undefined, "nottherightlength")).rejects.toMatchObject({
message: expect.stringMatching("nextRelease: Must be alphanumeric string with size 40 or empty"),
});
});
});

0 comments on commit d75274b

Please sign in to comment.