7
7
verbosity = 0 # Show what's going on, 0 1 or 2.
8
8
suggestions = 1 # Set to 0 to not include lengthy suggestions in error messages.
9
9
10
+ ignore_prefixes = []
11
+
10
12
11
13
def verbose (* args ):
12
14
if verbosity :
@@ -18,6 +20,22 @@ def very_verbose(*args):
18
20
print (* args )
19
21
20
22
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
+
21
39
def git_log (pretty_format , * args ):
22
40
# Delete pretty argument from user args so it doesn't interfere with what we do.
23
41
args = ["git" , "log" ] + [arg for arg in args if "--pretty" not in args ]
@@ -28,83 +46,88 @@ def git_log(pretty_format, *args):
28
46
yield line .decode ().rstrip ("\r \n " )
29
47
30
48
31
- def verify (sha ):
49
+ def verify (sha , err ):
32
50
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 + ": "
44
52
45
53
# Author and committer email.
46
54
for line in git_log ("%ae%n%ce" , sha , "-n1" ):
47
55
very_verbose ("email" , line )
48
56
if "noreply" in line :
49
- error ("Unwanted email address: " + line )
57
+ err . error ("Unwanted email address: " + line )
50
58
51
59
# Message body.
52
60
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 ):
53
65
if not raw_body :
54
- error ("Message is empty" )
55
- return errors , warnings
66
+ err . error ("Message is empty" )
67
+ return
56
68
57
69
# Subject line.
58
70
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
59
75
very_verbose ("subject_line" , subject_line )
60
76
subject_line_format = r"^[^!]+: [A-Z]+.+ .+\.$"
61
77
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 )
63
79
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 )
65
81
66
82
# Second one divides subject and body.
67
83
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 ])
69
85
70
86
# Message body lines.
71
87
for line in raw_body [2 :]:
72
88
# Long lines with URLs are exempt from the line length rule.
73
89
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 )
75
91
76
92
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" )
80
94
81
95
82
96
def run (args ):
83
97
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 :
95
112
if suggestions :
96
113
print ("See https://github.com/micropython/micropython/blob/master/CODECONVENTIONS.md" )
97
114
else :
98
115
print ("ok" )
99
- if has_errors :
116
+ if err . has_errors :
100
117
sys .exit (1 )
101
118
102
119
103
120
def show_help ():
104
- print ("usage: verifygitlog.py [-v -n -h] ..." )
121
+ print ("usage: verifygitlog.py [-v -n -h --check-file ] ..." )
105
122
print ("-v : increase verbosity, can be speficied multiple times" )
106
123
print ("-n : do not print multi-line suggestions" )
107
124
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
+ )
108
131
print ("... : arguments passed to git log to retrieve commits to verify" )
109
132
print (" see https://www.git-scm.com/docs/git-log" )
110
133
print (" passing no arguments at all will verify all commits" )
@@ -117,6 +140,10 @@ def show_help():
117
140
args = sys .argv [1 :]
118
141
verbosity = args .count ("-v" )
119
142
suggestions = args .count ("-n" ) == 0
143
+ if "--ignore-rebase" in args :
144
+ args .remove ("--ignore-rebase" )
145
+ ignore_prefixes = ["squash!" , "fixup!" , "amend!" , "WIP" ]
146
+
120
147
if "-h" in args :
121
148
show_help ()
122
149
else :
0 commit comments