class TitleMaxLengthCustom(MaxLineLengthCustom): name = "title-max-length-custom" id = "UT1" target = CommitMessageTitle options_spec = [IntOption('line-length', 65, "Max line length"), IntOption('warn-line-length', 50, "Warning about line length")] violation_message = "Title exceeds max length ({0}>{1})" warning_message = "{2} WARNING: Title exceeds max length ({0}>{1}): \"{3}\""
def test_option_equals(self): # 2 options are equal if their name, value and description match option1 = IntOption("test-option", 123, u"Test Dëscription") option2 = IntOption("test-option", 123, u"Test Dëscription") self.assertEqual(option1, option2) # Not equal: name, description, value are different self.assertNotEqual(option1, IntOption("test-option1", 123, u"Test Dëscription")) self.assertNotEqual(option1, IntOption("test-option", 1234, u"Test Dëscription")) self.assertNotEqual(option1, IntOption("test-option", 123, u"Test Dëscription2"))
class ConfigurableCommitRule(CommitRule): """ Rule that tests that we can add configuration to user-defined rules """ name = "configürable" id = "UC4" options_spec = [ IntOption("int-öption", 2, "int-öption description"), StrOption("str-öption", "föo", "int-öption description"), ListOption("list-öption", ["foo", "bar"], "list-öption description") ] def validate(self, _): violations = [ RuleViolation(self.id, f"int-öption: {self.options[u'int-öption'].value}", line_nr=1), RuleViolation(self.id, f"str-öption: {self.options[u'str-öption'].value}", line_nr=1), RuleViolation(self.id, f"list-öption: {self.options[u'list-öption'].value}", line_nr=1), ] return violations
class ReleaseConfigurationRule(ConfigurationRule): """ This rule will modify gitlint's behavior for Release Commits. This example might not be the most realistic for a real-world scenario, but is meant to give an overview of what's possible. """ # A rule MUST have a human friendly name name = "release-configuration-rule" # A rule MUST have a *unique* id, we recommend starting with UCR # (for User-defined Configuration-Rule), but this can really be anything. id = "UCR1" # A rule MAY have an option_spec if its behavior should be configurable. options_spec = [ IntOption('custom-verbosity', 2, "Gitlint verbosity for release commits") ] def apply(self, config, commit): self.log.debug( "ReleaseConfigurationRule: This will be visible when running `gitlint --debug`" ) # If the commit title starts with 'Release', we want to modify # how all subsequent rules interpret that commit if commit.message.title.startswith("Release"): # If your Release commit messages are auto-generated, the # body might contain trailing whitespace. Let's ignore that config.ignore.append("body-trailing-whitespace") # Similarly, the body lines might exceed 80 chars, # let's set gitlint's limit to 200 # To set rule options use: # config.set_rule_option(<rule-name>, <rule-option>, <value>) config.set_rule_option("body-max-line-length", "line-length", 200) # For kicks, let's set gitlint's verbosity to 2 # To set general options use # config.set_general_option(<general-option>, <value>) config.set_general_option("verbosity", 2) # Wwe can also use custom options to make this configurable config.set_general_option("verbosity", self.options['custom-verbosity'].value) # Strip any lines starting with $ from the commit message # (this only affects how gitlint sees your commit message, it does # NOT modify your actual commit in git) commit.message.body = [ line for line in commit.message.body if not line.startswith("$") ] # You can add any extra properties you want to the commit object, these will be available later on # in all rules. commit.my_property = "This is my property"
class MaxLineLengthCustom(LineRule): name = "max-line-length-custom" id = "UR1" target = CommitMessageBody options_spec = [IntOption('line-length', 80, "Max line length"), IntOption('warn-line-length', 78, "Warning about line length")] warning_message = "{2} WARNING: Line exceeds max length ({0}>{1}): \"{3}\"" violation_message = "Line exceeds max length ({0}>{1})" def validate(self, line, _commit): max_length = self.options['line-length'].value warn_length = self.options['warn-line-length'].value if len(line) > max_length: return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)] elif len(line) > warn_length: print self.warning_message.format(len(line), warn_length, self.id, line)
class BodyMaxLineCount(CommitRule): name = "body-max-line-count" id = "UC1" options_spec = [ IntOption('body-max-line-count', 3, "Maximum body line count") ] def validate(self, commit): if len(commit.message.body) > self.options['body-max-line-count']: return [RuleViolation(self.id, "Body contains too many lines")]
class MaxLineLength(LineRule): name = "max-line-length" id = "R1" options_spec = [IntOption('line-length', 80, "Max line length")] violation_message = "Line exceeds max length ({0}>{1})" def validate(self, line, _commit): max_length = self.options['line-length'].value if len(line) > max_length: return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
class MyUserCommitRule(CommitRule): name = "my-üser-commit-rule" id = "UC1" options_spec = [IntOption('violation-count', 1, "Number of violåtions to return")] def validate(self, _commit): violations = [] for i in range(1, self.options['violation-count'].value + 1): violations.append(RuleViolation(self.id, "Commit violåtion %d" % i, "Contënt %d" % i, i)) return violations
class MaxLineLengthExceptions(LineRule): name = "max-line-length-with-exceptions" id = "UC4" target = CommitMessageBody options_spec = [IntOption('line-length', 80, "Max line length")] violation_message = "Line exceeds max length ({0}>{1})" def validate(self, line, _commit): max_length = self.options['line-length'].value if len(line) > max_length and not line.startswith('Signed-off-by'): return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
class TitleMaxLengthRevert(LineRule): name = "title-max-length-no-revert" id = "UC5" target = CommitMessageTitle options_spec = [IntOption('line-length', 72, "Max line length")] violation_message = "Title exceeds max length ({0}>{1})" def validate(self, line, _commit): max_length = self.options['line-length'].value if len(line) > max_length and not line.startswith("Revert"): return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
class BodyMinLength(CommitRule): name = "body-min-length" id = "B5" options_spec = [IntOption('min-length', 20, "Minimum body length")] def validate(self, commit): min_length = self.options['min-length'].value body_message_no_newline = "".join([line for line in commit.message.body if line is not None]) actual_length = len(body_message_no_newline) if actual_length > 0 and actual_length < min_length: violation_message = "Body message is too short ({0}<{1})".format(actual_length, min_length) return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)]
class TitleMinLength(LineRule): name = "title-min-length" id = "T8" target = CommitMessageTitle options_spec = [IntOption('min-length', 5, "Minimum required title length")] def validate(self, title, _commit): min_length = self.options['min-length'].value actual_length = len(title) if actual_length < min_length: violation_message = f"Title is too short ({actual_length}<{min_length})" return [RuleViolation(self.id, violation_message, title, 1)]
class BodyMinLength(CommitRule): name = "body-min-length" id = "B5" options_spec = [IntOption('min-length', 20, "Minimum body length")] def validate(self, commit): min_length = self.options['min-length'].value lines = commit.message.body if len(lines) == 2: actual_length = len(lines[1]) if lines[0] == "" and actual_length <= min_length: violation_message = "Body message is too short ({0}<{1})".format( actual_length, min_length) return [RuleViolation(self.id, violation_message, lines[1], 3)]
class BodyMinLength(MultiLineRule, CommitMessageBodyRule): name = "body-min-length" id = "B5" options_spec = [IntOption('min-length', 20, "Minimum body length")] def validate(self, gitcontext): min_length = self.options['min-length'].value lines = gitcontext.commit_msg.body if len(lines) == 3: actual_length = len(lines[1]) if lines[0] == "" and actual_length <= min_length: violation_message = "Body message is too short ({}<{})".format( actual_length, min_length) return [RuleViolation(self.id, violation_message, lines[1], 3)]
class BodyMaxLineCount(CommitRule): # A rule MUST have a human friendly name name = "body-max-line-count" # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). id = "UC1" # A rule MAY have an option_spec if its behavior should be configurable. options_spec = [IntOption('max-line-count', 3, "Maximum body line count")] def validate(self, commit): line_count = len(commit.message.body) max_line_count = self.options['max-line-count'].value if line_count > max_line_count: message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count) return [RuleViolation(self.id, message, line_nr=1)]
class BodyMinLineCount(CommitRule): # A rule MUST have a human friendly name name = "body-min-line-count" # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). id = "UC6" # A rule MAY have an option_spec if its behavior should be configurable. options_spec = [IntOption('min-line-count', 2, "Minimum body line count excluding Signed-off-by")] def validate(self, commit): filtered = [x for x in commit.message.body if not x.lower().startswith("signed-off-by") and x != ''] line_count = len(filtered) min_line_count = self.options['min-line-count'].value if line_count < min_line_count: message = "Body has no content, should at least have {} line.".format(min_line_count) return [RuleViolation(self.id, message, line_nr=1)]
class ConfigurableCommitRule(CommitRule): """ Rule that tests that we can add configuration to user-defined rules """ name = u"configürable" id = "UC4" options_spec = [IntOption(u"int-öption", 2, u"int-öption description"), StrOption(u"str-öption", u"föo", u"int-öption description"), ListOption(u"list-öption", [u"foo", u"bar"], u"list-öption description")] def validate(self, _): violations = [ RuleViolation(self.id, u"int-öption: {0}".format(self.options[u'int-öption'].value), line_nr=1), RuleViolation(self.id, u"str-öption: {0}".format(self.options[u'str-öption'].value), line_nr=1), RuleViolation(self.id, u"list-öption: {0}".format(sstr(self.options[u'list-öption'].value)), line_nr=1), ] return violations
class MaxLineLengthExceptions(LineRule): name = "max-line-length-with-exceptions" id = "UC4" target = CommitMessageBody options_spec = [IntOption('line-length', 80, "Max line length")] violation_message = "Line exceeds max length ({0}>{1})" def validate(self, line, _commit): max_length = self.options['line-length'].value urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', line) if line.startswith('Signed-off-by'): return if urls: return if len(line) > max_length: return [RuleViolation(self.id, self.violation_message.format(len(line), max_length), line)]
def test_extra_path(self): config = LintConfig() config.set_general_option("extra-path", self.get_user_rules_path()) self.assertEqual(config.extra_path, self.get_user_rules_path()) actual_rule = config.get_rule('UC1') self.assertTrue(actual_rule.user_defined) self.assertEqual(ustr(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>") self.assertEqual(actual_rule.id, 'UC1') self.assertEqual(actual_rule.name, u'my-üser-commit-rule') self.assertEqual(actual_rule.target, None) expected_rule_option = IntOption('violation-count', 1, u"Number of violåtions to return") self.assertListEqual(actual_rule.options_spec, [expected_rule_option]) self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option}) # reset value (this is a different code path) config.set_general_option("extra-path", self.SAMPLES_DIR) self.assertEqual(config.extra_path, self.SAMPLES_DIR) self.assertIsNone(config.get_rule("UC1"))
class BodyMaxLineCount(CommitRule): # A rule MUST have a human friendly name name = "body-max-line-count" # A rule MUST have a *unique* id, we recommend starting with UC (for User-defined Commit-rule). id = "UC1" # A rule MAY have an option_spec if its behavior should be configurable. options_spec = [IntOption('max-line-count', 3, "Maximum body line count")] def validate(self, commit): self.log.debug( "BodyMaxLineCount: This will be visible when running `gitlint --debug`" ) line_count = len(commit.message.body) max_line_count = self.options['max-line-count'].value if line_count > max_line_count: message = f"Body contains too many lines ({line_count} > {max_line_count})" return [RuleViolation(self.id, message, line_nr=1)]
def test_option_equality(self): options = { IntOption: 123, StrOption: u"foöbar", BoolOption: False, ListOption: ["a", "b"], PathOption: ".", RegexOption: u"^foöbar(.*)" } for clazz, val in options.items(): # 2 options are equal if their name, value and description match option1 = clazz(u"test-öption", val, u"Test Dëscription") option2 = clazz(u"test-öption", val, u"Test Dëscription") self.assertEqual(option1, option2) # Not equal: class, name, description, value are different self.assertNotEqual( option1, IntOption(u"tëst-option1", 123, u"Test Dëscription")) self.assertNotEqual( option1, StrOption(u"tëst-option1", u"åbc", u"Test Dëscription")) self.assertNotEqual( option1, StrOption(u"tëst-option", u"åbcd", u"Test Dëscription")) self.assertNotEqual( option1, StrOption(u"tëst-option", u"åbc", u"Test Dëscription2"))
class TitleMaxLength(MaxLineLength): name = "title-max-length" id = "T1" target = CommitMessageTitle options_spec = [IntOption('line-length', 72, "Max line length")] violation_message = "Title exceeds max length ({0}>{1})"
def test_int_option(self): # normal behavior option = IntOption("test-name", 123, "Test Description") option.set(456) self.assertEqual(option.value, 456) # error on negative int when not allowed expected_error = "Option 'test-name' must be a positive integer \(current value: '-123'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set(-123) # error on non-int value expected_error = "Option 'test-name' must be a positive integer \(current value: 'foo'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set("foo") # no error on negative value when allowed and negative int is passed option = IntOption("test-name", 123, "Test Description", allow_negative=True) option.set(-456) self.assertEqual(option.value, -456) # error on non-int value when negative int is allowed expected_error = "Option 'test-name' must be an integer \(current value: 'foo'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set("foo")
def test_int_option(self): # normal behavior option = IntOption("test-name", 123, "Test Description") self.assertEqual(option.value, 123) self.assertEqual(option.name, "test-name") self.assertEqual(option.description, "Test Description") # re-set value option.set(456) self.assertEqual(option.value, 456) # error on negative int when not allowed expected_error = r"Option 'test-name' must be a positive integer \(current value: '-123'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set(-123) # error on non-int value expected_error = r"Option 'test-name' must be a positive integer \(current value: 'foo'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set("foo") # no error on negative value when allowed and negative int is passed option = IntOption("test-name", 123, "Test Description", allow_negative=True) option.set(-456) self.assertEqual(option.value, -456) # error on non-int value when negative int is allowed expected_error = r"Option 'test-name' must be an integer \(current value: 'foo'\)" with self.assertRaisesRegexp(RuleOptionError, expected_error): option.set("foo")
class BodyMaxLineLengthCustom(MaxLineLengthCustom): name = "body-max-line-length-custom" id = "UB1" target = CommitMessageBody options_spec = [IntOption('line-length', 80, "Max line length"), IntOption('warn-line-length', 78, "Warning about line length")]