Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove revert commits from changelog #65

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
6 changes: 4 additions & 2 deletions src/commands/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
parseCommits,
bumpVersion,
generateMarkDown,
filterCommits,
BumpVersionOptions,
} from "..";
import { npmPublish, renamePackage } from "../package";
Expand Down Expand Up @@ -37,6 +38,7 @@ export default async function defaultMain(args: Argv) {
config.types[c.type] &&
!(c.type === "chore" && c.scope === "deps" && !c.isBreaking)
);
const filteredCommits = filterCommits(commits, config);

// Shortcut for canary releases
if (args.canary) {
Expand All @@ -59,7 +61,7 @@ export default async function defaultMain(args: Argv) {
// Bump version optionally
if (args.bump || args.release) {
const bumpOptions = _getBumpVersionOptions(args);
const newVersion = await bumpVersion(commits, config, bumpOptions);
const newVersion = await bumpVersion(filteredCommits, config, bumpOptions);
if (!newVersion) {
consola.error("Unable to bump version based on changes.");
process.exit(1);
Expand All @@ -68,7 +70,7 @@ export default async function defaultMain(args: Argv) {
}

// Generate markdown
const markdown = await generateMarkDown(commits, config);
const markdown = await generateMarkDown(filteredCommits, config);

// Show changelog in CLI unless bumping or releasing
const displayOnly = !args.bump && !args.release;
Expand Down
67 changes: 67 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface GitCommit extends RawGitCommit {
references: Reference[];
authors: GitCommitAuthor[];
isBreaking: boolean;
revertedHashes: string[];
}

export interface RevertPair {
shortRevertingHash: string;
revertedHash: string;
}

export async function getLastGitTag() {
Expand Down Expand Up @@ -100,6 +106,7 @@ const ConventionalCommitRegex =
const CoAuthoredByRegex = /co-authored-by:\s*(?<name>.+)(<(?<email>.+)>)/gim;
const PullRequestRE = /\([ a-z]*(#\d+)\s*\)/gm;
const IssueRE = /(#\d+)/gm;
const RevertHashRE = /This reverts commit (?<hash>[\da-f]{40})./gm;

export function parseGitCommit(
commit: RawGitCommit,
Expand Down Expand Up @@ -133,6 +140,13 @@ export function parseGitCommit(
// Remove references and normalize
description = description.replace(PullRequestRE, "").trim();

// Extract the reverted hashes.
const revertedHashes = [];
const matchedHashes = commit.body.matchAll(RevertHashRE);
for (const matchedHash of matchedHashes) {
revertedHashes.push(matchedHash.groups.hash);
}

// Find all authors
const authors: GitCommitAuthor[] = [commit.author];
for (const match of commit.body.matchAll(CoAuthoredByRegex)) {
Expand All @@ -150,5 +164,58 @@ export function parseGitCommit(
scope,
references,
isBreaking,
revertedHashes,
};
}

export function filterCommits(
commits: GitCommit[],
config: ChangelogConfig
): GitCommit[] {
const commitsWithNoDeps = commits.filter(
(c) =>
config.types[c.type] &&
!(c.type === "chore" && c.scope === "deps" && !c.isBreaking)
);

let resolvedCommits: GitCommit[] = [];
let revertWatchList: RevertPair[] = [];
for (const commit of commitsWithNoDeps) {
// Include the reverted hashes in the watch list
if (commit.revertedHashes.length > 0) {
revertWatchList.push(
...commit.revertedHashes.map(
(revertedHash) =>
({
revertedHash,
shortRevertingHash: commit.shortHash,
} as RevertPair)
)
);
}

// Find the commits which revert the current commit being evaluated
const shortRevertingHashes = revertWatchList
.filter((pair) => pair.revertedHash.startsWith(commit.shortHash))
.map((pair) => pair.shortRevertingHash);

if (shortRevertingHashes.length > 0) {
// Remove commits that reverts this current commit
resolvedCommits = resolvedCommits.filter(
(resolvedCommit) =>
!shortRevertingHashes.includes(resolvedCommit.shortHash)
);

// Unwatch reverting hashes that has been resolved
revertWatchList = revertWatchList.filter(
(watchedRevert) =>
!shortRevertingHashes.includes(watchedRevert.shortRevertingHash)
);
} else {
// If the current commit is known not to have been reverted, put it to resolved commits.
resolvedCommits = [...resolvedCommits, commit];
}
}

return resolvedCommits;
}
101 changes: 101 additions & 0 deletions test/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { describe, expect, test } from "vitest";
import { GitCommit } from "../src/git";
import type { ChangelogConfig } from "../src/config";
import {
generateMarkDown,
getGitDiff,
loadChangelogConfig,
parseCommits,
filterCommits,
getRepoConfig,
formatReference,
} from "../src";
Expand Down Expand Up @@ -133,6 +136,7 @@ describe("git", () => {
"value": "3828bda",
},
],
"revertedHashes": [],
"scope": "release",
"shortHash": "3828bda",
"type": "chore",
Expand All @@ -155,6 +159,7 @@ describe("git", () => {
"value": "20e622e",
},
],
"revertedHashes": [],
"scope": "scope",
"shortHash": "20e622e",
"type": "fix",
Expand All @@ -169,6 +174,7 @@ describe("git", () => {
"value": "6fc5087",
},
],
"revertedHashes": [],
"scope": "release",
"shortHash": "6fc5087",
"type": "chore",
Expand All @@ -187,6 +193,7 @@ describe("git", () => {
"value": "c0febf1",
},
],
"revertedHashes": [],
"scope": "",
"shortHash": "c0febf1",
"type": "feat",
Expand All @@ -201,6 +208,7 @@ describe("git", () => {
"value": "f4f42a3",
},
],
"revertedHashes": [],
"scope": "release",
"shortHash": "f4f42a3",
"type": "chore",
Expand All @@ -215,6 +223,7 @@ describe("git", () => {
"value": "648ccf1",
},
],
"revertedHashes": [],
"scope": "",
"shortHash": "648ccf1",
"type": "fix",
Expand All @@ -229,6 +238,7 @@ describe("git", () => {
"value": "5451f18",
},
],
"revertedHashes": [],
"scope": "",
"shortHash": "5451f18",
"type": "feat",
Expand All @@ -243,6 +253,7 @@ describe("git", () => {
"value": "8796cf1",
},
],
"revertedHashes": [],
"scope": "",
"shortHash": "8796cf1",
"type": "chore",
Expand All @@ -257,6 +268,7 @@ describe("git", () => {
"value": "c210976",
},
],
"revertedHashes": [],
"scope": "",
"shortHash": "c210976",
"type": "chore",
Expand All @@ -275,6 +287,7 @@ describe("git", () => {
"value": "a80e372",
},
],
"revertedHashes": [],
"scope": "deps",
"shortHash": "a80e372",
"type": "chore",
Expand Down Expand Up @@ -318,6 +331,94 @@ describe("git", () => {
`);
});

test("filterCommits should retain reverts from previous version", () => {
const inputLog = [
{
type: "example",
scope: "",
shortHash: "a12345",
revertedHashes: ["b12345"],
} as unknown as GitCommit,
{
type: "example",
scope: "",
shortHash: "c12345",
revertedHashes: ["d12345"],
} as unknown as GitCommit,
];
const config: ChangelogConfig = {
types: {
example: { title: "Example" },
},
scopeMap: undefined,
github: "",
from: "",
to: "",
cwd: "",
output: "",
};

const resolvedLog = filterCommits(inputLog, config);
expect(resolvedLog).toStrictEqual([
{
type: "example",
scope: "",
shortHash: "a12345",
revertedHashes: ["b12345"],
} as unknown as GitCommit,
{
type: "example",
scope: "",
shortHash: "c12345",
revertedHashes: ["d12345"],
} as unknown as GitCommit,
]);
});

test("filterCommits should remove reverts from upcoming version", () => {
const inputLog = [
{
type: "example",
scope: "",
shortHash: "a12345",
revertedHashes: ["b12345"],
} as unknown as GitCommit,
{
type: "example",
scope: "",
shortHash: "b12345",
revertedHashes: [],
} as unknown as GitCommit,
{
type: "example",
scope: "",
shortHash: "c12345",
revertedHashes: [],
} as unknown as GitCommit,
];
const config: ChangelogConfig = {
types: {
example: { title: "Example" },
},
scopeMap: undefined,
github: "",
from: "",
to: "",
cwd: "",
output: "",
};

const resolvedLog = filterCommits(inputLog, config);
expect(resolvedLog).toStrictEqual([
{
type: "example",
scope: "",
shortHash: "c12345",
revertedHashes: [],
} as unknown as GitCommit,
]);
});

test("parse host config", () => {
expect(getRepoConfig(undefined)).toMatchObject({});
expect(getRepoConfig("")).toMatchObject({});
Expand Down