Exemple #1
0
class SpecialChars(LineRule):
    """ This rule will enforce that the commit message title does not contain any of the following characters:
        $^%@!*() """

    # A rule MUST have a human friendly name
    name = "title-no-special-chars"

    # A rule MUST have a *unique* id, we recommend starting with UL (for User-defined Line-rule), but this can
    # really be anything.
    id = "UL1"

    # A line-rule MUST have a target (not required for CommitRules).
    target = CommitMessageTitle

    # A rule MAY have an option_spec if its behavior should be configurable.
    options_spec = [
        ListOption(
            'special-chars', ['$', '^', '%', '@', '!', '*', '(', ')'],
            "Comma separated list of characters that should not occur in the title"
        )
    ]

    def validate(self, line, _commit):
        violations = []
        # options can be accessed by looking them up by their name in self.options
        for char in self.options['special-chars'].value:
            if char in line:
                violation = RuleViolation(
                    self.id,
                    "Title contains the special character '{0}'".format(char),
                    line)
                violations.append(violation)

        return violations
Exemple #2
0
class ConventionalCommit(LineRule):
    """ This rule enforces the spec at https://www.conventionalcommits.org/. """

    name = "contrib-title-conventional-commits"
    id = "CT1"
    target = CommitMessageTitle

    options_spec = [
        ListOption(
            "types",
            ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert"],
            "Comma separated list of allowed commit types.",
        )
    ]

    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
Exemple #3
0
class BranchNamingConventions(CommitRule):
    """ This rule will enforce that a commit is part of a branch that meets certain naming conventions.
        See GitFlow for real-world example of this: https://nvie.com/posts/a-successful-git-branching-model/
    """

    # A rule MUST have a human friendly name
    name = "branch-naming-conventions"

    # A rule MUST have a *unique* id, we recommend starting with UC (for User-defined Commit-rule).
    id = "UC3"

    # A rule MAY have an option_spec if its behavior should be configurable.
    options_spec = [
        ListOption('branch-prefixes', ["feature/", "hotfix/", "release/"],
                   "Allowed branch prefixes")
    ]

    def validate(self, commit):
        violations = []
        allowed_branch_prefixes = self.options['branch-prefixes'].value
        for branch in commit.branches:
            valid_branch_name = False

            for allowed_prefix in allowed_branch_prefixes:
                if branch.startswith(allowed_prefix):
                    valid_branch_name = True
                    break

            if not valid_branch_name:
                msg = "Branch name '{0}' does not start with one of {1}".format(
                    branch, utils.sstr(allowed_branch_prefixes))
                violations.append(RuleViolation(self.id, msg, line_nr=1))

        return violations
Exemple #4
0
class AreaFormatting(CommitRule):
    name = "area-formatting"
    id = "ZT2"

    options_spec = [
        ListOption("exclusions", ["WIP"], "Exclusions to area lower-case rule")
    ]

    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))
        for area in title_components[:-1]:
            if not (area.islower() or area in exclusions) 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
Exemple #5
0
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
Exemple #6
0
    def test_list_option(self):
        # normal behavior
        option = ListOption("test-name", "bar", "Test Description")
        option.set("a,b,c,d")
        self.assertListEqual(option.value, ["a", "b", "c", "d"])

        # trailing comma
        option.set("e,f,g,")
        self.assertListEqual(option.value, ["e", "f", "g"])

        # spaces should be trimmed
        option.set(" abc , def   , ghi \t ")
        self.assertListEqual(option.value, ["abc", "def", "ghi"])

        # conversion to string before split
        option.set(123)
        self.assertListEqual(option.value, ["123"])
Exemple #7
0
    def test_list_option(self):
        # normal behavior
        option = ListOption("test-name", "bar", "Test Description")
        option.set("a,b,c,d")
        self.assertListEqual(option.value, ["a", "b", "c", "d"])

        # trailing comma
        option.set("e,f,g,")
        self.assertListEqual(option.value, ["e", "f", "g"])

        # spaces should be trimmed
        option.set(" abc , def   , ghi \t ")
        self.assertListEqual(option.value, ["abc", "def", "ghi"])

        # conversion to string before split
        option.set(123)
        self.assertListEqual(option.value, ["123"])
Exemple #8
0
class BodyChangedFileMention(CommitRule):
    name = "body-changed-file-mention"
    id = "B7"
    options_spec = [ListOption('files', [], "Files that need to be mentioned")]

    def validate(self, commit):
        violations = []
        for needs_mentioned_file in self.options['files'].value:
            # if a file that we need to look out for is actually changed, then check whether it occurs
            # in the commit msg body
            if needs_mentioned_file in commit.changed_files:
                if needs_mentioned_file not in " ".join(commit.message.body):
                    violation_message = u"Body does not mention changed file '{0}'".format(needs_mentioned_file)
                    violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1))
        return violations if violations else None
Exemple #9
0
class SpecialChars(LineRule):
    """
    This rule will enforce that the commit message title does not contain any
    banned characters.
    """

    # A rule MUST have a human friendly name
    name = "title-no-special-chars"

    # A rule MUST have a *unique* id
    id = "UL1"

    # A line-rule MUST have a target (not required for CommitRules).
    target = CommitMessageTitle

    # A rule MAY have an option_spec if its behavior should be configurable.
    options_spec = [
        ListOption(
            "special-chars",
            list(DEFAULT_BANNED_CHARS),
            "Comma separated list of characters that should not occur in the title",
        )
    ]

    def validate(self, line, _commit):
        """
        Validate each line

        Args:
            line (str): line
            _commit (???): ???

        Returns:
            list: List of violations
        """
        violations = []
        # options can be accessed by looking them up by their name in self.options
        for char in self.options["special-chars"].value:
            if char in line:
                violation = RuleViolation(
                    self.id,
                    "Title contains the special character '{0}'".format(char),
                    line,
                )
                violations.append(violation)

        return violations
Exemple #10
0
class LineMustNotContainWord(LineRule):
    """ Violation if a line contains one of a list of words (NOTE: using a word in the list inside another word is not
    a violation, e.g: WIPING is not a violation if 'WIP' is a word that is not allowed.) """
    name = "line-must-not-contain"
    id = "R5"
    options_spec = [ListOption('words', [], "Comma separated list of words that should not be found")]
    violation_message = u"Line contains {0}"

    def validate(self, line, _commit):
        strings = self.options['words'].value
        violations = []
        for string in strings:
            regex = re.compile(r"\b%s\b" % string.lower(), re.IGNORECASE | re.UNICODE)
            match = regex.search(line.lower())
            if match:
                violations.append(RuleViolation(self.id, self.violation_message.format(string), line))
        return violations if violations else None
Exemple #11
0
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
Exemple #12
0
class CommitType(LineRule):
    """ This rule will enforce that the commit message title contains special word indicating the type of commit,
        as recommended by PyCharm Git Commit Template plugin (see also https://udacity.github.io/git-styleguide/). """

    name = "title-commit-type"
    id = "UL1"
    target = CommitMessageTitle
    options_spec = [ListOption('special-words',
                               ['feat', 'fix', 'docs', 'style', 'refactor',
                                'perf', 'test', 'build', 'ci', 'chore', 'revert'],
                               "Comma separated list of words that should occur in the title")]

    def validate(self, line, _commit):
        violations = []
        first_word = line.split('(')[0].split(':')[0]
        if first_word not in self.options['special-words'].value:
            violation = RuleViolation(self.id, "Title begins with '{0}'; only words from {1} are allowed. In commit".
                                      format(first_word, self.options['special-words'].value), line)
            violations.append(violation)

        return violations
Exemple #13
0
class TitleMustNotContainWord(LineMustNotContainWord):
    name = "title-must-not-contain-word"
    id = "T5"
    target = CommitMessageTitle
    options_spec = [ListOption('words', ["WIP"], "Must not contain word")]
    violation_message = "Title contains the word '{0}' (case-insensitive)"
Exemple #14
0
    def test_list_option(self):
        # normal behavior
        option = ListOption("test-name", u"å,b,c,d", "Test Description")
        self.assertListEqual(option.value, [u"å", u"b", u"c", u"d"])

        # re-set value
        option.set(u"1,2,3,4")
        self.assertListEqual(option.value, [u"1", u"2", u"3", u"4"])

        # set list
        option.set([u"foo", u"bår", u"test"])
        self.assertListEqual(option.value, [u"foo", u"bår", u"test"])

        # empty string
        option.set("")
        self.assertListEqual(option.value, [])

        # whitespace string
        option.set("  \t  ")
        self.assertListEqual(option.value, [])

        # empty list
        option.set([])
        self.assertListEqual(option.value, [])

        # trailing comma
        option.set(u"ë,f,g,")
        self.assertListEqual(option.value, [u"ë", u"f", u"g"])

        # leading and trailing whitespace should be trimmed, but only deduped within text
        option.set(" abc , def   , ghi \t  , jkl  mno  ")
        self.assertListEqual(option.value, ["abc", "def", "ghi", "jkl  mno"])

        # Also strip whitespace within a list
        option.set(["\t foo", "bar \t ", " test  123 "])
        self.assertListEqual(option.value, ["foo", "bar", "test  123"])

        # conversion to string before split
        option.set(123)
        self.assertListEqual(option.value, ["123"])
Exemple #15
0
    def test_list_option(self):
        # normal behavior
        option = ListOption("test-name", "a,b,c,d", "Test Description")
        self.assertListEqual(option.value, ["a", "b", "c", "d"])

        # re-set value
        option.set("1,2,3,4")
        self.assertListEqual(option.value, ["1", "2", "3", "4"])

        # set list
        option.set(["foo", "bar", "test"])
        self.assertListEqual(option.value, ["foo", "bar", "test"])

        # trailing comma
        option.set("e,f,g,")
        self.assertListEqual(option.value, ["e", "f", "g"])

        # leading and trailing whitespace should be trimmed, but only deduped within text
        option.set(" abc , def   , ghi \t  , jkl  mno  ")
        self.assertListEqual(option.value, ["abc", "def", "ghi", "jkl  mno"])

        # Also strip whitespace within a list
        option.set(["\t foo", "bar \t ", " test  123 "])
        self.assertListEqual(option.value, ["foo", "bar", "test  123"])

        # conversion to string before split
        option.set(123)
        self.assertListEqual(option.value, ["123"])