#!/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) subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$" if not re.match(subject_line_format, subject_line): error("Subject line should match " + repr(subject_line_format) + ": " + 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:]: # Long lines with URLs are exempt from the line length rule. if len(line) >= 76 and "://" not in line: 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)