]> git.kaiwu.me - nginx.git/commitdiff
GH: check commit messages for the most common errors
authorAndrew Clayton <a.clayton@nginx.com>
Wed, 4 Mar 2026 18:52:07 +0000 (18:52 +0000)
committerAndrew Clayton <a.clayton@nginx.com>
Fri, 24 Apr 2026 16:33:41 +0000 (17:33 +0100)
This checks commit messages in a pull-request for the most common
problems we see.

.github/scripts/commit-msg-check.pl [new file with mode: 0755]
.github/workflows/check-commit-message.yaml [new file with mode: 0644]

diff --git a/.github/scripts/commit-msg-check.pl b/.github/scripts/commit-msg-check.pl
new file mode 100755 (executable)
index 0000000..80ac975
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env perl
+
+# Copyright (C) Nginx, Inc.
+
+#
+# Takes input in the form
+#
+#   git show -s --format=%B <hash>
+#
+
+use strict;
+use warnings;
+
+my $E = "❌ ";
+
+# 72 characters is a natural choice. It provides 4 characters of
+# left/right margin on a standard 80 character wide terminal in
+# git-log(1) etc standard output.
+#
+# vim(1) (from 7.2) ships with Tim Pope's vim-git ftplugin which
+# amongst other things autowraps lines when editing commit messages
+# after 72 characters.
+my $LINE_LENGTH_LIMIT = 72;
+
+my $subject = <>;
+my $body;
+
+while (<>) {
+       $body .= $_;
+}
+
+sub chk_sub_length {
+       if (length($subject) > $LINE_LENGTH_LIMIT) {
+               print $E . "Subject is longer than " . $LINE_LENGTH_LIMIT .
+                     " characters\n";
+       }
+}
+
+sub chk_sub_prefix_cap {
+       my $excemptions = qr/gRPC: /;
+
+       if ($subject =~ /^[a-z][a-zA-Z_-]*: /) {
+               if ($subject =~ /^((?!$excemptions).)*$/) {
+                       print $E . "Subject prefix should be capitalised\n";
+               }
+       }
+
+       if ($subject =~ /^[a-zA-Z_-]*: [A-Z]/) {
+               print $E . "First word after the prefix should be lower case\n";
+       }
+}
+
+sub chk_body_blank_line {
+       if (($body =~ /^(.*)/)[0]) {
+               print $E . "Commit message body should be separated from the subject by a blank line\n";
+       }
+}
+
+sub chk_body_trailers {
+       my $prev_line = "";
+
+       foreach (split(/\n/, $body)) {
+               if (/^[a-zA-Z-]*: /) {
+                       if ($prev_line ne "") {
+                               print $E . "Commit tags/trailers should be separated from the commit message body by a blank line\n";
+                       }
+
+                       last;
+               }
+
+               $prev_line = $_;
+       }
+}
+
+sub chk_body_line_length {
+       foreach (split(/\n/, $body)) {
+               # Ignore indented lines for command/log output etc and URLs.
+               if (/^[ \t]/ || /https?:\/\// || /ftp:\/\//) {
+                       next;
+               }
+
+               # Stop after hitting commit tags/trailers
+               if (/^[a-zA-Z-]*: /) {
+                       last;
+               }
+
+               if (length($_) <= $LINE_LENGTH_LIMIT) {
+                       next;
+               }
+
+               print $E . "One or more body lines exceed " . $LINE_LENGTH_LIMIT . " characters. (Indent command/log output etc lines to quell this error)\n";
+
+               last;
+       }
+}
+
+chomp($subject);
+chk_sub_length();
+chk_sub_prefix_cap();
+
+chk_body_blank_line();
+chk_body_trailers();
+chk_body_line_length();
diff --git a/.github/workflows/check-commit-message.yaml b/.github/workflows/check-commit-message.yaml
new file mode 100644 (file)
index 0000000..3f0cc80
--- /dev/null
@@ -0,0 +1,38 @@
+name: Check Commit Message(s)
+
+# Get the repository with all commits to ensure that we can
+# analyze all of the commits contributed via the Pull Request.
+
+on:
+  pull_request:
+    types: [ opened, synchronize ]
+
+jobs:
+  check-commit-messages:
+    runs-on: ubuntu-24.04
+    steps:
+    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+      with:
+        fetch-depth: 0
+
+    - name: check-commits.sh
+      run: |
+        echo "## Commit Message Linter Results" >${GITHUB_STEP_SUMMARY}
+        err=0
+        while read hash subj
+        do
+          echo "Checking: ${hash} (\"${subj}\")" | tee -a ${GITHUB_STEP_SUMMARY}
+          out=$(git show -s --format=%B ${hash} | .github/scripts/commit-msg-check.pl)
+          if test -n "${out}"
+          then
+            echo "${out}" | tee -a ${GITHUB_STEP_SUMMARY}
+            err=1
+          else
+            echo "✅ ok" | tee -a ${GITHUB_STEP_SUMMARY}
+          fi
+        done <<< $(git rev-list --oneline ${{github.event.pull_request.base.sha}}..${{github.event.pull_request.head.sha}})
+
+        if test ${err} -ne 0
+        then
+          exit 2
+        fi