77verbosity = 0 # Show what's going on, 0 1 or 2.
88suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages.
99
10+ ignore_prefixes = []
11+
1012
1113def verbose (* args ):
1214 if verbosity :
@@ -18,6 +20,22 @@ def very_verbose(*args):
1820 print (* args )
1921
2022
23+ class ErrorCollection :
24+ # Track errors and warnings as the program runs
25+ def __init__ (self ):
26+ self .has_errors = False
27+ self .has_warnings = False
28+ self .prefix = ""
29+
30+ def error (self , text ):
31+ print ("error: {}{}" .format (self .prefix , text ))
32+ self .has_errors = True
33+
34+ def warning (self , text ):
35+ print ("warning: {}{}" .format (self .prefix , text ))
36+ self .has_warnings = True
37+
38+
2139def git_log (pretty_format , * args ):
2240 # Delete pretty argument from user args so it doesn't interfere with what we do.
2341 args = ["git" , "log" ] + [arg for arg in args if "--pretty" not in args ]
@@ -28,83 +46,88 @@ def git_log(pretty_format, *args):
2846 yield line .decode ().rstrip ("\r \n " )
2947
3048
31- def verify (sha ):
49+ def verify (sha , err ):
3250 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 ))
51+ err .prefix = "commit " + sha + ": "
4452
4553 # Author and committer email.
4654 for line in git_log ("%ae%n%ce" , sha , "-n1" ):
4755 very_verbose ("email" , line )
4856 if "noreply" in line :
49- error ("Unwanted email address: " + line )
57+ err . error ("Unwanted email address: " + line )
5058
5159 # Message body.
5260 raw_body = list (git_log ("%B" , sha , "-n1" ))
61+ verify_message_body (raw_body , err )
62+
63+
64+ def verify_message_body (raw_body , err ):
5365 if not raw_body :
54- error ("Message is empty" )
55- return errors , warnings
66+ err . error ("Message is empty" )
67+ return
5668
5769 # Subject line.
5870 subject_line = raw_body [0 ]
71+ for prefix in ignore_prefixes :
72+ if subject_line .startswith (prefix ):
73+ verbose ("Skipping ignored commit message" )
74+ return
5975 very_verbose ("subject_line" , subject_line )
6076 subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$"
6177 if not re .match (subject_line_format , subject_line ):
62- error ("Subject line should match " + repr (subject_line_format ) + ": " + subject_line )
78+ err . error ("Subject line should match " + repr (subject_line_format ) + ": " + subject_line )
6379 if len (subject_line ) >= 73 :
64- error ("Subject line should be 72 or less characters: " + subject_line )
80+ err . error ("Subject line should be 72 or less characters: " + subject_line )
6581
6682 # Second one divides subject and body.
6783 if len (raw_body ) > 1 and raw_body [1 ]:
68- error ("Second message line should be empty: " + raw_body [1 ])
84+ err . error ("Second message line should be empty: " + raw_body [1 ])
6985
7086 # Message body lines.
7187 for line in raw_body [2 :]:
7288 # Long lines with URLs are exempt from the line length rule.
7389 if len (line ) >= 76 and "://" not in line :
74- error ("Message lines should be 75 or less characters: " + line )
90+ err . error ("Message lines should be 75 or less characters: " + line )
7591
7692 if not raw_body [- 1 ].startswith ("Signed-off-by: " ) or "@" not in raw_body [- 1 ]:
77- warning ("Message should be signed-off" )
78-
79- return errors , warnings
93+ err .warning ("Message should be signed-off" )
8094
8195
8296def run (args ):
8397 verbose ("run" , * args )
84- has_errors = False
85- has_warnings = False
86- for sha in git_log ("%h" , * args ):
87- errors , warnings = verify (sha )
88- has_errors |= any (errors )
89- has_warnings |= any (warnings )
90- for err in errors :
91- print ("error:" , err )
92- for err in warnings :
93- print ("warning:" , err )
94- if has_errors or has_warnings :
98+
99+ err = ErrorCollection ()
100+
101+ if "--check-file" in args :
102+ filename = args [- 1 ]
103+ verbose ("checking commit message from" , filename )
104+ with open (args [- 1 ]) as f :
105+ lines = [line .rstrip ("\r \n " ) for line in f ]
106+ verify_message_body (lines , err )
107+ else : # Normal operation, pass arguments to git log
108+ for sha in git_log ("%h" , * args ):
109+ verify (sha , err )
110+
111+ if err .has_errors or err .has_warnings :
95112 if suggestions :
96113 print ("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md" )
97114 else :
98115 print ("ok" )
99- if has_errors :
116+ if err . has_errors :
100117 sys .exit (1 )
101118
102119
103120def show_help ():
104- print ("usage: verifygitlog.py [-v -n -h] ..." )
121+ print ("usage: verifygitlog.py [-v -n -h --check-file ] ..." )
105122 print ("-v : increase verbosity, can be speficied multiple times" )
106123 print ("-n : do not print multi-line suggestions" )
107124 print ("-h : print this help message and exit" )
125+ print (
126+ "--check-file : Pass a single argument which is a file containing a candidate commit message"
127+ )
128+ print (
129+ "--ignore-rebase : Skip checking commits with git rebase autosquash prefixes or WIP as a prefix"
130+ )
108131 print ("... : arguments passed to git log to retrieve commits to verify" )
109132 print (" see https://www.git-scm.com/docs/git-log" )
110133 print (" passing no arguments at all will verify all commits" )
@@ -117,6 +140,10 @@ def show_help():
117140 args = sys .argv [1 :]
118141 verbosity = args .count ("-v" )
119142 suggestions = args .count ("-n" ) == 0
143+ if "--ignore-rebase" in args :
144+ args .remove ("--ignore-rebase" )
145+ ignore_prefixes = ["squash!" , "fixup!" , "amend!" , "WIP" ]
146+
120147 if "-h" in args :
121148 show_help ()
122149 else :
0 commit comments