Skip to content

ci: add dco-advisor #14

ci: add dco-advisor

ci: add dco-advisor #14

Workflow file for this run

name: DCO Advisor Bot
on:
pull_request:
types: [opened, reopened, synchronize]
permissions:
pull-requests: write
issues: write
jobs:
dco_advisor:
runs-on: ubuntu-latest
steps:
- name: Handle DCO check result
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request || context.payload.check_run?.pull_requests?.[0];
if (!pr) return;
const prNumber = pr.number;
const baseRef = pr.base.ref;
const headSha =
context.payload.check_run?.head_sha ||
pr.head?.sha;
console.log("HEAD SHA:", headSha);
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// Poll until DCO check has a conclusion (max 6 attempts, 30s)
let dcoCheck = null;
for (let attempt = 0; attempt < 6; attempt++) {
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: headSha
});
console.log("All check runs:");
checks.check_runs.forEach(run => {
console.log(`- ${run.name} (${run.status}/${run.conclusion}) @ ${run.head_sha}`);
});
dcoCheck = checks.check_runs.find(run =>
run.name.toLowerCase().includes("dco") &&
!run.name.toLowerCase().includes("dco_advisor") &&
run.head_sha === headSha
);
console.log(`DCO check ${dcoCheck?.name} --> ${dcoCheck?.conclusion}`);
console.log(`Actions: ${dcoCheck}`);
console.log(`Actions: ${dcoCheck?.actions}`);
if (dcoCheck?.conclusion) break;
console.log(`Waiting for DCO check... (${attempt + 1})`);
await sleep(5000); // wait 5 seconds
}
if (!dcoCheck || !dcoCheck.conclusion) {
console.log("DCO check did not complete in time.");
return;
}
// Get existing comments on the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
// Look for a previous bot comment
const existingComment = comments.find(c =>
c.body.includes("<!-- dco-advice-bot -->")
);
// Define the failure comment body
const failureBody = [
'<!-- dco-advice-bot -->',
'❌ **DCO Check Failed**',
'',
`Hi @${pr.user.login}, your pull request has failed the Developer Certificate of Origin (DCO) check.`,
'',
'Because this repository supports **remediation commits**, you can fix this without rewriting history!',
'',
'---',
'',
'### 🛠 Quick Fix: Add a remediation commit',
'```bash',
'git commit --allow-empty -s -m "chore: DCO remediation for failing commits"',
'git push',
'```',
'',
'This adds an empty signed commit to declare you agree with your previous commits.',
'',
'---',
'',
'<details>',
'<summary>🔧 Advanced: Sign off each commit</summary>',
'',
'If you prefer to re-sign your existing commits:',
'',
'**For the latest commit:**',
'```bash',
'git commit --amend --signoff',
'git push --force',
'```',
'',
'**For multiple commits:**',
'```bash',
`git rebase --signoff origin/${baseRef}`,
'git push --force-with-lease',
'```',
'',
'</details>',
'',
'---',
'',
'More info: [DCO Fix Guide](https://github.com/probot/dco#how-it-works)'
].join('\n');
const successBody = [
'<!-- dco-advice-bot -->',
'✅ **DCO Check Passed**',
'',
`Thanks @${pr.user.login}, all your commits are properly signed off. 🎉`
].join('\n');
const isFailure = ["failure", "action_required"].includes(dcoCheck.conclusion);
console.log(`DCO check conclusion for ${headSha}: ${dcoCheck.conclusion} (treated as ${isFailure ? "failure" : "success"})`);
const newBody = isFailure ? failureBody : successBody;
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: newBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: newBody
});
}