blob: 915f08544fb5dda1f25703e07c952fef24f52604 [file] [log] [blame]
Anas Nashif3ae52622019-04-06 09:08:09 -04001# SPDX-License-Identifier: Apache-2.0
2
Anas Nashifa35378e2017-04-22 11:59:30 -04003"""
4The classes below are examples of user-defined CommitRules. Commit rules are gitlint rules that
5act on the entire commit at once. Once the rules are discovered, gitlint will automatically take care of applying them
6to the entire commit. This happens exactly once per commit.
7
8A CommitRule contrasts with a LineRule (see examples/my_line_rules.py) in that a commit rule is only applied once on
9an entire commit. This allows commit rules to implement more complex checks that span multiple lines and/or checks
10that should only be done once per gitlint run.
11
12While every LineRule can be implemented as a CommitRule, it's usually easier and more concise to go with a LineRule if
13that fits your needs.
14"""
15
Ulf Magnussond5b0bd12019-03-19 19:37:07 +010016from gitlint.rules import CommitRule, RuleViolation, CommitMessageTitle, LineRule, CommitMessageBody
17from gitlint.options import IntOption, StrOption
Ulf Magnusson7da00532019-03-25 20:23:42 +010018import re
19
Anas Nashif2dd5cef2018-01-10 19:12:00 -050020class BodyMinLineCount(CommitRule):
21 # A rule MUST have a human friendly name
22 name = "body-min-line-count"
23
24 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
25 id = "UC6"
26
27 # A rule MAY have an option_spec if its behavior should be configurable.
28 options_spec = [IntOption('min-line-count', 2, "Minimum body line count excluding Signed-off-by")]
29
30 def validate(self, commit):
31 filtered = [x for x in commit.message.body if not x.lower().startswith("signed-off-by") and x != '']
32 line_count = len(filtered)
33 min_line_count = self.options['min-line-count'].value
Anas Nashifabfed532018-01-11 09:57:03 -050034 if line_count < min_line_count:
Anas Nashif2dd5cef2018-01-10 19:12:00 -050035 message = "Body has no content, should at least have {} line.".format(min_line_count)
36 return [RuleViolation(self.id, message, line_nr=1)]
Anas Nashifa35378e2017-04-22 11:59:30 -040037
38class BodyMaxLineCount(CommitRule):
39 # A rule MUST have a human friendly name
40 name = "body-max-line-count"
41
42 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
43 id = "UC1"
44
45 # A rule MAY have an option_spec if its behavior should be configurable.
46 options_spec = [IntOption('max-line-count', 3, "Maximum body line count")]
47
48 def validate(self, commit):
49 line_count = len(commit.message.body)
50 max_line_count = self.options['max-line-count'].value
51 if line_count > max_line_count:
52 message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count)
53 return [RuleViolation(self.id, message, line_nr=1)]
54
Anas Nashifa35378e2017-04-22 11:59:30 -040055class SignedOffBy(CommitRule):
Paul Sokolovsky26e562c2019-02-18 19:52:59 +030056 """ This rule will enforce that each commit contains a "Signed-off-by" line.
57 We keep things simple here and just check whether the commit body contains a line that starts with "Signed-off-by".
Anas Nashifa35378e2017-04-22 11:59:30 -040058 """
59
60 # A rule MUST have a human friendly name
61 name = "body-requires-signed-off-by"
62
63 # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
64 id = "UC2"
65
66 def validate(self, commit):
Anas Nashif1338f492017-05-05 16:39:34 -040067 flags = re.UNICODE
68 flags |= re.IGNORECASE
Anas Nashifa35378e2017-04-22 11:59:30 -040069 for line in commit.message.body:
70 if line.lower().startswith("signed-off-by"):
Ulf Magnussona449c982019-03-21 21:38:03 +010071 if not re.search(r"(^)Signed-off-by: ([-'\w.]+) ([-'\w.]+) (.*)", line, flags=flags):
Anas Nashif1338f492017-05-05 16:39:34 -040072 return [RuleViolation(self.id, "Signed-off-by: must have a full name", line_nr=1)]
73 else:
74 return
Paul Sokolovsky26e562c2019-02-18 19:52:59 +030075 return [RuleViolation(self.id, "Body does not contain a 'Signed-off-by:' line", line_nr=1)]
Anas Nashif3c27c462017-05-05 19:37:52 -040076
Anas Nashif87766a22017-08-08 08:36:01 -040077class TitleMaxLengthRevert(LineRule):
78 name = "title-max-length-no-revert"
79 id = "UC5"
80 target = CommitMessageTitle
81 options_spec = [IntOption('line-length', 72, "Max line length")]
82 violation_message = "Title exceeds max length ({0}>{1})"
83
84 def validate(self, line, _commit):
85 max_length = self.options['line-length'].value
86 if len(line) > max_length and not line.startswith("Revert"):
87 return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
Anas Nashif3c27c462017-05-05 19:37:52 -040088
89class TitleStartsWithSubsystem(LineRule):
90 name = "title-starts-with-subsystem"
91 id = "UC3"
92 target = CommitMessageTitle
93 options_spec = [StrOption('regex', ".*", "Regex the title should match")]
94
95 def validate(self, title, _commit):
96 regex = self.options['regex'].value
97 pattern = re.compile(regex, re.UNICODE)
Anas Nashif8d84f922018-11-21 23:08:25 -050098 violation_message = "Title does not follow [subsystem]: [subject] (and should not start with literal subsys:)"
Anas Nashif3c27c462017-05-05 19:37:52 -040099 if not pattern.search(title):
100 return [RuleViolation(self.id, violation_message, title)]
Anas Nashifb5200752017-06-06 08:50:11 -0400101
102class MaxLineLengthExceptions(LineRule):
103 name = "max-line-length-with-exceptions"
104 id = "UC4"
105 target = CommitMessageBody
106 options_spec = [IntOption('line-length', 80, "Max line length")]
107 violation_message = "Line exceeds max length ({0}>{1})"
108
109 def validate(self, line, _commit):
110 max_length = self.options['line-length'].value
Ulf Magnussona449c982019-03-21 21:38:03 +0100111 urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
Anas Nashif408a61d2017-08-08 08:07:00 -0400112 if line.startswith('Signed-off-by'):
113 return
114
Ulf Magnusson1c57b222019-09-02 14:38:31 +0200115 if urls:
Anas Nashif408a61d2017-08-08 08:07:00 -0400116 return
117
118 if len(line) > max_length:
Anas Nashifb5200752017-06-06 08:50:11 -0400119 return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]