feat: add ignore_commits_pattern and allow disabling major/minor patterns

This commit is contained in:
Paul Hatcherian 2026-01-18 19:02:24 -06:00
parent c54fa1275a
commit cd97954ab7
7 changed files with 172 additions and 3 deletions

View file

@ -36,6 +36,10 @@ inputs:
description: "A string which indicates the flags used by the `minor_pattern` regular expression. Supported flags: idgs"
required: false
default: ""
ignore_commits_pattern:
description: "A pattern to match commits that should be ignored when calculating the version. Commits matching this pattern will not trigger any version bump. Wrap with '/' to use a regular expression."
required: false
default: ""
version_format:
description: "Pattern to use when formatting output version"
required: true

View file

@ -32,6 +32,8 @@ export class ActionConfig {
public enablePrereleaseMode: boolean = false;
/** If bump_each_commit is also set to true, setting this value will cause the version to increment only if the pattern specified is matched. */
public bumpEachCommitPatchPattern: string = "";
/** A pattern to match commits that should be ignored when calculating the version. Commits matching this pattern will not trigger any version bump. Wrap with '/' to use a regular expression. */
public ignoreCommitsPattern: string = "";
/** If enabled, diagnostic information will be added to the action output. */
public debug: boolean = false;
/** Diagnostics to replay */

View file

@ -36,6 +36,11 @@ program
"/feat:/",
)
.option("--minor-flags <flags>", "Flags for minor pattern regex", "")
.option(
"-i, --ignore-commits-pattern <pattern>",
"Pattern to match commits that should be ignored when calculating version",
"",
)
.option(
"--version-format <format>",
"Version format template",
@ -81,6 +86,7 @@ program
config.majorFlags = options.majorFlags || "";
config.minorPattern = options.minorPattern;
config.minorFlags = options.minorFlags || "";
config.ignoreCommitsPattern = options.ignoreCommitsPattern || "";
config.bumpEachCommit = options.bumpEachCommit;
config.bumpEachCommitPatchPattern =
options.bumpEachCommitPatchPattern || "";

View file

@ -1431,5 +1431,140 @@ testInterfaces.forEach((testInterface) => {
},
timeout,
);
test(
"Empty major pattern disables major version bumps",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
majorPattern: "",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("fix!: breaking fix");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.0.1+0");
},
timeout,
);
test(
"Empty major pattern still allows normal behavior to verify test",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("fix!: breaking fix");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("2.0.0+0");
},
timeout,
);
test(
"Empty minor pattern disables minor version bumps",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
minorPattern: "",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("feat: Feature Commit");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.0.1+0");
},
timeout,
);
test(
"Empty major and minor patterns result in only patch bumps",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
majorPattern: "",
minorPattern: "",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("feat!: Breaking Change");
repo.makeCommit("feat: New Feature");
repo.makeCommit("Regular Commit");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.0.1+2");
},
timeout,
);
test(
"Commits matching ignore pattern are excluded from version calculation",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
ignoreCommitsPattern: "/^chore:|^docs:|^style:/",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("chore: update dependencies");
repo.makeCommit("docs: update readme");
repo.makeCommit("style: fix formatting");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.0.0+0");
expect(result.changed).toBe(false);
},
timeout,
);
test(
"Non-ignored commits still trigger version bumps",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
ignoreCommitsPattern: "/^chore:|^docs:/",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("chore: update dependencies");
repo.makeCommit("fix: bug fix");
repo.makeCommit("docs: update readme");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.0.1+0");
},
timeout,
);
test(
"Ignored commits do not affect major/minor detection",
async () => {
const repo = createTestRepo(testRunner, {
tagPrefix: "",
ignoreCommitsPattern: "/^chore:/",
});
repo.makeCommit("Initial Commit");
repo.exec("git tag 1.0.0");
repo.makeCommit("chore: update dependencies");
repo.makeCommit("feat: new feature");
repo.makeCommit("chore: cleanup");
const result = await repo.runAction();
expect(result.formattedVersion).toBe("1.1.0+0");
},
timeout,
);
});
});

View file

@ -84,6 +84,7 @@ export async function run() {
userFormatType: core.getInput("user_format_type"),
enablePrereleaseMode: toBool(core.getInput("enable_prerelease_mode")),
bumpEachCommitPatchPattern: core.getInput("bump_each_commit_patch_pattern"),
ignoreCommitsPattern: core.getInput("ignore_commits_pattern"),
debug: toBool(core.getInput("debug")),
replay: "",
};

View file

@ -38,15 +38,17 @@ export class BumpAlwaysVersionClassifier extends DefaultVersionClassifier {
);
}
const filteredCommitSet = this.filterIgnoredCommits(commitSet);
let { major, minor, patch } = lastRelease;
let type = VersionType.None;
let increment = 0;
if (commitSet.commits.length === 0) {
if (filteredCommitSet.commits.length === 0) {
return new VersionClassification(type, 0, false, major, minor, patch);
}
for (let commit of commitSet.commits.reverse()) {
for (let commit of filteredCommitSet.commits.reverse()) {
if (this.majorPattern(commit)) {
type = VersionType.Major;
} else if (this.minorPattern(commit)) {

View file

@ -9,6 +9,7 @@ import { VersionType } from "./VersionType";
export class DefaultVersionClassifier implements VersionClassifier {
protected majorPattern: (commit: CommitInfo) => boolean;
protected minorPattern: (commit: CommitInfo) => boolean;
protected ignorePattern: ((commit: CommitInfo) => boolean) | null;
protected enablePrereleaseMode: boolean;
constructor(config: ActionConfig) {
@ -23,14 +24,31 @@ export class DefaultVersionClassifier implements VersionClassifier {
config.minorFlags,
searchBody,
);
this.ignorePattern = config.ignoreCommitsPattern
? this.parsePattern(config.ignoreCommitsPattern, "", searchBody)
: null;
this.enablePrereleaseMode = config.enablePrereleaseMode;
}
protected filterIgnoredCommits(commitSet: CommitInfoSet): CommitInfoSet {
if (!this.ignorePattern) {
return commitSet;
}
const filteredCommits = commitSet.commits.filter(
(commit) => !this.ignorePattern!(commit),
);
const changed = filteredCommits.length > 0 ? commitSet.changed : false;
return new CommitInfoSet(changed, filteredCommits);
}
protected parsePattern(
pattern: string,
flags: string,
searchBody: boolean,
): (pattern: CommitInfo) => boolean {
if (pattern === "") {
return (_commit: CommitInfo) => false;
}
if (/^\/.+\/[i]*$/.test(pattern)) {
const regexEnd = pattern.lastIndexOf("/");
const parsedFlags = pattern.slice(pattern.lastIndexOf("/") + 1);
@ -149,7 +167,8 @@ export class DefaultVersionClassifier implements VersionClassifier {
lastRelease: ReleaseInformation,
commitSet: CommitInfoSet,
): Promise<VersionClassification> {
const { type, increment, changed } = this.resolveCommitType(commitSet);
const filteredCommitSet = this.filterIgnoredCommits(commitSet);
const { type, increment, changed } = this.resolveCommitType(filteredCommitSet);
const { major, minor, patch } = this.getNextVersion(lastRelease, type);