Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,22 @@ on:
branches: [ '*' ]

jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm test

test-action:
name: Enforce convention commit
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v2
- name: Test the action locally
Expand Down
8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ inputs:
required: false
description: Whether to scrub old lint reports from comments
default: 'true'
check_double_colon:
required: false
description: 'Fail when a commit header contains a redundant colon after the type(scope): separator, e.g. "fix: : description"'
default: 'true'
duplicate_threshold:
required: false
description: 'Similarity threshold (0.0–1.0) above which two commit subjects are considered duplicates and the action fails. Leave empty to disable.'
default: ''

runs:
using: node12
Expand Down
3,327 changes: 3,283 additions & 44 deletions dist/index.js

Large diffs are not rendered by default.

67 changes: 59 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ const moment = require('moment');
const lint = lintLib.default;
const defaultConfigRules = defaultConfig.rules;

const { normalizeSubject, diceSimilarity, hasDoubleColon } = require('./src/similarity');

const validEvent = ['pull_request'];

async function run() {
try {
const token = core.getInput('token', { required: true });
const createComment = core.getBooleanInput('comment');
const deleteComment = core.getBooleanInput('delete_comment');
const checkDoubleColon = core.getBooleanInput('check_double_colon');
const duplicateThresholdRaw = core.getInput('duplicate_threshold');
const duplicateThreshold = duplicateThresholdRaw !== '' ? parseFloat(duplicateThresholdRaw) : null;

// load rules from file, if there
const configPath = core.getInput('config_path', { required: true });

// relative to dist/index.js
const filename = path.join(__dirname, '/../', configPath);
const filename = path.join(process.env.GITHUB_WORKSPACE || process.cwd(), configPath);
core.debug(`Loading config from path: ${filename}`);
const config = fs.existsSync(filename) ? JSON.parse(fs.readFileSync(filename, 'utf8')) : {};
core.debug(`Loaded config is: ${JSON.stringify(config)}`);
Expand Down Expand Up @@ -109,8 +113,9 @@ async function run() {
`By **[${commit.author.name} (${meta.committer.login})](https://github.com/${meta.committer.login})** _${relativeTime}_` :
`By **${commit.author.name} (Unknown Login) _${relativeTime}_`;

const headerIcon = report.valid ? '✅' :
report.errors.length ? '❌' : '⚠️';
const doubleColon = checkDoubleColon && hasDoubleColon(commit.message);
const headerIcon = (report.valid && !doubleColon) ? '✅' :
(report.errors.length || doubleColon) ? '❌' : '⚠️';
let commitReportText = `
### ${headerIcon} [Commit ${shaShort}](https://github.com/${owner}/${repo.name}/commit/${sha})

Expand Down Expand Up @@ -146,14 +151,40 @@ ${commit.message}
commitReportText += `
${errorReportText}
${warningReportText}
`;
}

if (doubleColon) {
core.error(`header-colon-count: commit header contains a redundant colon ("${commit.message}")`);
countErrors++;
commitReportText += `
❌ **ERROR**: commit header must contain exactly one colon — remove the extra \`:\` before the description
`;
}
commitReports.push(commitReportText);
core.endGroup();
});

if (countErrors) {
core.setFailed(`Action failed with ${countErrors} errors (and ${countWarnings} warnings)`);
// Duplicate / similar commit detection
const duplicatePairs = [];
if (duplicateThreshold !== null) {
core.debug(`Checking for duplicate commit subjects (threshold: ${duplicateThreshold})...`);
for (let i = 0; i < commits.data.length; i++) {
for (let j = i + 1; j < commits.data.length; j++) {
const subjectA = normalizeSubject(commits.data[i].commit.message);
const subjectB = normalizeSubject(commits.data[j].commit.message);
const similarity = diceSimilarity(subjectA, subjectB);
core.debug(` Similarity [${i}↔${j}]: ${similarity.toFixed(3)} — "${subjectA}" vs "${subjectB}"`);
if (similarity >= duplicateThreshold) {
duplicatePairs.push({ a: commits.data[i], b: commits.data[j], similarity });
}
}
}
core.debug(`Found ${duplicatePairs.length} duplicate pair(s)`);
}

if (countErrors || duplicatePairs.length) {
core.setFailed(`Action failed with ${countErrors} error(s), ${countWarnings} warning(s), and ${duplicatePairs.length} duplicate commit subject(s)`);
}

if (deleteComment) {
Expand Down Expand Up @@ -187,7 +218,26 @@ ${warningReportText}
}

if (createComment) {
if (countErrors || countWarnings) {
const duplicateReport = duplicatePairs.length ? `
## Duplicate Commit Subjects

The following commit pairs have subjects that are too similar (threshold: ${duplicateThreshold}):

${duplicatePairs.map(({ a, b, similarity }) => {
const shaA = a.sha.substring(0, 7);
const shaB = b.sha.substring(0, 7);
const pct = Math.round(similarity * 100);
return `### 🔁 ${pct}% similar

| | Commit | Subject |
|---|---|---|
| A | [\`${shaA}\`](https://github.com/${owner}/${repo.name}/commit/${a.sha}) | \`${a.commit.message.split('\n')[0]}\` |
| B | [\`${shaB}\`](https://github.com/${owner}/${repo.name}/commit/${b.sha}) | \`${b.commit.message.split('\n')[0]}\` |
`;
}).join('\n')}
` : '';

if (countErrors || countWarnings || duplicatePairs.length) {
const finalReport = `
# 🚨🚔 Unconventional Commit 👮‍♀️🙅‍♂️

Expand All @@ -199,9 +249,10 @@ ${warningReportText}
- 👤 **${authors.length} author(s)**
- ❌ **${countErrors} lint error(s)**
- ⚠️ **${countWarnings} lint warning(s)**
- 🔁 **${duplicatePairs.length} duplicate subject pair(s)**

${commitReports.join('\n')}

${duplicateReport}
## Tips

Be sure to follow the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) guideline when authoring your commits.
Expand Down
Loading
Loading