mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-03-08 01:24:09 +01:00
ci/github-script/lint-commits: support PRs with over 250 commits, check for "fixup!" commits (#486796)
This commit is contained in:
commit
963f784f6d
2 changed files with 98 additions and 11 deletions
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
|
|
@ -136,10 +136,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
persist-credentials: true # Needed to run git fetch for large PRs.
|
||||
path: trusted
|
||||
sparse-checkout: |
|
||||
ci/github-script
|
||||
- name: Check commit messages
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
|
|
@ -150,4 +148,5 @@ jobs:
|
|||
github,
|
||||
context,
|
||||
core,
|
||||
repoPath: 'trusted',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,14 +1,37 @@
|
|||
// @ts-check
|
||||
const { classify } = require('../supportedBranches.js')
|
||||
const { promisify } = require('node:util')
|
||||
const execFile = promisify(require('node:child_process').execFile)
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* args: string[]
|
||||
* core: import('@actions/core'),
|
||||
* quiet?: boolean,
|
||||
* repoPath?: string,
|
||||
* }} RunGitProps
|
||||
*/
|
||||
async function runGit({ args, repoPath, core, quiet }) {
|
||||
if (repoPath) {
|
||||
args = ['-C', repoPath, ...args]
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
core.info(`About to run \`git ${args.map((s) => `'${s}'`).join(' ')}\``)
|
||||
}
|
||||
|
||||
return await execFile('git', args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* github: InstanceType<import('@actions/github/lib/utils').GitHub>,
|
||||
* context: import('@actions/github/lib/context').Context
|
||||
* core: import('@actions/core')
|
||||
* context: import('@actions/github/lib/context').Context,
|
||||
* core: import('@actions/core'),
|
||||
* repoPath?: string,
|
||||
* }} CheckCommitMessagesProps
|
||||
*/
|
||||
async function checkCommitMessages({ github, context, core }) {
|
||||
async function checkCommitMessages({ 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) {
|
||||
|
|
@ -44,15 +67,70 @@ async function checkCommitMessages({ github, context, core }) {
|
|||
return
|
||||
}
|
||||
|
||||
const commits = await github.paginate(github.rest.pulls.listCommits, {
|
||||
...context.repo,
|
||||
pull_number,
|
||||
})
|
||||
/**
|
||||
* GitHub's API will return a maximum of 250 commits.
|
||||
* We will use it if we can, but fall back to using git locally.
|
||||
* This type is used to abstract over the differences between the two.
|
||||
* @type {{
|
||||
* message: string,
|
||||
* sha: string,
|
||||
* }[]}
|
||||
*/
|
||||
let commits
|
||||
|
||||
if (pr.commits < 250) {
|
||||
commits = (
|
||||
await github.paginate(github.rest.pulls.listCommits, {
|
||||
...context.repo,
|
||||
pull_number,
|
||||
})
|
||||
).map((commit) => ({ message: commit.commit.message, sha: commit.sha }))
|
||||
} else {
|
||||
await runGit({
|
||||
args: ['fetch', `--depth=1`, 'origin', pr.base.sha],
|
||||
repoPath,
|
||||
core,
|
||||
})
|
||||
await runGit({
|
||||
args: ['fetch', `--depth=${pr.commits + 1}`, 'origin', pr.head.sha],
|
||||
repoPath,
|
||||
core,
|
||||
})
|
||||
|
||||
const shas = (
|
||||
await runGit({
|
||||
args: [
|
||||
'rev-list',
|
||||
`--max-count=${pr.commits}`,
|
||||
`${pr.base.sha}..${pr.head.sha}`,
|
||||
],
|
||||
repoPath,
|
||||
core,
|
||||
})
|
||||
).stdout
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
commits = await Promise.all(
|
||||
shas.map(async (sha) => ({
|
||||
sha,
|
||||
message: (
|
||||
await runGit({
|
||||
args: ['log', '--format=%s', '-1', sha],
|
||||
repoPath,
|
||||
core,
|
||||
quiet: true,
|
||||
})
|
||||
).stdout,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
const failures = new Set()
|
||||
|
||||
for (const commit of commits) {
|
||||
const message = commit.commit.message
|
||||
const message = commit.message
|
||||
const firstLine = message.split('\n')[0]
|
||||
|
||||
const logMsgStart = `Commit ${commit.sha}'s message's subject ("${firstLine}")`
|
||||
|
|
@ -74,6 +152,16 @@ async function checkCommitMessages({ github, context, core }) {
|
|||
failures.add(commit.sha)
|
||||
}
|
||||
|
||||
const fixups = ['amend!', 'fixup!', 'squash!']
|
||||
if (fixups.some((s) => firstLine.startsWith(s))) {
|
||||
core.error(
|
||||
`${logMsgStart} was detected as not meeting our guidelines because ` +
|
||||
`it begins with "${fixups.find((s) => firstLine.startsWith(s))}". ` +
|
||||
'Did you forget to run `git rebase -i --autosquash`?',
|
||||
)
|
||||
failures.add(commit.sha)
|
||||
}
|
||||
|
||||
if (!failures.has(commit.sha)) {
|
||||
core.info(`${logMsgStart} passed our automated checks!`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue