Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
from gitlint.options import ListOption
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
from gitlint.utils import ustr
RULE_REGEX = re.compile(r"[^(]+?(\([^)]+?\))?: .+")
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", "ci", "build"],
"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))
class EndsWithDot(CommitRule):
name = "title-doesn't-end-with-dot"
id = "ZT1"
def validate(self, commit: Any) -> Optional[List[RuleViolation]]:
error = "Title does not end with a '.' character"
if not commit.message.title.endswith("."):
return [RuleViolation(self.id, error, line_nr=1)]
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:
return [RuleViolation(self.id, "Body does not contain a 'Signed-Off-By' line", line_nr=1)]
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))
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
def __init__(self):
self.rules = RuleCollection(self.default_rule_classes)
self._verbosity = options.IntOption('verbosity', 3, "Verbosity")
self._ignore_merge_commits = options.BoolOption('ignore-merge-commits', True, "Ignore merge commits")
self._ignore_fixup_commits = options.BoolOption('ignore-fixup-commits', True, "Ignore fixup commits")
self._ignore_squash_commits = options.BoolOption('ignore-squash-commits', True, "Ignore squash commits")
self._ignore_revert_commits = options.BoolOption('ignore-revert-commits', True, "Ignore revert commits")
self._debug = options.BoolOption('debug', False, "Enable debug mode")
self._extra_path = None
target_description = "Path of the target git repository (default=current working directory)"
self._target = options.PathOption('target', os.path.realpath(os.getcwd()), target_description)
self._ignore = options.ListOption('ignore', [], 'List of rule-ids to ignore')
self._contrib = options.ListOption('contrib', [], 'List of contrib-rules to enable')
self._config_path = None
ignore_stdin_description = "Ignore any stdin data. Useful for running in CI server."
self._ignore_stdin = options.BoolOption('ignore-stdin', False, ignore_stdin_description)
self._staged = options.BoolOption('staged', False, "Read staged commit meta-info from the local repository.")
def __init__(self):
self.rules = RuleCollection(self.default_rule_classes)
self._verbosity = options.IntOption('verbosity', 3, "Verbosity")
self._ignore_merge_commits = options.BoolOption('ignore-merge-commits', True, "Ignore merge commits")
self._ignore_fixup_commits = options.BoolOption('ignore-fixup-commits', True, "Ignore fixup commits")
self._ignore_squash_commits = options.BoolOption('ignore-squash-commits', True, "Ignore squash commits")
self._ignore_revert_commits = options.BoolOption('ignore-revert-commits', True, "Ignore revert commits")
self._debug = options.BoolOption('debug', False, "Enable debug mode")
self._extra_path = None
target_description = "Path of the target git repository (default=current working directory)"
self._target = options.PathOption('target', os.path.realpath(os.getcwd()), target_description)
self._ignore = options.ListOption('ignore', [], 'List of rule-ids to ignore')
self._contrib = options.ListOption('contrib', [], 'List of contrib-rules to enable')
self._config_path = None
ignore_stdin_description = "Ignore any stdin data. Useful for running in CI server."
self._ignore_stdin = options.BoolOption('ignore-stdin', False, ignore_stdin_description)
self._staged = options.BoolOption('staged', False, "Read staged commit meta-info from the local repository.")
class HardTab(LineRule):
name = "hard-tab"
id = "R3"
violation_message = "Line contains hard tab characters (\\t)"
def validate(self, line, _commit):
if "\t" in line:
return [RuleViolation(self.id, self.violation_message, line)]
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
class LeadingWhiteSpace(LineRule):
name = "leading-whitespace"
id = "R6"
if title.endswith(punctuation_mark):
return [RuleViolation(self.id, u"Title has trailing punctuation ({0})".format(punctuation_mark), title)]
class TitleHardTab(HardTab):
name = "title-hard-tab"
id = "T4"
target = CommitMessageTitle
violation_message = "Title contains hard tab characters (\\t)"
class TitleMustNotContainWord(LineMustNotContainWord):
name = "title-must-not-contain-word"
id = "T5"
target = CommitMessageTitle
options_spec = [ListOption('words', ["WIP"], "Must not contain word")]
violation_message = u"Title contains the word '{0}' (case-insensitive)"
class TitleLeadingWhitespace(LeadingWhiteSpace):
name = "title-leading-whitespace"
id = "T6"
target = CommitMessageTitle
violation_message = "Title has leading whitespace"
class TitleRegexMatches(LineRule):
name = "title-match-regex"
id = "T7"
target = CommitMessageTitle
options_spec = [StrOption('regex', ".*", "Regex the title should match")]
name = "body-is-missing"
id = "B6"
options_spec = [BoolOption('ignore-merge-commits', True, "Ignore merge commits")]
def validate(self, commit):
# ignore merges when option tells us to, which may have no body
if self.options['ignore-merge-commits'].value and commit.is_merge_commit:
return
if len(commit.message.body) < 2:
return [RuleViolation(self.id, "Body message is missing", None, 3)]
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
class AuthorValidEmail(CommitRule):
name = "author-valid-email"
id = "M1"