blob: 3c8c5b47634a272b6f8a24ab0dff34e66dca8069 [file] [log] [blame]
Anas Nashifb5200752017-06-06 08:50:11 -04001from gitlint.rules import CommitRule, RuleViolation, TitleRegexMatches, CommitMessageTitle, LineRule, CommitMessageBody
Anas Nashif3c27c462017-05-05 19:37:52 -04002from gitlint.options import IntOption, BoolOption, StrOption, ListOption
Anas Nashif1338f492017-05-05 16:39:34 -04003import re
Anas Nashifa35378e2017-04-22 11:59:30 -04004
5"""
6The classes below are examples of user-defined CommitRules. Commit rules are gitlint rules that
7act on the entire commit at once. Once the rules are discovered, gitlint will automatically take care of applying them
8to the entire commit. This happens exactly once per commit.
9
10A CommitRule contrasts with a LineRule (see examples/my_line_rules.py) in that a commit rule is only applied once on
11an entire commit. This allows commit rules to implement more complex checks that span multiple lines and/or checks
12that should only be done once per gitlint run.
13
14While every LineRule can be implemented as a CommitRule, it's usually easier and more concise to go with a LineRule if
15that fits your needs.
16"""
17
Anas Nashif2dd5cef2018-01-10 19:12:00 -050018class BodyMinLineCount(CommitRule):
19 # A rule MUST have a human friendly name
20 name = "body-min-line-count"
21
22 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
23 id = "UC6"
24
25 # A rule MAY have an option_spec if its behavior should be configurable.
26 options_spec = [IntOption('min-line-count', 2, "Minimum body line count excluding Signed-off-by")]
27
28 def validate(self, commit):
29 filtered = [x for x in commit.message.body if not x.lower().startswith("signed-off-by") and x != '']
30 line_count = len(filtered)
31 min_line_count = self.options['min-line-count'].value
Anas Nashifabfed532018-01-11 09:57:03 -050032 if line_count < min_line_count:
Anas Nashif2dd5cef2018-01-10 19:12:00 -050033 message = "Body has no content, should at least have {} line.".format(min_line_count)
34 return [RuleViolation(self.id, message, line_nr=1)]
Anas Nashifa35378e2017-04-22 11:59:30 -040035
36class BodyMaxLineCount(CommitRule):
37 # A rule MUST have a human friendly name
38 name = "body-max-line-count"
39
40 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
41 id = "UC1"
42
43 # A rule MAY have an option_spec if its behavior should be configurable.
44 options_spec = [IntOption('max-line-count', 3, "Maximum body line count")]
45
46 def validate(self, commit):
47 line_count = len(commit.message.body)
48 max_line_count = self.options['max-line-count'].value
49 if line_count > max_line_count:
50 message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count)
51 return [RuleViolation(self.id, message, line_nr=1)]
52
Anas Nashifa35378e2017-04-22 11:59:30 -040053class SignedOffBy(CommitRule):
54 """ This rule will enforce that each commit contains a "Signed-Off-By" line.
55 We keep things simple here and just check whether the commit body contains a line that starts with "Signed-Off-By".
56 """
57
58 # A rule MUST have a human friendly name
59 name = "body-requires-signed-off-by"
60
61 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
62 id = "UC2"
63
64 def validate(self, commit):
Anas Nashif1338f492017-05-05 16:39:34 -040065 flags = re.UNICODE
66 flags |= re.IGNORECASE
Anas Nashifa35378e2017-04-22 11:59:30 -040067 for line in commit.message.body:
68 if line.lower().startswith("signed-off-by"):
Anas Nashif1338f492017-05-05 16:39:34 -040069 if not re.search('(^)Signed-off-by: ([-\w.]+) ([-\w.]+) (.*)', line, flags=flags):
70 return [RuleViolation(self.id, "Signed-off-by: must have a full name", line_nr=1)]
71 else:
72 return
Anas Nashifa35378e2017-04-22 11:59:30 -040073 return [RuleViolation(self.id, "Body does not contain a 'Signed-Off-By' line", line_nr=1)]
Anas Nashif3c27c462017-05-05 19:37:52 -040074
Anas Nashif87766a22017-08-08 08:36:01 -040075class TitleMaxLengthRevert(LineRule):
76 name = "title-max-length-no-revert"
77 id = "UC5"
78 target = CommitMessageTitle
79 options_spec = [IntOption('line-length', 72, "Max line length")]
80 violation_message = "Title exceeds max length ({0}>{1})"
81
82 def validate(self, line, _commit):
83 max_length = self.options['line-length'].value
84 if len(line) > max_length and not line.startswith("Revert"):
85 return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
Anas Nashif3c27c462017-05-05 19:37:52 -040086
87class TitleStartsWithSubsystem(LineRule):
88 name = "title-starts-with-subsystem"
89 id = "UC3"
90 target = CommitMessageTitle
91 options_spec = [StrOption('regex', ".*", "Regex the title should match")]
92
93 def validate(self, title, _commit):
94 regex = self.options['regex'].value
95 pattern = re.compile(regex, re.UNICODE)
Anas Nashif789d51c2017-10-27 14:29:59 -040096 violation_message = "Title does not follow [subsystem]: [subject]"
Anas Nashif3c27c462017-05-05 19:37:52 -040097 if not pattern.search(title):
98 return [RuleViolation(self.id, violation_message, title)]
Anas Nashifb5200752017-06-06 08:50:11 -040099
100class MaxLineLengthExceptions(LineRule):
101 name = "max-line-length-with-exceptions"
102 id = "UC4"
103 target = CommitMessageBody
104 options_spec = [IntOption('line-length', 80, "Max line length")]
105 violation_message = "Line exceeds max length ({0}>{1})"
106
107 def validate(self, line, _commit):
108 max_length = self.options['line-length'].value
Anas Nashif408a61d2017-08-08 08:07:00 -0400109 urls = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
110 if line.startswith('Signed-off-by'):
111 return
112
113 if len(urls) > 0:
114 return
115
116 if len(line) > max_length:
Anas Nashifb5200752017-06-06 08:50:11 -0400117 return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]