Skip to content

Commit 252b102

Browse files
authored
Add support for ignored patterns (#124)
There was already logic to ignore merge commits, but it was using a very restricted regular expression. This PR: - Improve the existing merge commit detection. - Allow users to disable merge commit detection. - Allow users to provide custom patterns to ignore Related issues: - Closes #110 Signed-off-by: Luiz Ferraz <luiz@lferraz.com>
1 parent e4d81e5 commit 252b102

File tree

7 files changed

+167
-14
lines changed

7 files changed

+167
-14
lines changed

action.yml

+10
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ inputs:
4040
description: 'If set to "true", validate commits in pull requests instead of the pull request body'
4141
required: false
4242
default: ''
43+
ignore-merge-commits:
44+
description: 'If set to "false", merge commits will not be ignored.'
45+
required: false
46+
default: 'true'
47+
ignore-patterns:
48+
description: >-
49+
Pattern to ignore in commit messages. One pattern per line.
50+
Patterns are matched against the whole commit message as regular expressions without any flags.
51+
required: false
52+
default: ''
4353
github-token:
4454
description: 'GitHub token'
4555
required: false

dist/index.js

+47-5
Original file line numberDiff line numberDiff line change
@@ -32686,6 +32686,8 @@ class Inputs {
3268632686
this.enforceSignOff = values.enforceSignOff;
3268732687
this.validatePullRequestCommits = values.validatePullRequestCommits;
3268832688
this.skipBodyCheck = values.skipBodyCheck;
32689+
this.ignoreMergeCommits = values.ignoreMergeCommits;
32690+
this.ignorePatterns = values.ignorePatterns;
3268932691
}
3269032692
}
3269132693
exports.Inputs = Inputs;
@@ -32710,7 +32712,7 @@ class MaybeInputs {
3271032712
}
3271132713
exports.MaybeInputs = MaybeInputs;
3271232714
function parseInputs(rawInputs) {
32713-
const { additionalVerbsInput = '', pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', skipBodyCheckInput = '', } = rawInputs;
32715+
const { additionalVerbsInput = '', pathToAdditionalVerbsInput = '', allowOneLinersInput = '', maxSubjectLengthInput = '', maxBodyLineLengthInput = '', enforceSignOffInput = '', validatePullRequestCommitsInput = '', skipBodyCheckInput = '', ignoreMergeCommitsInput = '', ignorePatternsInput = '', } = rawInputs;
3271432716
const additionalVerbs = new Set();
3271532717
const hasAdditionalVerbsInput = additionalVerbsInput.length > 0;
3271632718
if (additionalVerbsInput) {
@@ -32770,6 +32772,20 @@ function parseInputs(rawInputs) {
3277032772
return new MaybeInputs(null, 'Unexpected value for skip-body-check. ' +
3277132773
`Expected either 'true' or 'false', got: ${skipBodyCheckInput}`);
3277232774
}
32775+
const ignoreMergeCommits = !ignoreMergeCommitsInput
32776+
? true
32777+
: parseBooleanFromString(ignoreMergeCommitsInput);
32778+
if (ignoreMergeCommits === null) {
32779+
return new MaybeInputs(null, 'Unexpected value for ignore-merge-commits. ' +
32780+
`Expected either 'true' or 'false', got: ${ignoreMergeCommitsInput}`);
32781+
}
32782+
const ignorePatterns = ignorePatternsInput == null
32783+
? []
32784+
: ignorePatternsInput
32785+
.split('\n')
32786+
.map(s => s.trim())
32787+
.filter(s => s.length > 0)
32788+
.map(s => new RegExp(s));
3277332789
return new MaybeInputs(new Inputs({
3277432790
hasAdditionalVerbsInput,
3277532791
pathToAdditionalVerbs: pathToAdditionalVerbsInput,
@@ -32780,6 +32796,8 @@ function parseInputs(rawInputs) {
3278032796
enforceSignOff,
3278132797
validatePullRequestCommits,
3278232798
skipBodyCheck,
32799+
ignoreMergeCommits,
32800+
ignorePatterns,
3278332801
}), null);
3278432802
}
3278532803
exports.parseInputs = parseInputs;
@@ -33042,12 +33060,27 @@ function checkSignedOff(bodyLines) {
3304233060
}
3304333061
return errors;
3304433062
}
33045-
const mergeMessageRe = new RegExp("^Merge branch '[^\\000-\\037\\177 ~^:?*[]+' " +
33046-
'into [^\\000-\\037\\177 ~^:?*[]+$');
33063+
const mergeMessagePatterns = [
33064+
// Local merges to default branch
33065+
/^Merge branch (?:'\S+'|"\S+"|\S+)/,
33066+
// Local merges to alternate branch
33067+
/^Merge (:?remote-tracking )?branch (?:'\S+'|"\S+"|\S+) into \S+/,
33068+
// GitHub Web UI merge pull request
33069+
/^Merge pull request #\d+ from \S+/,
33070+
];
3304733071
function check(message, inputs) {
3304833072
const errors = [];
33049-
if (mergeMessageRe.test(message)) {
33050-
return errors;
33073+
if (inputs.ignoreMergeCommits) {
33074+
for (const pattern of mergeMessagePatterns) {
33075+
if (pattern.test(message)) {
33076+
return errors;
33077+
}
33078+
}
33079+
}
33080+
for (const ignorePattern of inputs.ignorePatterns) {
33081+
if (ignorePattern.test(message)) {
33082+
return errors;
33083+
}
3305133084
}
3305233085
const lines = splitLines(message);
3305333086
if (lines.length === 0) {
@@ -33197,6 +33230,12 @@ function runWithExceptions() {
3319733230
const skipBodyCheckInput = core.getInput('skip-body-check', {
3319833231
required: false,
3319933232
});
33233+
const ignoreMergeCommitsInput = core.getInput('ignore-merge-commits', {
33234+
required: false,
33235+
});
33236+
const ignorePatternsInput = core.getInput('ignore-patterns', {
33237+
required: false,
33238+
});
3320033239
const maybeInputs = input.parseInputs({
3320133240
additionalVerbsInput,
3320233241
pathToAdditionalVerbsInput,
@@ -33206,6 +33245,8 @@ function runWithExceptions() {
3320633245
enforceSignOffInput,
3320733246
validatePullRequestCommitsInput,
3320833247
skipBodyCheckInput,
33248+
ignoreMergeCommitsInput,
33249+
ignorePatternsInput,
3320933250
});
3321033251
if (maybeInputs.error !== null) {
3321133252
core.error(maybeInputs.error);
@@ -33980,6 +34021,7 @@ exports.SET = new Set([
3398034021
'override',
3398134022
'pack',
3398234023
'package',
34024+
'parse',
3398334025
'patch',
3398434026
'pin',
3398534027
'privatize',

src/__tests__/input.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ it('parses the inputs.', () => {
3939
enforceSignOffInput: 'true',
4040
validatePullRequestCommitsInput: 'true',
4141
skipBodyCheckInput: 'true',
42+
ignoreMergeCommitsInput: 'false',
43+
ignorePatternsInput: `
44+
^Some pattern$
45+
Another pattern
46+
`
4247
});
4348

4449
expect(maybeInputs.error).toBeNull();
@@ -55,4 +60,9 @@ it('parses the inputs.', () => {
5560
expect(inputs.enforceSignOff).toBeTruthy();
5661
expect(inputs.validatePullRequestCommits).toBeTruthy();
5762
expect(inputs.skipBodyCheck).toBeTruthy();
63+
expect(inputs.ignoreMergeCommits).toBeFalsy();
64+
expect(inputs.ignorePatterns).toEqual([
65+
/^Some pattern$/,
66+
/Another pattern/
67+
]);
5868
});

src/__tests__/inspection.test.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {Inputs} from '../input';
12
import * as input from '../input';
23
import * as inspection from '../inspection';
34

@@ -221,6 +222,8 @@ it(
221222
enforceSignOff: false,
222223
validatePullRequestCommits: false,
223224
skipBodyCheck: false,
225+
ignoreMergeCommits: false,
226+
ignorePatterns: [],
224227
});
225228

226229
const message =
@@ -427,10 +430,41 @@ it('reports duplicate starting word in subject and body.', () => {
427430
]);
428431
});
429432

430-
it('ignores merge messages.', () => {
431-
const message = "Merge branch 'V20DataModel' into miho/Conform-to-spec";
433+
it.each([
434+
// Local merge to default branch
435+
"Merge branch 'fantastic-feature'",
436+
// Local merge to alternate branch
437+
"Merge branch 'V20DataModel' into miho/Conform-to-spec",
438+
// Local merge from remote
439+
"Merge remote-tracking branch 'origin/remote-branch' into local-branch",
440+
// Web UI merge pull request
441+
"Merge pull request #11 from acme-corp/the-project"
442+
])('ignores merge messages.', (message) => {
443+
const inputs = new Inputs({
444+
...defaultInputs,
445+
ignoreMergeCommits: true,
446+
// Ensure all messages would fail if not for ignoring merge commits.
447+
maxSubjectLength: 1,
448+
})
432449

433-
const errors = inspection.check(message, defaultInputs);
450+
const errors = inspection.check(message, inputs);
451+
expect(errors).toEqual([]);
452+
});
453+
454+
it('ignores messages with given pattern.', () => {
455+
const inputs = new Inputs({
456+
...defaultInputs,
457+
ignorePatterns: [/\[ALWAYS VALID]/],
458+
// Ensure all messages would fail if not for the ignore pattern.
459+
maxSubjectLength: 1,
460+
})
461+
462+
const message = 'Change SomeClass to OtherClass\n'
463+
+ '\n'
464+
+ 'This replaces the SomeClass with OtherClass in all of the module.\n'
465+
+ '[ALWAYS VALID] '
466+
467+
const errors = inspection.check(message, inputs);
434468
expect(errors).toEqual([]);
435469
});
436470

src/input.ts

+33
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ interface InputValues {
1010
enforceSignOff: boolean;
1111
validatePullRequestCommits: boolean;
1212
skipBodyCheck: boolean;
13+
ignoreMergeCommits: boolean;
14+
ignorePatterns: RegExp[];
1315
}
1416

1517
export class Inputs implements InputValues {
@@ -20,6 +22,8 @@ export class Inputs implements InputValues {
2022
public maxBodyLineLength: number;
2123
public skipBodyCheck: boolean;
2224
public validatePullRequestCommits: boolean;
25+
public ignoreMergeCommits: boolean;
26+
public ignorePatterns: RegExp[];
2327

2428
// This is a complete appendix to the whitelist parsed both from
2529
// the GitHub action input "additional-verbs" and from the file
@@ -38,6 +42,8 @@ export class Inputs implements InputValues {
3842
this.enforceSignOff = values.enforceSignOff;
3943
this.validatePullRequestCommits = values.validatePullRequestCommits;
4044
this.skipBodyCheck = values.skipBodyCheck;
45+
this.ignoreMergeCommits = values.ignoreMergeCommits;
46+
this.ignorePatterns = values.ignorePatterns;
4147
}
4248
}
4349

@@ -80,6 +86,8 @@ interface RawInputs {
8086
enforceSignOffInput?: string;
8187
validatePullRequestCommitsInput?: string;
8288
skipBodyCheckInput?: string;
89+
ignoreMergeCommitsInput?: string;
90+
ignorePatternsInput?: string;
8391
}
8492

8593
export function parseInputs(rawInputs: RawInputs): MaybeInputs {
@@ -92,6 +100,8 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs {
92100
enforceSignOffInput = '',
93101
validatePullRequestCommitsInput = '',
94102
skipBodyCheckInput = '',
103+
ignoreMergeCommitsInput = '',
104+
ignorePatternsInput = '',
95105
} = rawInputs;
96106

97107
const additionalVerbs = new Set<string>();
@@ -193,6 +203,27 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs {
193203
);
194204
}
195205

206+
const ignoreMergeCommits: boolean | null = !ignoreMergeCommitsInput
207+
? true
208+
: parseBooleanFromString(ignoreMergeCommitsInput);
209+
210+
if (ignoreMergeCommits === null) {
211+
return new MaybeInputs(
212+
null,
213+
'Unexpected value for ignore-merge-commits. ' +
214+
`Expected either 'true' or 'false', got: ${ignoreMergeCommitsInput}`,
215+
);
216+
}
217+
218+
const ignorePatterns: RegExp[] =
219+
ignorePatternsInput == null
220+
? []
221+
: ignorePatternsInput
222+
.split('\n')
223+
.map(s => s.trim())
224+
.filter(s => s.length > 0)
225+
.map(s => new RegExp(s));
226+
196227
return new MaybeInputs(
197228
new Inputs({
198229
hasAdditionalVerbsInput,
@@ -204,6 +235,8 @@ export function parseInputs(rawInputs: RawInputs): MaybeInputs {
204235
enforceSignOff,
205236
validatePullRequestCommits,
206237
skipBodyCheck,
238+
ignoreMergeCommits,
239+
ignorePatterns,
207240
}),
208241
null,
209242
);

src/inspection.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,30 @@ function checkSignedOff(bodyLines: string[]): string[] {
293293
return errors;
294294
}
295295

296-
const mergeMessageRe = new RegExp(
297-
"^Merge branch '[^\\000-\\037\\177 ~^:?*[]+' " +
298-
'into [^\\000-\\037\\177 ~^:?*[]+$',
299-
);
296+
const mergeMessagePatterns: RegExp[] = [
297+
// Local merges to default branch
298+
/^Merge branch (?:'\S+'|"\S+"|\S+)/,
299+
// Local merges to alternate branch
300+
/^Merge (:?remote-tracking )?branch (?:'\S+'|"\S+"|\S+) into \S+/,
301+
// GitHub Web UI merge pull request
302+
/^Merge pull request #\d+ from \S+/,
303+
];
300304

301305
export function check(message: string, inputs: input.Inputs): string[] {
302306
const errors: string[] = [];
303307

304-
if (mergeMessageRe.test(message)) {
305-
return errors;
308+
if (inputs.ignoreMergeCommits) {
309+
for (const pattern of mergeMessagePatterns) {
310+
if (pattern.test(message)) {
311+
return errors;
312+
}
313+
}
314+
}
315+
316+
for (const ignorePattern of inputs.ignorePatterns) {
317+
if (ignorePattern.test(message)) {
318+
return errors;
319+
}
306320
}
307321

308322
const lines = splitLines(message);

src/mainImpl.ts

+10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ async function runWithExceptions(): Promise<void> {
4444
required: false,
4545
});
4646

47+
const ignoreMergeCommitsInput = core.getInput('ignore-merge-commits', {
48+
required: false,
49+
});
50+
51+
const ignorePatternsInput = core.getInput('ignore-patterns', {
52+
required: false,
53+
});
54+
4755
const maybeInputs = input.parseInputs({
4856
additionalVerbsInput,
4957
pathToAdditionalVerbsInput,
@@ -53,6 +61,8 @@ async function runWithExceptions(): Promise<void> {
5361
enforceSignOffInput,
5462
validatePullRequestCommitsInput,
5563
skipBodyCheckInput,
64+
ignoreMergeCommitsInput,
65+
ignorePatternsInput,
5666
});
5767

5868
if (maybeInputs.error !== null) {

0 commit comments

Comments
 (0)