def validate(self, commit: Any) -> Optional[List[RuleViolation]]:
        title_components = commit.message.title.split(": ")

        violations = []

        # Return just this violation, since latter checks assume an area
        error = ("Title should start with at least one area, "
                 "followed by a colon and space")
        if len(title_components) < 2:
            return [RuleViolation(self.id, error, line_nr=1)]

        exclusions = self.options['exclusions'].value
        exclusions_text = ", or ".join(exclusions)
        if exclusions_text:
            exclusions_text = " (or {})".format(exclusions_text)
        error = ("Areas at start of title should be lower case{}, "
                 "followed by ': '".format(exclusions_text))

        def deny_capital_text(text: str) -> bool:
            if text in exclusions:
                return False
            if not text.islower():
                return True
            return False

        for area in title_components[:-1]:
            if (any(deny_capital_text(word) for word in area.split('/'))
                    or ' ' in area):
                violations += [RuleViolation(self.id, error, line_nr=1)]

        error = "Summary of change, after area(s), should be capitalized"
        if not title_components[-1][0].isupper():
            violations += [RuleViolation(self.id, error, line_nr=1)]

        return violations
Beispiel #2
0
    def test_named_rules(self):
        """ Test that when named rules are present, both them and the original (non-named) rules executed """

        lint_config = LintConfig()
        for rule_name in [u"my-ïd", u"another-rule-ïd"]:
            rule_id = TitleMustNotContainWord.id + ":" + rule_name
            lint_config.rules.add_rule(TitleMustNotContainWord, rule_id)
            lint_config.set_rule_option(rule_id, "words", [u"Föo"])
            linter = GitLinter(lint_config)

        violations = [
            RuleViolation("T5",
                          u"Title contains the word 'WIP' (case-insensitive)",
                          u"WIP: Föo bar", 1),
            RuleViolation(u"T5:another-rule-ïd",
                          u"Title contains the word 'Föo' (case-insensitive)",
                          u"WIP: Föo bar", 1),
            RuleViolation(u"T5:my-ïd",
                          u"Title contains the word 'Föo' (case-insensitive)",
                          u"WIP: Föo bar", 1)
        ]
        self.assertListEqual(
            violations,
            linter.lint(
                self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")))
Beispiel #3
0
    def test_ignore_named_rules(self):
        """ Test that named rules can be ignored """

        # Add named rule to lint config
        config_builder = LintConfigBuilder()
        rule_id = TitleMustNotContainWord.id + u":my-ïd"
        config_builder.set_option(rule_id, "words", [u"Föo"])
        lint_config = config_builder.build()
        linter = GitLinter(lint_config)
        commit = self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")

        # By default, we expect both the violations of the regular rule as well as the named rule to show up
        violations = [
            RuleViolation("T5",
                          u"Title contains the word 'WIP' (case-insensitive)",
                          u"WIP: Föo bar", 1),
            RuleViolation(u"T5:my-ïd",
                          u"Title contains the word 'Föo' (case-insensitive)",
                          u"WIP: Föo bar", 1)
        ]
        self.assertListEqual(violations, linter.lint(commit))

        # ignore regular rule: only named rule violations show up
        lint_config.ignore = ["T5"]
        self.assertListEqual(violations[1:], linter.lint(commit))

        # ignore named rule by id: only regular rule violations show up
        lint_config.ignore = [rule_id]
        self.assertListEqual(violations[:-1], linter.lint(commit))

        # ignore named rule by name: only regular rule violations show up
        lint_config.ignore = [TitleMustNotContainWord.name + u":my-ïd"]
        self.assertListEqual(violations[:-1], linter.lint(commit))
Beispiel #4
0
    def test_lint_sample3(self):
        linter = GitLinter(LintConfig())
        gitcontext = self.gitcontext(self.get_sample("commit_message/sample3"))
        violations = linter.lint(gitcontext.commits[-1])

        title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
        expected = [
            RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
            RuleViolation("T3", "Title has trailing punctuation (.)", title,
                          1),
            RuleViolation("T4", "Title contains hard tab characters (\\t)",
                          title, 1),
            RuleViolation("T5",
                          "Title contains the word 'WIP' (case-insensitive)",
                          title, 1),
            RuleViolation("T6", "Title has leading whitespace", title, 1),
            RuleViolation("B4", "Second line is not empty",
                          "This line should be empty", 2),
            RuleViolation(
                "B1", "Line exceeds max length (101>80)",
                u"This is the first line is meånt to test a line that exceeds the maximum line "
                + "length of 80 characters.", 3),
            RuleViolation("B2", "Line has trailing whitespace",
                          "This line has a trailing space. ", 4),
            RuleViolation("B2", "Line has trailing whitespace",
                          u"This line has a tråiling tab.\t", 5),
            RuleViolation("B3", "Line contains hard tab characters (\\t)",
                          u"This line has a tråiling tab.\t", 5)
        ]

        self.assertListEqual(violations, expected)
Beispiel #5
0
    def test_print_violations(self):
        violations = [
            RuleViolation("RULE_ID_1", u"Error Messåge 1",
                          "Violating Content 1", None),
            RuleViolation("RULE_ID_2", "Error Message 2",
                          u"Violåting Content 2", 2)
        ]
        linter = GitLinter(LintConfig())

        # test output with increasing verbosity
        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            linter.config.verbosity = 0
            linter.print_violations(violations)
            self.assertEqual("", stderr.getvalue())

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            linter.config.verbosity = 1
            linter.print_violations(violations)
            expected = u"-: RULE_ID_1\n2: RULE_ID_2\n"
            self.assertEqual(expected, stderr.getvalue())

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            linter.config.verbosity = 2
            linter.print_violations(violations)
            expected = u"-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
            self.assertEqual(expected, stderr.getvalue())

        with patch('gitlint.display.stderr', new=StringIO()) as stderr:
            linter.config.verbosity = 3
            linter.print_violations(violations)
            expected = u"-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
                       u"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
            self.assertEqual(expected, stderr.getvalue())
Beispiel #6
0
    def validate(self, commit):
        violations = [
            RuleViolation(self.id, u"GitCommit.branches: {0}".format(sstr(commit.branches)), line_nr=1),
            RuleViolation(self.id, u"GitCommit.custom_prop: {0}".format(commit.custom_prop), line_nr=1),
        ]

        return violations
Beispiel #7
0
    def test_min_line_length(self):
        rule = TitleMinLength()

        # assert no error
        violation = rule.validate("å" * 72, None)
        self.assertIsNone(violation)

        # assert error on line length < 5
        expected_violation = RuleViolation("T8", "Title is too short (4<5)",
                                           "å" * 4, 1)
        violations = rule.validate("å" * 4, None)
        self.assertListEqual(violations, [expected_violation])

        # set line length to 3, and check no violation on length 4
        rule = TitleMinLength({'min-length': 3})
        violations = rule.validate("å" * 4, None)
        self.assertIsNone(violations)

        # assert no violations on length 3 (this asserts we've implemented a *strict* less than)
        rule = TitleMinLength({'min-length': 3})
        violations = rule.validate("å" * 3, None)
        self.assertIsNone(violations)

        # assert raise on 2
        expected_violation = RuleViolation("T8", "Title is too short (2<3)",
                                           "å" * 2, 1)
        violations = rule.validate("å" * 2, None)
        self.assertListEqual(violations, [expected_violation])

        # assert raise on empty title
        expected_violation = RuleViolation("T8", "Title is too short (0<3)",
                                           "", 1)
        violations = rule.validate("", None)
        self.assertListEqual(violations, [expected_violation])
Beispiel #8
0
    def validate(self, commit):
        violations = [
            RuleViolation(self.id, u"GitContext.current_branch: {0}".format(commit.context.current_branch), line_nr=1),
            RuleViolation(self.id, u"GitContext.commentchar: {0}".format(commit.context.commentchar), line_nr=1)
        ]

        return violations
    def test_leading_whitespace(self):
        rule = TitleLeadingWhitespace()

        # assert no error
        violations = rule.validate("a", None)
        self.assertIsNone(violations)

        # leading space
        expected_violation = RuleViolation("T6",
                                           "Title has leading whitespace",
                                           " a")
        violations = rule.validate(" a", None)
        self.assertListEqual(violations, [expected_violation])

        # leading tab
        expected_violation = RuleViolation("T6",
                                           "Title has leading whitespace",
                                           "\ta")
        violations = rule.validate("\ta", None)
        self.assertListEqual(violations, [expected_violation])

        # unicode test
        expected_violation = RuleViolation("T6",
                                           "Title has leading whitespace",
                                           u" ☺")
        violations = rule.validate(u" ☺", None)
        self.assertListEqual(violations, [expected_violation])
Beispiel #10
0
    def test_lint_regex_rules(self):
        """ Additional test for title-match-regex, body-match-regex """
        commit = self.gitcommit(
            self.get_sample("commit_message/no-violations"))
        lintconfig = LintConfig()
        linter = GitLinter(lintconfig)
        violations = linter.lint(commit)
        # No violations by default
        self.assertListEqual(violations, [])

        # Matching regexes shouldn't be a problem
        rule_regexes = [("title-match-regex", u"Tïtle$"),
                        ("body-match-regex", u"Sïgned-Off-By: (.*)$")]
        for rule_regex in rule_regexes:
            lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1])
            violations = linter.lint(commit)
            self.assertListEqual(violations, [])

        # Non-matching regexes should return violations
        rule_regexes = [("title-match-regex", ), ("body-match-regex", )]
        lintconfig.set_rule_option("title-match-regex", "regex", u"^Tïtle")
        lintconfig.set_rule_option("body-match-regex", "regex",
                                   u"Sügned-Off-By: (.*)$")
        expected_violations = [
            RuleViolation("T7", u"Title does not match regex (^Tïtle)",
                          u"Normal Commit Tïtle", 1),
            RuleViolation("B8",
                          u"Body does not match regex (Sügned-Off-By: (.*)$)",
                          None, 6)
        ]
        violations = linter.lint(commit)
        self.assertListEqual(violations, expected_violations)
Beispiel #11
0
    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
    def test_conventional_commits(self):
        rule = ConventionalCommit()

        # No violations when using a correct type and format
        for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]:
            violations = rule.validate(type + ": föo", None)
            self.assertListEqual([], violations)

        # assert violation on wrong type
        expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
                                                  " style, refactor, perf, test, revert, ci, build", "bår: foo")
        violations = rule.validate("bår: foo", None)
        self.assertListEqual([expected_violation], violations)

        # assert violation when use strange chars after correct type
        expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
                                                  " style, refactor, perf, test, revert, ci, build",
                                                  "feat_wrong_chars: föo")
        violations = rule.validate("feat_wrong_chars: föo", None)
        self.assertListEqual([expected_violation], violations)

        # assert violation when use strange chars after correct type
        expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
                                                  " style, refactor, perf, test, revert, ci, build",
                                                  "feat_wrong_chars(scope): föo")
        violations = rule.validate("feat_wrong_chars(scope): föo", None)
        self.assertListEqual([expected_violation], violations)

        # assert violation on wrong format
        expected_violation = RuleViolation("CT1", "Title does not follow ConventionalCommits.org format "
                                                  "'type(optional-scope): description'", "fix föo")
        violations = rule.validate("fix föo", None)
        self.assertListEqual([expected_violation], violations)

        # assert no violation when use ! for breaking changes without scope
        violations = rule.validate("feat!: föo", None)
        self.assertListEqual([], violations)

        # assert no violation when use ! for breaking changes with scope
        violations = rule.validate("fix(scope)!: föo", None)
        self.assertListEqual([], violations)

        # assert no violation when adding new type
        rule = ConventionalCommit({'types': ["föo", "bär"]})
        for typ in ["föo", "bär"]:
            violations = rule.validate(typ + ": hür dur", None)
            self.assertListEqual([], violations)

        # assert violation when using incorrect type when types have been reconfigured
        violations = rule.validate("fix: hür dur", None)
        expected_violation = RuleViolation("CT1", "Title does not start with one of föo, bär", "fix: hür dur")
        self.assertListEqual([expected_violation], violations)

        # assert no violation when adding new type named with numbers
        rule = ConventionalCommit({'types': ["föo123", "123bär"]})
        for typ in ["föo123", "123bär"]:
            violations = rule.validate(typ + ": hür dur", None)
            self.assertListEqual([], violations)
Beispiel #13
0
    def test_lint_sample2(self):
        linter = GitLinter(LintConfig())
        gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
        violations = linter.lint(gitcontext.commits[-1])
        expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
                                  u"Just a title contåining WIP", 1),
                    RuleViolation("B6", "Body message is missing", None, 3)]

        self.assertListEqual(violations, expected)
Beispiel #14
0
 def validate(self, commit):
     flags = re.UNICODE
     flags |= re.IGNORECASE
     for line in commit.message.body:
         if line.lower().startswith("signed-off-by"):
             if not re.search(r"(^)Signed-off-by: ([-'\w.]+) ([-'\w.]+) (.*)", line, flags=flags):
                 return [RuleViolation(self.id, "Signed-off-by: must have a full name", line_nr=1)]
             else:
                 return
     return [RuleViolation(self.id, "Body does not contain a 'Signed-off-by:' line", line_nr=1)]
Beispiel #15
0
    def test_lint_ignore(self):
        lint_config = LintConfig()
        lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"]
        linter = GitLinter(lint_config)
        violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample3")))

        expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
                    RuleViolation("B3", "Line contains hard tab characters (\\t)",
                                  u"This line has a tråiling tab.\t", 5)]

        self.assertListEqual(violations, expected)
Beispiel #16
0
    def validate(self, commit):
        violations = [
            RuleViolation(self.id,
                          f"GitCommit.branches: {commit.branches}",
                          line_nr=1),
            RuleViolation(self.id,
                          f"GitCommit.custom_prop: {commit.custom_prop}",
                          line_nr=1),
        ]

        return violations
Beispiel #17
0
    def test_lint_meta(self):
        """ Lint sample2 but also add some metadata to the commit so we that get's linted as well """
        linter = GitLinter(LintConfig())
        gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
        gitcontext.commits[0].author_email = u"foo bår"
        violations = linter.lint(gitcontext.commits[-1])
        expected = [RuleViolation("M1", "Author email for commit is invalid", u"foo bår", None),
                    RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
                                  u"Just a title contåining WIP", 1),
                    RuleViolation("B6", "Body message is missing", None, 3)]

        self.assertListEqual(violations, expected)
Beispiel #18
0
    def validate(self, commit):
        violations = [
            RuleViolation(
                self.id,
                f"GitContext.current_branch: {commit.context.current_branch}",
                line_nr=1),
            RuleViolation(
                self.id,
                f"GitContext.commentchar: {commit.context.commentchar}",
                line_nr=1)
        ]

        return violations
Beispiel #19
0
    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
Beispiel #20
0
    def validate(self, line, _commit):
        violations = []
        match = RULE_REGEX.match(line)

        if not match:
            msg = "Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
            violations.append(RuleViolation(self.id, msg, line))
        else:
            line_commit_type = match.group(1)
            if line_commit_type not in self.options["types"].value:
                msg = "Title does not start with one of {0}".format(', '.join(
                    self.options['types'].value))
                violations.append(RuleViolation(self.id, msg, line))

        return violations
Beispiel #21
0
    def validate(self, line, _commit):
        violations = []

        for commit_type in self.options["types"].value:
            if line.startswith(ustr(commit_type)):
                break
        else:
            msg = u"Title does not start with one of {0}".format(', '.join(self.options['types'].value))
            violations.append(RuleViolation(self.id, msg, line))

        if not RULE_REGEX.match(line):
            msg = u"Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
            violations.append(RuleViolation(self.id, msg, line))

        return violations
Beispiel #22
0
 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)]
Beispiel #23
0
    def test_author_valid_email_rule(self):
        rule = AuthorValidEmail()

        # valid email addresses
        valid_email_addresses = [
            "fö[email protected]", "Jö[email protected]", "jö[email protected]",
            "jöhn/[email protected]", "jö[email protected]"
        ]
        for email in valid_email_addresses:
            commit = self.gitcommit("", author_email=email)
            violations = rule.validate(commit)
            self.assertIsNone(violations)

        # No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an
        # email address)
        commit = self.gitcommit("")
        violations = rule.validate(commit)
        self.assertIsNone(violations)

        # Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint)
        invalid_email_addresses = [
            "föo@bar", "JöhnDoe", "Jöhn Doe", "Jöhn [email protected]",
            " Jö[email protected]", "JöhnDoe@ foo.com", "JöhnDoe@foo. com",
            "JöhnDoe@foo. com", "@bår.com", "fö[email protected]"
        ]
        for email in invalid_email_addresses:
            commit = self.gitcommit("", author_email=email)
            violations = rule.validate(commit)
            self.assertListEqual(violations, [
                RuleViolation("M1", "Author email for commit is invalid",
                              email)
            ])
Beispiel #24
0
    def test_author_valid_email_rule_custom_regex(self):
        # regex=None -> the rule isn't applied
        rule = AuthorValidEmail()
        rule.options['regex'].set(None)
        emailadresses = ["föo", None, "hür dür"]
        for email in emailadresses:
            commit = self.gitcommit("", author_email=email)
            violations = rule.validate(commit)
            self.assertIsNone(violations)

        # Custom domain
        rule = AuthorValidEmail({'regex': "[^@]+@bår.com"})
        valid_email_addresses = [
            "föo@bår.com", "Jöhn.Doe@bår.com", "jöhn+doe@bår.com",
            "jöhn/doe@bår.com"
        ]
        for email in valid_email_addresses:
            commit = self.gitcommit("", author_email=email)
            violations = rule.validate(commit)
            self.assertIsNone(violations)

        # Invalid email addresses
        invalid_email_addresses = ["fö[email protected]"]
        for email in invalid_email_addresses:
            commit = self.gitcommit("", author_email=email)
            violations = rule.validate(commit)
            self.assertListEqual(violations, [
                RuleViolation("M1", "Author email for commit is invalid",
                              email)
            ])
Beispiel #25
0
 def validate(self, line, _commit):
     if line == "":
         return
     first_word = line.split()[0]
     for suffix in self.options['suffixes'].value:
         if first_word.endswith(suffix):
             return [RuleViolation(self.id, self.violation_message, line)]
Beispiel #26
0
    def validate(self, commit):
        for line in commit.message.body:
            if line.startswith("Signed-off-by"):
                return

        msg = "Body does not contain a 'Signed-Off-By' line"
        return [RuleViolation(self.id, msg, line_nr=1)]
Beispiel #27
0
    def test_lint_configuration_rule(self):
        # Test that all rules are ignored because of matching regex
        lint_config = LintConfig()
        lint_config.set_rule_option("I1", "regex", "^Just a title(.*)")

        linter = GitLinter(lint_config)
        violations = linter.lint(
            self.gitcommit(self.get_sample("commit_message/sample2")))
        self.assertListEqual(violations, [])

        # Test ignoring only certain rules
        lint_config = LintConfig()
        lint_config.set_rule_option("I1", "regex", "^Just a title(.*)")
        lint_config.set_rule_option("I1", "ignore", "B6")

        linter = GitLinter(lint_config)
        violations = linter.lint(
            self.gitcommit(self.get_sample("commit_message/sample2")))

        # Normally we'd expect a B6 violation, but that one is skipped because of the specific ignore set above
        expected = [
            RuleViolation("T5",
                          "Title contains the word 'WIP' (case-insensitive)",
                          u"Just a title contåining WIP", 1)
        ]

        self.assertListEqual(violations, expected)
Beispiel #28
0
 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)]
Beispiel #29
0
    def validate(self, commit):
        """Validate user defined gitlint rules."""
        for line in commit.message.body:
            if line.startswith("Signed-off-by"):
                return []

        msg = "Body does not contain a 'Signed-off-by' line"
        return [RuleViolation(self.id, msg, line_nr=1)]
Beispiel #30
0
 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)
         ]