zephyr_commit_rules.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # SPDX-License-Identifier: Apache-2.0
  2. """
  3. The classes below are examples of user-defined CommitRules. Commit rules are gitlint rules that
  4. act on the entire commit at once. Once the rules are discovered, gitlint will automatically take care of applying them
  5. to the entire commit. This happens exactly once per commit.
  6. A CommitRule contrasts with a LineRule (see examples/my_line_rules.py) in that a commit rule is only applied once on
  7. an entire commit. This allows commit rules to implement more complex checks that span multiple lines and/or checks
  8. that should only be done once per gitlint run.
  9. While every LineRule can be implemented as a CommitRule, it's usually easier and more concise to go with a LineRule if
  10. that fits your needs.
  11. """
  12. from gitlint.rules import CommitRule, RuleViolation, CommitMessageTitle, LineRule, CommitMessageBody
  13. from gitlint.options import IntOption, StrOption
  14. import re
  15. class BodyMinLineCount(CommitRule):
  16. # A rule MUST have a human friendly name
  17. name = "body-min-line-count"
  18. # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
  19. id = "UC6"
  20. # A rule MAY have an option_spec if its behavior should be configurable.
  21. options_spec = [IntOption('min-line-count', 2, "Minimum body line count excluding Signed-off-by")]
  22. def validate(self, commit):
  23. filtered = [x for x in commit.message.body if not x.lower().startswith("signed-off-by") and x != '']
  24. line_count = len(filtered)
  25. min_line_count = self.options['min-line-count'].value
  26. if line_count < min_line_count:
  27. message = "Body has no content, should at least have {} line.".format(min_line_count)
  28. return [RuleViolation(self.id, message, line_nr=1)]
  29. class BodyMaxLineCount(CommitRule):
  30. # A rule MUST have a human friendly name
  31. name = "body-max-line-count"
  32. # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
  33. id = "UC1"
  34. # A rule MAY have an option_spec if its behavior should be configurable.
  35. options_spec = [IntOption('max-line-count', 3, "Maximum body line count")]
  36. def validate(self, commit):
  37. line_count = len(commit.message.body)
  38. max_line_count = self.options['max-line-count'].value
  39. if line_count > max_line_count:
  40. message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count)
  41. return [RuleViolation(self.id, message, line_nr=1)]
  42. class SignedOffBy(CommitRule):
  43. """ This rule will enforce that each commit contains a "Signed-off-by" line.
  44. We keep things simple here and just check whether the commit body contains a line that starts with "Signed-off-by".
  45. """
  46. # A rule MUST have a human friendly name
  47. name = "body-requires-signed-off-by"
  48. # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule).
  49. id = "UC2"
  50. def validate(self, commit):
  51. flags = re.UNICODE
  52. flags |= re.IGNORECASE
  53. for line in commit.message.body:
  54. if line.lower().startswith("signed-off-by"):
  55. if not re.search(r"(^)Signed-off-by: ([-'\w.]+) ([-'\w.]+) (.*)", line, flags=flags):
  56. return [RuleViolation(self.id, "Signed-off-by: must have a full name", line_nr=1)]
  57. else:
  58. return
  59. return [RuleViolation(self.id, "Body does not contain a 'Signed-off-by:' line", line_nr=1)]
  60. class TitleMaxLengthRevert(LineRule):
  61. name = "title-max-length-no-revert"
  62. id = "UC5"
  63. target = CommitMessageTitle
  64. options_spec = [IntOption('line-length', 72, "Max line length")]
  65. violation_message = "Title exceeds max length ({0}>{1})"
  66. def validate(self, line, _commit):
  67. max_length = self.options['line-length'].value
  68. if len(line) > max_length and not line.startswith("Revert"):
  69. return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
  70. class TitleStartsWithSubsystem(LineRule):
  71. name = "title-starts-with-subsystem"
  72. id = "UC3"
  73. target = CommitMessageTitle
  74. options_spec = [StrOption('regex', ".*", "Regex the title should match")]
  75. def validate(self, title, _commit):
  76. regex = self.options['regex'].value
  77. pattern = re.compile(regex, re.UNICODE)
  78. violation_message = "Title does not follow [subsystem]: [subject] (and should not start with literal subsys:)"
  79. if not pattern.search(title):
  80. return [RuleViolation(self.id, violation_message, title)]
  81. class MaxLineLengthExceptions(LineRule):
  82. name = "max-line-length-with-exceptions"
  83. id = "UC4"
  84. target = CommitMessageBody
  85. options_spec = [IntOption('line-length', 80, "Max line length")]
  86. violation_message = "Line exceeds max length ({0}>{1})"
  87. def validate(self, line, _commit):
  88. max_length = self.options['line-length'].value
  89. urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line)
  90. if line.startswith('Signed-off-by'):
  91. return
  92. if urls:
  93. return
  94. if len(line) > max_length:
  95. return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]