class BodyRegexMatches(CommitRule): name = "body-match-regex" id = "B8" options_spec = [RegexOption('regex', None, "Regex the body should match")] def validate(self, commit): # If no regex is specified, immediately return if not self.options['regex'].value: return # We intentionally ignore the first line in the body as that's the empty line after the title, # which most users are not going to expect to be part of the body when matching a regex. # If this causes contention, we can always introduce an option to change the behavior in a backward- # compatible way. body_lines = commit.message.body[1:] if len( commit.message.body) > 1 else [] # Similarly, the last line is often empty, this has to do with how git returns commit messages # User's won't expect this, so prune it off by default if body_lines and body_lines[-1] == "": body_lines.pop() full_body = "\n".join(body_lines) if not self.options['regex'].value.search(full_body): violation_msg = f"Body does not match regex ({self.options['regex'].value.pattern})" return [ RuleViolation(self.id, violation_msg, None, len(commit.message.body) + 1) ]
class IgnoreByBody(ConfigurationRule): name = "ignore-by-body" id = "I2" options_spec = [ RegexOption( 'regex', None, "Regex matching lines of the body of commits this rule should apply to" ), StrOption('ignore', "all", "Comma-separated list of rules to ignore") ] def apply(self, config, commit): # If no regex is specified, immediately return if not self.options['regex'].value: return for line in commit.message.body: if self.options['regex'].value.match(line): config.ignore = self.options['ignore'].value message = f"Commit message line '{line}' matches the regex '{self.options['regex'].value.pattern}'," + \ f" ignoring rules: {self.options['ignore'].value}" self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message) # No need to check other lines if we found a match return
class IgnoreByTitle(ConfigurationRule): name = "ignore-by-title" id = "I1" options_spec = [ RegexOption( 'regex', None, "Regex matching the titles of commits this rule should apply to"), StrOption('ignore', "all", "Comma-separated list of rules to ignore") ] def apply(self, config, commit): # If no regex is specified, immediately return if not self.options['regex'].value: return if self.options['regex'].value.match(commit.message.title): config.ignore = self.options['ignore'].value message = u"Commit title '{0}' matches the regex '{1}', ignoring rules: {2}" message = message.format(commit.message.title, self.options['regex'].value.pattern, self.options['ignore'].value) self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
class AuthorValidEmail(CommitRule): name = "author-valid-email" id = "M1" options_spec = [RegexOption('regex', r"[^@ ]+@[^@ ]+\.[^@ ]+", "Regex that author email address should match")] def validate(self, commit): # If no regex is specified, immediately return if not self.options['regex'].value: return if commit.author_email and not self.options['regex'].value.match(commit.author_email): return [RuleViolation(self.id, "Author email for commit is invalid", commit.author_email)]
class TitleRegexMatches(LineRule): name = "title-match-regex" id = "T7" target = CommitMessageTitle options_spec = [RegexOption('regex', None, "Regex the title should match")] def validate(self, title, _commit): # If no regex is specified, immediately return if not self.options['regex'].value: return if not self.options['regex'].value.search(title): violation_msg = f"Title does not match regex ({self.options['regex'].value.pattern})" return [RuleViolation(self.id, violation_msg, title)]
class IgnoreBodyLines(ConfigurationRule): name = "ignore-body-lines" id = "I3" options_spec = [RegexOption('regex', None, "Regex matching lines of the body that should be ignored")] def apply(self, _, commit): # If no regex is specified, immediately return if not self.options['regex'].value: return new_body = [] for line in commit.message.body: if self.options['regex'].value.match(line): debug_msg = "Ignoring line '%s' because it matches '%s'" self.log.debug(debug_msg, line, self.options['regex'].value.pattern) else: new_body.append(line) commit.message.body = new_body commit.message.full = "\n".join([commit.message.title] + new_body)
def test_regex_option(self): # normal behavior option = RegexOption(u"tëst-regex", u"^myrëgex(.*)foo$", u"Tëst Regex Description") self.assertEqual(option.name, u"tëst-regex") self.assertEqual(option.description, u"Tëst Regex Description") self.assertEqual(option.value, re.compile(u"^myrëgex(.*)foo$", re.UNICODE)) # re-set value option.set(u"[0-9]föbar.*") self.assertEqual(option.value, re.compile(u"[0-9]föbar.*", re.UNICODE)) # set None option.set(None) self.assertIsNone(option.value) # error on invalid regex incorrect_values = [u"foo(", 123, -1] for value in incorrect_values: with self.assertRaisesRegex(RuleOptionError, u"Invalid regular expression"): option.set(value)