git: a9a6a51edac1 - main - github: Add new checklist workflow

From: Ed Maste <emaste_at_FreeBSD.org>
Date: Fri, 24 Jan 2025 20:27:44 UTC
The branch main has been updated by emaste:

URL: https://cgit.FreeBSD.org/src/commit/?id=a9a6a51edac13aaff5517dd602272152efa6d7bd

commit a9a6a51edac13aaff5517dd602272152efa6d7bd
Author:     Ahmad Khalifa <ahmadkhalifa570@gmail.com>
AuthorDate: 2025-01-05 05:01:07 +0000
Commit:     Ed Maste <emaste@FreeBSD.org>
CommitDate: 2025-01-24 20:27:08 +0000

    github: Add new checklist workflow
    
    Add a new 'checklist' workflow that checks the commit messages on pull
    requests. Currently, the workflow creates a comment on the pull request
    if any of these conditions are hit:
      - Missing Signed-off-by
      - Malformed Signed-off-by
      - Bad email (i.e *noreply*)
    
    Reviewed by:    emaste, imp
    Pull request:   https://github.com/freebsd/freebsd-src/pull/1570
    
    Signed-off-by: Ahmad Khalifa <ahmadkhalifa570@gmail.com>
---
 .github/workflows/checklist.yml | 106 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 106 insertions(+)

diff --git a/.github/workflows/checklist.yml b/.github/workflows/checklist.yml
new file mode 100644
index 000000000000..9734af4a1a1d
--- /dev/null
+++ b/.github/workflows/checklist.yml
@@ -0,0 +1,106 @@
+name: Checklist
+
+# Produce a list of things that need to be changed
+# for the submission to align with CONTRIBUTING.md
+
+on:
+  pull_request:
+    types: [ opened, reopened, edited, synchronize ]
+
+permissions:
+  pull-requests: write
+
+jobs:
+  checklist:
+    name: commit
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v7
+        with:
+          # An asynchronous javascript function
+          script: |
+            /*
+             * Github's API returns the results in pages of 30, so
+             * pass the function we want, along with it's arguments,
+             * to paginate() which will handle gathering all the results.
+             */
+            const comments = await github.paginate(github.rest.issues.listComments, {
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number
+            });
+
+            const commits = await github.paginate(github.rest.pulls.listCommits, {
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              pull_number: context.issue.number
+            });
+
+            let checklist = {};
+            let checklist_len = 0;
+            let comment_id = -1;
+
+            const msg_prefix = "Thank you for taking the time to contribute to FreeBSD!\n";
+            const addToChecklist = (msg, sha) => {
+              if (!checklist[msg]) {
+                checklist[msg] = [];
+                checklist_len++;
+              }
+              checklist[msg].push(sha);
+            }
+
+            for (const commit of commits) {
+              const sob_lines = commit.commit.message.match(/^[^\S\r\n]*signed-off-by:.*/gim);
+
+              if (sob_lines == null && !commit.commit.author.email.toLowerCase().endsWith("freebsd.org"))
+                addToChecklist("Missing Signed-off-by lines", commit.sha);
+              else if (sob_lines != null) {
+                let author_signed = false;
+                for (const line of sob_lines) {
+                  if (!line.includes("Signed-off-by: "))
+                    /* Only display the part we care about. */
+                    addToChecklist("Expected `Signed-off-by: `, got `" + line.match(/^[^\S\r\n]*signed-off-by:./i) + "`", commit.sha);
+                  if (line.includes(commit.commit.author.email))
+                    author_signed = true;
+                }
+
+                if (!author_signed)
+                  console.log("::warning title=Missing-Author-Signature::Missing Signed-off-by from author");
+              }
+
+              if (commit.commit.author.email.toLowerCase().includes("noreply"))
+                addToChecklist("Real email address is needed", commit.sha);
+            }
+
+            /* Check if we've commented before. */
+            for (const comment of comments) {
+              if (comment.user.login == "github-actions[bot]") {
+                comment_id = comment.id;
+                break;
+              }
+            }
+
+            if (checklist_len != 0) {
+              let msg = msg_prefix +
+                "There " + (checklist_len > 1 ? "are a few issues that need " : "is an issue that needs ") +
+                "to be fixed:\n";
+              let comment_func = comment_id == -1 ? github.rest.issues.createComment : github.rest.issues.updateComment;
+
+              /* Loop for each key in "checklist". */
+              for (const c in checklist)
+                msg += "- " + c + "<sup>" + checklist[c].join(", ") + "</sup>\n";
+
+              comment_func({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: msg,
+                ...(comment_id == -1 ? {issue_number: context.issue.number} : {comment_id: comment_id})
+              });
+            } else if (comment_id != -1) {
+              github.rest.issues.updateComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: comment_id,
+                body: msg_prefix + "All issues resolved."
+              });
+            }