-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathcommit_checks.py
More file actions
199 lines (166 loc) · 6.35 KB
/
commit_checks.py
File metadata and controls
199 lines (166 loc) · 6.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"""igcommit - Checks on Git commits
Copyright (c) 2021 InnoGames GmbH
Portions Copyright (c) 2021 Emre Hasegeli
"""
from igcommit.base_check import BaseCheck, Severity
from igcommit.git import Commit
class CommitCheck(BaseCheck):
"""Parent class for all single commit checks"""
commit = None
def prepare(self, obj):
new = super(CommitCheck, self).prepare(obj)
if not new or not isinstance(obj, Commit):
return new
new = new.clone()
new.commit = obj
return new
def __str__(self):
return '{} on {}'.format(type(self).__name__, self.commit)
class CheckCommitMessage(CommitCheck):
def get_problems(self):
for line_id, line in enumerate(self.commit.get_message_lines()):
if line_id == 0:
continue
elif line_id == 1:
if line.strip():
yield Severity.ERROR, 'no single line commit summary'
else:
if line.startswith(' ') or line.startswith('>'):
continue
if line:
for problem in self.get_line_problems(line_id + 1, line):
yield problem
def get_line_problems(self, line_number, line):
if line.rstrip() != line:
line = line.rstrip()
yield (
Severity.ERROR,
'line {}: trailing space'.format(line_number)
)
if line.lstrip() != line:
line = line.lstrip()
yield (
Severity.WARNING,
'line {}: leading space'.format(line_number)
)
if len(line) > 72:
yield (
Severity.WARNING,
'line {}: longer than 72'.format(line_number)
)
class CheckCommitSummary(CommitCheck):
commit_tags = {
'BREAKING',
'BUGFIX',
'CLEANUP',
'FEATURE',
'HOTFIX',
'MESS',
'MIGRATE',
'REFACTORING',
'REVIEW',
'SECURITY',
'STYLE',
'TASK',
'TEMP',
'WIP',
'!!',
}
def get_problems(self):
tags, rest = self.commit.parse_tags()
if rest.startswith('['):
yield Severity.WARNING, 'not terminated commit tags'
if tags:
for problem in self.get_commit_tag_problems(tags, rest):
yield problem
rest = rest[1:]
if rest.startswith('Revert'):
for problem in self.get_revert_commit_problems(rest):
yield problem
return
for problem in self.get_summary_problems(rest):
yield problem
def get_revert_commit_problems(self, rest):
rest = rest[len('Revert'):]
if not rest.startswith(' "') or not rest.endswith('"'):
yield Severity.WARNING, 'ill-formatted revert commit message'
def get_commit_tag_problems(self, tags, rest):
used_tags = []
for tag in tags:
tag_upper = tag.upper()
if tag != tag_upper:
yield (
Severity.ERROR,
'commit tag [{}] not upper-case'.format(tag)
)
if tag_upper not in CheckCommitSummary.commit_tags:
yield (
Severity.WARNING,
'commit tag [{}] not on the list {}'.format(
tag, ', '.join(
'[{}]'.format(t)
for t in CheckCommitSummary.commit_tags
)
)
)
if tag_upper in used_tags:
yield Severity.ERROR, 'duplicate commit tag [{}]'.format(tag)
used_tags.append(tag_upper)
if not rest.startswith(' '):
yield Severity.WARNING, 'commit tags not separated with space'
def get_summary_problems(self, rest):
if not rest:
yield Severity.ERROR, 'no commit summary'
return
rest_len = len(rest)
if rest_len > 72:
if rest.startswith('Merge branch '):
sev = Severity.WARNING
else:
sev = Severity.ERROR
yield sev, 'commit summary longer than 72 characters'
elif rest_len > 50:
yield Severity.WARNING, 'commit summary longer than 50 characters'
if ' ' in rest:
yield Severity.WARNING, 'multiple spaces'
category_index = rest[:24].find(': ')
rest_index = category_index + len(': ')
if category_index >= 0 and len(rest) > rest_index:
for problem in self.get_category_problems(rest[:category_index]):
yield problem
rest = rest[rest_index:]
for problem in self.get_title_problems(rest):
yield problem
def get_category_problems(self, category):
if not category[0].isalpha():
yield Severity.WARNING, 'commit category starts with non-letter'
if category.lower() != category:
yield Severity.WARNING, 'commit category has upper-case letter'
if category.rstrip() != category:
yield Severity.WARNING, 'commit category with trailing space'
def get_title_problems(self, rest):
if not rest:
yield Severity.ERROR, 'no commit title'
return
first_letter = rest[0]
if not first_letter.isalpha():
yield Severity.WARNING, 'commit title start with non-letter'
elif first_letter.upper() != first_letter:
yield Severity.WARNING, 'commit title not capitalized'
if rest.endswith('.'):
yield Severity.WARNING, 'commit title ends with a dot'
first_word = rest.split(' ', 1)[0]
if first_word.endswith('ed'):
yield Severity.WARNING, 'past tense used on commit title'
if first_word.endswith('ing'):
yield Severity.WARNING, 'continuous tense used on commit title'
class CheckChangedFilePaths(CommitCheck):
"""Check file names and directories on a single commit"""
def get_problems(self):
for changed_file in self.commit.get_changed_files():
extension = changed_file.get_extension()
if (
extension in ('pp', 'py', 'sh') and
changed_file.path != changed_file.path.lower()
):
yield Severity.ERROR, '{} has upper case'.format(changed_file)