ci/github-script/lint-commits: confirm Git names/emails are present

Prevents issues reported on Matrix by Jujutsu users,
caused by people omitting these fields.
This commit is contained in:
Michael Daniels
2026-05-03 13:30:07 -04:00
parent ea5da5202c
commit 4d776b4670
2 changed files with 97 additions and 13 deletions

View File

@@ -2,6 +2,17 @@
const { promisify } = require('node:util')
const execFile = promisify(require('node:child_process').execFile)
/**
* @typedef {{
* subject: string,
* sha: string,
* author: { name: string, email: string },
* committer: { name: string, email: string}
* changedPaths: string[],
* changedPathSegments: Set<string>,
* }} Commit
*/
/**
* @param {{
* args: string[]
@@ -34,12 +45,7 @@ async function runGit({ args, repoPath, core, quiet }) {
* repoPath?: string,
* }} GetCommitMessagesForPRProps
*
* @returns {Promise<{
* subject: string,
* sha: string,
* changedPaths: string[],
* changedPathSegments: Set<string>,
* }[]>}
* @returns {Promise<Commit[]>}
*/
async function getCommitDetailsForPR({ core, pr, repoPath }) {
await runGit({
@@ -70,17 +76,25 @@ async function getCommitDetailsForPR({ core, pr, repoPath }) {
return Promise.all(
shas.map(async (sha) => {
// Subject first, then a blank line, then filenames.
// Subject, author name, author email, committer name, committer email (all tab-seperated)
// then a blank line, then filenames.
const result = (
await runGit({
args: ['log', '--format=%s', '--name-only', '-1', sha],
args: [
'log',
'--format=%s\t%aN\t%aE\t%cN\t%cE',
'--name-only',
'-1',
sha,
],
repoPath,
core,
quiet: true,
})
).stdout.split('\n')
const subject = result[0]
const [subject, authorName, authorEmail, committerName, committerEmail] =
result[0].split('\t')
const changedPaths = result.slice(2, -1)
@@ -91,6 +105,8 @@ async function getCommitDetailsForPR({ core, pr, repoPath }) {
return {
sha,
subject,
author: { name: authorName, email: authorEmail },
committer: { name: committerName, email: committerEmail },
changedPaths,
changedPathSegments,
}

View File

@@ -2,15 +2,17 @@
const { classify } = require('../supportedBranches.js')
const { getCommitDetailsForPR } = require('./get-pr-commit-details.js')
/** @typedef {import('./get-pr-commit-details.js').Commit} Commit */
/**
* @param {{
* github: InstanceType<import('@actions/github/lib/utils').GitHub>,
* context: import('@actions/github/lib/context').Context,
* context: typeof import('@actions/github').context,
* core: import('@actions/core'),
* repoPath?: string,
* }} CheckCommitMessagesProps
* }} LintCommitsProps
*/
async function checkCommitMessages({ github, context, core, repoPath }) {
async function lintCommits({ github, context, core, repoPath }) {
// This check should only be run when we have the pull_request context.
const pull_number = context.payload.pull_request?.number
if (!pull_number) {
@@ -48,6 +50,17 @@ async function checkCommitMessages({ github, context, core, repoPath }) {
const commits = await getCommitDetailsForPR({ core, pr, repoPath })
await checkCommitMessages({ commits, core })
await checkCommitMetadata({ commits, core })
}
/**
* @param {{
* commits: Commit[],
* core: import('@actions/core'),
* }} CheckCommitMessagesProps
*/
async function checkCommitMessages({ commits, core }) {
const failures = new Set()
const conventionalCommitTypes = [
@@ -152,4 +165,59 @@ async function checkCommitMessages({ github, context, core, repoPath }) {
}
}
module.exports = checkCommitMessages
/**
* @param {{
* commits: Commit[],
* core: import('@actions/core'),
* }} CheckGitFieldsProps
*/
async function checkCommitMetadata({ commits, core }) {
const failures = new Set()
/** @type {(s: string) => boolean} */
const isEmail = (s) => /^.+@.*$/.test(s)
for (const commit of commits) {
if (!commit.author.name) {
core.error(`Commit ${commit.sha} author's name field is missing`)
failures.add(commit.sha)
}
if (!commit.author.email || !isEmail(commit.author.email)) {
core.error(
`Commit ${commit.sha} author's email field is missing or invalid`,
)
failures.add(commit.sha)
}
if (!commit.committer.name) {
core.error(`Commit ${commit.sha} committer's name field is missing`)
failures.add(commit.sha)
}
if (!commit.committer.email || !isEmail(commit.committer.email)) {
core.error(
`Commit ${commit.sha} committer's email field is missing or invalid`,
)
failures.add(commit.sha)
}
if (!failures.has(commit.sha)) {
core.info(
`Commit ${commit.sha}'s git fields passed our automated checks!`,
)
}
}
if (failures.size !== 0) {
core.error(
'Please add the missing commit fields. ' +
'You can use the noreply email address generated for you by GitHub ' +
'(https://docs.github.com/en/account-and-profile/reference/email-addresses-reference#your-noreply-email-address) ' +
"if you'd like.",
)
core.setFailed('Committers: merging is discouraged.')
}
}
module.exports = lintCommits