Unverified Commit e97d1c56 authored by Matt Sturgeon's avatar Matt Sturgeon Committed by GitHub
Browse files

{workflows/lint,ci/github-script}: lint commit messages (#470523)

parents 6fa7d771 7cf59724
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -124,3 +124,25 @@ jobs:
            echo "If you're having trouble, ping @NixOS/nixpkgs-vet"
            exit "$exitCode"
          fi

  commits:
    runs-on: ubuntu-slim
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          persist-credentials: false
          path: trusted
          sparse-checkout: |
            ci/github-script
      - name: Check commit messages
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          script: |
            const checkCommitMessages = require('./trusted/ci/github-script/lint-commits.js')

            checkCommitMessages({
              github,
              context,
              core,
            })
+90 −0
Original line number Diff line number Diff line
// @ts-check
const { classify } = require('../supportedBranches.js')

/**
 * @param {{
 *  github: InstanceType<import('@actions/github/lib/utils').GitHub>,
 *  context: import('@actions/github/lib/context').Context
 *  core: import('@actions/core')
 * }} CheckCommitMessagesProps
 */
async function checkCommitMessages({ github, context, core }) {
  // Do not run on merge groups: it'd be much harder, and there is no need
  // (changes to the target branch can't change commit messages on the base branch).
  const pull_number = context.payload.pull_request?.number
  if (!pull_number) {
    core.info('This is not a pull request. Skipping checks.')
    return
  }

  const pr = (
    await github.rest.pulls.get({
      ...context.repo,
      pull_number,
    })
  ).data

  const baseBranchType = classify(
    pr.base.ref.replace(/^refs\/heads\//, ''),
  ).type
  const headBranchType = classify(
    pr.head.ref.replace(/^refs\/heads\//, ''),
  ).type

  if (
    baseBranchType.includes('development') &&
    headBranchType.includes('development')
  ) {
    // This matches, for example, PRs from staging-next to master, or vice versa.
    // Ignore them: we should only care about PRs introducing *new* commits.
    core.info(
      'This PR is from one development branch to another. Skipping checks.',
    )
    return
  }

  const commits = await github.paginate(github.rest.pulls.listCommits, {
    ...context.repo,
    pull_number,
  })

  const failures = new Set()

  for (const commit of commits) {
    const message = commit.commit.message
    const firstLine = message.split('\n')[0]

    const logMsgStart = `Commit ${commit.sha}'s message's subject ("${firstLine}")`

    if (!firstLine.includes(':')) {
      core.error(
        `${logMsgStart} was detected as not meeting our guidelines because ` +
          'it does not contain a colon. There are likely other issues as well.',
      )
      failures.add(commit.sha)
    }

    if (firstLine.endsWith('.')) {
      core.error(
        `${logMsgStart} was detected as not meeting our guidelines because ` +
          'it ends in a period. There may be other issues as well.',
      )
      failures.add(commit.sha)
    }

    if (!failures.has(commit.sha)) {
      core.info(`${logMsgStart} passed our automated checks!`)
    }
  }

  if (failures.size !== 0) {
    core.error(
      'Please review the guidelines at ' +
        'https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#commit-conventions, ' +
        'as well as the applicable area-specific guidelines linked there.',
    )
    core.setFailed('Committers: merging is discouraged.')
  }
}

module.exports = checkCommitMessages
+11 −0
Original line number Diff line number Diff line
@@ -94,4 +94,15 @@ program
    await run(getTeams, owner, repo, undefined, { ...options, outFile })
  })

program
  .command('lint-commits')
  .description('Lint for common errors in commit messages')
  .argument('<owner>', 'Owner of the GitHub repository to run on (Example: NixOS)')
  .argument('<repo>', 'Name of the GitHub repository to run on (Example: nixpkgs)')
  .argument('<pr>', 'Number of the Pull Request to run on')
  .action(async (owner, repo, pr, options) => {
    const checkCommitMessages = (await import('./lint-commits.js')).default
    await run(checkCommitMessages, owner, repo, pr, options)
  })

await program.parse()