1#!/usr/bin/env python3 2 3import re 4import subprocess 5import sys 6 7verbosity = 0 # Show what's going on, 0 1 or 2. 8suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages. 9 10 11def verbose(*args): 12 if verbosity: 13 print(*args) 14 15 16def very_verbose(*args): 17 if verbosity > 1: 18 print(*args) 19 20 21def git_log(pretty_format, *args): 22 # Delete pretty argument from user args so it doesn't interfere with what we do. 23 args = ["git", "log"] + [arg for arg in args if "--pretty" not in args] 24 args.append("--pretty=format:" + pretty_format) 25 very_verbose("git_log", *args) 26 # Generator yielding each output line. 27 for line in subprocess.Popen(args, stdout=subprocess.PIPE).stdout: 28 yield line.decode().rstrip("\r\n") 29 30 31def verify(sha): 32 verbose("verify", sha) 33 errors = [] 34 warnings = [] 35 36 def error_text(err): 37 return "commit " + sha + ": " + err 38 39 def error(err): 40 errors.append(error_text(err)) 41 42 def warning(err): 43 warnings.append(error_text(err)) 44 45 # Author and committer email. 46 for line in git_log("%ae%n%ce", sha, "-n1"): 47 very_verbose("email", line) 48 if "noreply" in line: 49 error("Unwanted email address: " + line) 50 51 # Message body. 52 raw_body = list(git_log("%B", sha, "-n1")) 53 if not raw_body: 54 error("Message is empty") 55 return errors, warnings 56 57 # Subject line. 58 subject_line = raw_body[0] 59 very_verbose("subject_line", subject_line) 60 subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$" 61 if not re.match(subject_line_format, subject_line): 62 error("Subject line should match " + repr(subject_line_format) + ": " + subject_line) 63 if len(subject_line) >= 73: 64 error("Subject line should be 72 or less characters: " + subject_line) 65 66 # Second one divides subject and body. 67 if len(raw_body) > 1 and raw_body[1]: 68 error("Second message line should be empty: " + raw_body[1]) 69 70 # Message body lines. 71 for line in raw_body[2:]: 72 if len(line) >= 76: 73 error("Message lines should be 75 or less characters: " + line) 74 75 if not raw_body[-1].startswith("Signed-off-by: ") or "@" not in raw_body[-1]: 76 warning("Message should be signed-off") 77 78 return errors, warnings 79 80 81def run(args): 82 verbose("run", *args) 83 has_errors = False 84 has_warnings = False 85 for sha in git_log("%h", *args): 86 errors, warnings = verify(sha) 87 has_errors |= any(errors) 88 has_warnings |= any(warnings) 89 for err in errors: 90 print("error:", err) 91 for err in warnings: 92 print("warning:", err) 93 if has_errors or has_warnings: 94 if suggestions: 95 print("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md") 96 else: 97 print("ok") 98 if has_errors: 99 sys.exit(1) 100 101 102def show_help(): 103 print("usage: verifygitlog.py [-v -n -h] ...") 104 print("-v : increase verbosity, can be speficied multiple times") 105 print("-n : do not print multi-line suggestions") 106 print("-h : print this help message and exit") 107 print("... : arguments passed to git log to retrieve commits to verify") 108 print(" see https://www.git-scm.com/docs/git-log") 109 print(" passing no arguments at all will verify all commits") 110 print("examples:") 111 print("verifygitlog.py -n10 # Check last 10 commits") 112 print("verifygitlog.py -v master..HEAD # Check commits since master") 113 114 115if __name__ == "__main__": 116 args = sys.argv[1:] 117 verbosity = args.count("-v") 118 suggestions = args.count("-n") == 0 119 if "-h" in args: 120 show_help() 121 else: 122 args = [arg for arg in args if arg not in ["-v", "-n", "-h"]] 123 run(args) 124