summaryrefslogtreecommitdiff
path: root/tools/verifygitlog.py
diff options
context:
space:
mode:
authorstijn <stijn@ignitron.net>2021-01-19 13:07:51 +0100
committerDamien George <damien@micropython.org>2021-01-30 14:08:29 +1100
commitd48860c7ddfb37bdc48d0f3afc3a26dcd54cca0f (patch)
treea9add876586fce588ceaf7c1da58150f19412671 /tools/verifygitlog.py
parentfca2730ea01bc8457804884f4a0472ae0d938a19 (diff)
tools/verifygitlog.py: Add script for verifying commit message format.
The main rules enforced are: - At most 72 characters in the subject line, with a ": " in it. - At most 75 characters per line in the body. - No "noreply" email addresses.
Diffstat (limited to 'tools/verifygitlog.py')
-rwxr-xr-xtools/verifygitlog.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/tools/verifygitlog.py b/tools/verifygitlog.py
new file mode 100755
index 000000000..0080b96bf
--- /dev/null
+++ b/tools/verifygitlog.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+
+import re
+import subprocess
+import sys
+
+verbosity = 0 # Show what's going on, 0 1 or 2.
+suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages.
+
+
+def verbose(*args):
+ if verbosity:
+ print(*args)
+
+
+def very_verbose(*args):
+ if verbosity > 1:
+ print(*args)
+
+
+def git_log(pretty_format, *args):
+ # Delete pretty argument from user args so it doesn't interfere with what we do.
+ args = ["git", "log"] + [arg for arg in args if "--pretty" not in args]
+ args.append("--pretty=format:" + pretty_format)
+ very_verbose("git_log", *args)
+ # Generator yielding each output line.
+ for line in subprocess.Popen(args, stdout=subprocess.PIPE).stdout:
+ yield line.decode().rstrip("\r\n")
+
+
+def verify(sha):
+ verbose("verify", sha)
+ errors = []
+ warnings = []
+
+ def error_text(err):
+ return "commit " + sha + ": " + err
+
+ def error(err):
+ errors.append(error_text(err))
+
+ def warning(err):
+ warnings.append(error_text(err))
+
+ # Author and committer email.
+ for line in git_log("%ae%n%ce", sha, "-n1"):
+ very_verbose("email", line)
+ if "noreply" in line:
+ error("Unwanted email address: " + line)
+
+ # Message body.
+ raw_body = list(git_log("%B", sha, "-n1"))
+ if not raw_body:
+ error("Message is empty")
+ return errors, warnings
+
+ # Subject line.
+ subject_line = raw_body[0]
+ very_verbose("subject_line", subject_line)
+ if not re.match(r"^[^!]+: [A-Z]+.+ .+\.$", subject_line):
+ error("Subject line should contain ': ' and end in '.': " + subject_line)
+ if len(subject_line) >= 73:
+ error("Subject line should be 72 or less characters: " + subject_line)
+
+ # Second one divides subject and body.
+ if len(raw_body) > 1 and raw_body[1]:
+ error("Second message line should be empty: " + raw_body[1])
+
+ # Message body lines.
+ for line in raw_body[2:]:
+ if len(line) >= 76:
+ error("Message lines should be 75 or less characters: " + line)
+
+ if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]:
+ warning("Message should be signed-off")
+
+ return errors, warnings
+
+
+def run(args):
+ verbose("run", *args)
+ has_errors = False
+ has_warnings = False
+ for sha in git_log("%h", *args):
+ errors, warnings = verify(sha)
+ has_errors |= any(errors)
+ has_warnings |= any(warnings)
+ for err in errors:
+ print("error:", err)
+ for err in warnings:
+ print("warning:", err)
+ if has_errors or has_warnings:
+ if suggestions:
+ print("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md")
+ else:
+ print("ok")
+ if has_errors:
+ sys.exit(1)
+
+
+def show_help():
+ print("usage: verifygitlog.py [-v -n -h] ...")
+ print("-v : increase verbosity, can be speficied multiple times")
+ print("-n : do not print multi-line suggestions")
+ print("-h : print this help message and exit")
+ print("... : arguments passed to git log to retrieve commits to verify")
+ print(" see https://www.git-scm.com/docs/git-log")
+ print(" passing no arguments at all will verify all commits")
+ print("examples:")
+ print("verifygitlog.py -n10 # Check last 10 commits")
+ print("verifygitlog.py -v master..HEAD # Check commits since master")
+
+
+if __name__ == "__main__":
+ args = sys.argv[1:]
+ verbosity = args.count("-v")
+ suggestions = args.count("-n") == 0
+ if "-h" in args:
+ show_help()
+ else:
+ args = [arg for arg in args if arg not in ["-v", "-n", "-h"]]
+ run(args)