summaryrefslogtreecommitdiffstats
path: root/gitlint-core/gitlint/config.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-11-19 14:52:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-11-19 14:52:46 +0000
commita2aa51f5702b18016c25d943499941323952704d (patch)
tree7ee43f79639ee53903e7ca389e548974e1497c3a /gitlint-core/gitlint/config.py
parentAdding upstream version 0.17.0. (diff)
downloadgitlint-a2aa51f5702b18016c25d943499941323952704d.tar.xz
gitlint-a2aa51f5702b18016c25d943499941323952704d.zip
Adding upstream version 0.18.0.upstream/0.18.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gitlint-core/gitlint/config.py')
-rw-r--r--gitlint-core/gitlint/config.py260
1 files changed, 145 insertions, 115 deletions
diff --git a/gitlint-core/gitlint/config.py b/gitlint-core/gitlint/config.py
index 49c380a..f038d4a 100644
--- a/gitlint-core/gitlint/config.py
+++ b/gitlint-core/gitlint/config.py
@@ -1,7 +1,6 @@
from configparser import ConfigParser, Error as ConfigParserError
import copy
-import io
import re
import os
import shutil
@@ -16,8 +15,8 @@ from gitlint.exception import GitlintError
def handle_option_error(func):
- """ Decorator that calls given method/function and handles any RuleOptionError gracefully by converting it to a
- LintConfigError. """
+ """Decorator that calls given method/function and handles any RuleOptionError gracefully by converting it to a
+ LintConfigError."""
def wrapped(*args):
try:
@@ -32,53 +31,62 @@ class LintConfigError(GitlintError):
pass
-class LintConfig:
- """ Class representing gitlint configuration.
- Contains active config as well as number of methods to easily get/set the config.
+class LintConfig: # pylint: disable=too-many-instance-attributes
+ """Class representing gitlint configuration.
+ Contains active config as well as number of methods to easily get/set the config.
"""
# Default tuple of rule classes (tuple because immutable).
- default_rule_classes = (rules.IgnoreByTitle,
- rules.IgnoreByBody,
- rules.IgnoreBodyLines,
- rules.IgnoreByAuthorName,
- rules.TitleMaxLength,
- rules.TitleTrailingWhitespace,
- rules.TitleLeadingWhitespace,
- rules.TitleTrailingPunctuation,
- rules.TitleHardTab,
- rules.TitleMustNotContainWord,
- rules.TitleRegexMatches,
- rules.TitleMinLength,
- rules.BodyMaxLineLength,
- rules.BodyMinLength,
- rules.BodyMissing,
- rules.BodyTrailingWhitespace,
- rules.BodyHardTab,
- rules.BodyFirstLineEmpty,
- rules.BodyChangedFileMention,
- rules.BodyRegexMatches,
- rules.AuthorValidEmail)
+ default_rule_classes = (
+ rules.IgnoreByTitle,
+ rules.IgnoreByBody,
+ rules.IgnoreBodyLines,
+ rules.IgnoreByAuthorName,
+ rules.TitleMaxLength,
+ rules.TitleTrailingWhitespace,
+ rules.TitleLeadingWhitespace,
+ rules.TitleTrailingPunctuation,
+ rules.TitleHardTab,
+ rules.TitleMustNotContainWord,
+ rules.TitleRegexMatches,
+ rules.TitleMinLength,
+ rules.BodyMaxLineLength,
+ rules.BodyMinLength,
+ rules.BodyMissing,
+ rules.BodyTrailingWhitespace,
+ rules.BodyHardTab,
+ rules.BodyFirstLineEmpty,
+ rules.BodyChangedFileMention,
+ rules.BodyRegexMatches,
+ rules.AuthorValidEmail,
+ )
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._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_fixup_amend_commits = options.BoolOption(
+ "ignore-fixup-amend-commits", True, "Ignore fixup amend 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._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.")
- self._fail_without_commits = options.BoolOption('fail-without-commits', False,
- "Hard fail when the target commit range is empty")
+ 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.")
+ self._fail_without_commits = options.BoolOption(
+ "fail-without-commits", False, "Hard fail when the target commit range is empty"
+ )
+ self._regex_style_search = options.BoolOption(
+ "regex-style-search", False, "Use `search` instead of `match` semantics for regex rules"
+ )
@property
def target(self):
@@ -119,6 +127,15 @@ class LintConfig:
return self._ignore_fixup_commits.set(value)
@property
+ def ignore_fixup_amend_commits(self):
+ return self._ignore_fixup_amend_commits.value
+
+ @ignore_fixup_amend_commits.setter
+ @handle_option_error
+ def ignore_fixup_amend_commits(self, value):
+ return self._ignore_fixup_amend_commits.set(value)
+
+ @property
def ignore_squash_commits(self):
return self._ignore_squash_commits.value
@@ -183,6 +200,15 @@ class LintConfig:
return self._fail_without_commits.set(value)
@property
+ def regex_style_search(self):
+ return self._regex_style_search.value
+
+ @regex_style_search.setter
+ @handle_option_error
+ def regex_style_search(self, value):
+ return self._regex_style_search.set(value)
+
+ @property
def extra_path(self):
return self._extra_path.value if self._extra_path else None
@@ -193,9 +219,7 @@ class LintConfig:
self._extra_path.set(value)
else:
self._extra_path = options.PathOption(
- 'extra-path', value,
- "Path to a directory or module with extra user-defined rules",
- type='both'
+ "extra-path", value, "Path to a directory or module with extra user-defined rules", type="both"
)
# Make sure we unload any previously loaded extra-path rules
@@ -203,7 +227,7 @@ class LintConfig:
# Find rules in the new extra-path and add them to the existing rules
rule_classes = rule_finder.find_rule_classes(self.extra_path)
- self.rules.add_rules(rule_classes, {'is_user_defined': True})
+ self.rules.add_rules(rule_classes, {"is_user_defined": True})
except (options.RuleOptionError, rules.UserRuleError) as e:
raise LintConfigError(str(e)) from e
@@ -226,12 +250,11 @@ class LintConfig:
# For each specified contrib rule, check whether it exists among the contrib classes
for rule_id_or_name in self.contrib:
- rule_class = next((rc for rc in rule_classes if
- rule_id_or_name in (rc.id, rc.name)), False)
+ rule_class = next((rc for rc in rule_classes if rule_id_or_name in (rc.id, rc.name)), False)
# If contrib rule exists, instantiate it and add it to the rules list
if rule_class:
- self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True})
+ self.rules.add_rule(rule_class, rule_class.id, {"is_contrib": True})
else:
raise LintConfigError(f"No contrib rule with id or name '{rule_id_or_name}' found.")
@@ -250,14 +273,14 @@ class LintConfig:
return option
def get_rule_option(self, rule_name_or_id, option_name):
- """ Returns the value of a given option for a given rule. LintConfigErrors will be raised if the
- rule or option don't exist. """
+ """Returns the value of a given option for a given rule. LintConfigErrors will be raised if the
+ rule or option don't exist."""
option = self._get_option(rule_name_or_id, option_name)
return option.value
def set_rule_option(self, rule_name_or_id, option_name, option_value):
- """ Attempts to set a given value for a given option for a given rule.
- LintConfigErrors will be raised if the rule or option don't exist or if the value is invalid. """
+ """Attempts to set a given value for a given option for a given rule.
+ LintConfigErrors will be raised if the rule or option don't exist or if the value is invalid."""
option = self._get_option(rule_name_or_id, option_name)
try:
option.set(option_value)
@@ -275,45 +298,53 @@ class LintConfig:
setattr(self, attr_name, option_value)
def __eq__(self, other):
- return isinstance(other, LintConfig) and \
- self.rules == other.rules and \
- self.verbosity == other.verbosity and \
- self.target == other.target and \
- self.extra_path == other.extra_path and \
- self.contrib == other.contrib and \
- self.ignore_merge_commits == other.ignore_merge_commits and \
- self.ignore_fixup_commits == other.ignore_fixup_commits and \
- self.ignore_squash_commits == other.ignore_squash_commits and \
- self.ignore_revert_commits == other.ignore_revert_commits and \
- self.ignore_stdin == other.ignore_stdin and \
- self.staged == other.staged and \
- self.fail_without_commits == other.fail_without_commits and \
- self.debug == other.debug and \
- self.ignore == other.ignore and \
- self._config_path == other._config_path # noqa
+ return (
+ isinstance(other, LintConfig)
+ and self.rules == other.rules
+ and self.verbosity == other.verbosity
+ and self.target == other.target
+ and self.extra_path == other.extra_path
+ and self.contrib == other.contrib
+ and self.ignore_merge_commits == other.ignore_merge_commits
+ and self.ignore_fixup_commits == other.ignore_fixup_commits
+ and self.ignore_fixup_amend_commits == other.ignore_fixup_amend_commits
+ and self.ignore_squash_commits == other.ignore_squash_commits
+ and self.ignore_revert_commits == other.ignore_revert_commits
+ and self.ignore_stdin == other.ignore_stdin
+ and self.staged == other.staged
+ and self.fail_without_commits == other.fail_without_commits
+ and self.regex_style_search == other.regex_style_search
+ and self.debug == other.debug
+ and self.ignore == other.ignore
+ and self._config_path == other._config_path
+ )
def __str__(self):
# config-path is not a user exposed variable, so don't print it under the general section
- return (f"config-path: {self._config_path}\n"
- f"[GENERAL]\n"
- f"extra-path: {self.extra_path}\n"
- f"contrib: {self.contrib}\n"
- f"ignore: {','.join(self.ignore)}\n"
- f"ignore-merge-commits: {self.ignore_merge_commits}\n"
- f"ignore-fixup-commits: {self.ignore_fixup_commits}\n"
- f"ignore-squash-commits: {self.ignore_squash_commits}\n"
- f"ignore-revert-commits: {self.ignore_revert_commits}\n"
- f"ignore-stdin: {self.ignore_stdin}\n"
- f"staged: {self.staged}\n"
- f"fail-without-commits: {self.fail_without_commits}\n"
- f"verbosity: {self.verbosity}\n"
- f"debug: {self.debug}\n"
- f"target: {self.target}\n"
- f"[RULES]\n{self.rules}")
+ return (
+ f"config-path: {self._config_path}\n"
+ "[GENERAL]\n"
+ f"extra-path: {self.extra_path}\n"
+ f"contrib: {self.contrib}\n"
+ f"ignore: {','.join(self.ignore)}\n"
+ f"ignore-merge-commits: {self.ignore_merge_commits}\n"
+ f"ignore-fixup-commits: {self.ignore_fixup_commits}\n"
+ f"ignore-fixup-amend-commits: {self.ignore_fixup_amend_commits}\n"
+ f"ignore-squash-commits: {self.ignore_squash_commits}\n"
+ f"ignore-revert-commits: {self.ignore_revert_commits}\n"
+ f"ignore-stdin: {self.ignore_stdin}\n"
+ f"staged: {self.staged}\n"
+ f"fail-without-commits: {self.fail_without_commits}\n"
+ f"regex-style-search: {self.regex_style_search}\n"
+ f"verbosity: {self.verbosity}\n"
+ f"debug: {self.debug}\n"
+ f"target: {self.target}\n"
+ f"[RULES]\n{self.rules}"
+ )
class RuleCollection:
- """ Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules. """
+ """Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules."""
def __init__(self, rule_classes=None, rule_attrs=None):
# Use an ordered dict so that the order in which rules are applied is always the same
@@ -329,13 +360,13 @@ class RuleCollection:
return rule
def add_rule(self, rule_class, rule_id, rule_attrs=None):
- """ Instantiates and adds a rule to RuleCollection.
- Note: There can be multiple instantiations of the same rule_class in the RuleCollection, as long as the
- rule_id is unique.
- :param rule_class python class representing the rule
- :param rule_id unique identifier for the rule. If not unique, it will
- overwrite the existing rule with that id
- :param rule_attrs dictionary of attributes to set on the instantiated rule obj
+ """Instantiates and adds a rule to RuleCollection.
+ Note: There can be multiple instantiations of the same rule_class in the RuleCollection, as long as the
+ rule_id is unique.
+ :param rule_class python class representing the rule
+ :param rule_id unique identifier for the rule. If not unique, it will
+ overwrite the existing rule with that id
+ :param rule_attrs dictionary of attributes to set on the instantiated rule obj
"""
rule_obj = rule_class()
rule_obj.id = rule_id
@@ -345,12 +376,12 @@ class RuleCollection:
self._rules[rule_obj.id] = rule_obj
def add_rules(self, rule_classes, rule_attrs=None):
- """ Convenience method to add multiple rules at once based on a list of rule classes. """
+ """Convenience method to add multiple rules at once based on a list of rule classes."""
for rule_class in rule_classes:
self.add_rule(rule_class, rule_class.id, rule_attrs)
def delete_rules_by_attr(self, attr_name, attr_val):
- """ Deletes all rules from the collection that match a given attribute name and value """
+ """Deletes all rules from the collection that match a given attribute name and value"""
# Create a new list based on _rules.values() because in python 3, values() is a ValuesView as opposed to a list
# This means you can't modify the ValueView while iterating over it.
for rule in [r for r in self._rules.values()]: # pylint: disable=unnecessary-comprehension
@@ -358,8 +389,7 @@ class RuleCollection:
del self._rules[rule.id]
def __iter__(self):
- for rule in self._rules.values():
- yield rule
+ yield from self._rules.values()
def __eq__(self, other):
return isinstance(other, RuleCollection) and self._rules == other._rules
@@ -385,7 +415,7 @@ class RuleCollection:
class LintConfigBuilder:
- """ Factory class that can build gitlint config.
+ """Factory class that can build gitlint config.
This is primarily useful to deal with complex configuration scenarios where configuration can be set and overridden
from various sources (typically according to certain precedence rules) before the actual config should be
normalized, validated and build. Example usage can be found in gitlint.cli.
@@ -403,19 +433,19 @@ class LintConfigBuilder:
self._config_blueprint[section][option_name] = option_value
def set_config_from_commit(self, commit):
- """ Given a git commit, applies config specified in the commit message.
- Supported:
- - gitlint-ignore: all
+ """Given a git commit, applies config specified in the commit message.
+ Supported:
+ - gitlint-ignore: all
"""
for line in commit.message.body:
pattern = re.compile(r"^gitlint-ignore:\s*(.*)")
matches = pattern.match(line)
if matches and len(matches.groups()) == 1:
- self.set_option('general', 'ignore', matches.group(1))
+ self.set_option("general", "ignore", matches.group(1))
def set_config_from_string_list(self, config_options):
- """ Given a list of config options of the form "<rule>.<option>=<value>", parses out the correct rule and option
- and sets the value accordingly in this factory object. """
+ """Given a list of config options of the form "<rule>.<option>=<value>", parses out the correct rule and option
+ and sets the value accordingly in this factory object."""
for config_option in config_options:
try:
config_name, option_value = config_option.split("=", 1)
@@ -425,17 +455,18 @@ class LintConfigBuilder:
self.set_option(rule_name, option_name, option_value)
except ValueError as e: # raised if the config string is invalid
raise LintConfigError(
- f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'") from e
+ f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'"
+ ) from e
def set_from_config_file(self, filename):
- """ Loads lint config from an ini-style config file """
+ """Loads lint config from an ini-style config file"""
if not os.path.exists(filename):
raise LintConfigError(f"Invalid file path: {filename}")
self._config_path = os.path.realpath(filename)
try:
parser = ConfigParser()
- with io.open(filename, encoding=DEFAULT_ENCODING) as config_file:
+ with open(filename, encoding=DEFAULT_ENCODING) as config_file:
parser.read_file(config_file, filename)
for section_name in parser.sections():
@@ -446,8 +477,8 @@ class LintConfigBuilder:
raise LintConfigError(str(e)) from e
def _add_named_rule(self, config, qualified_rule_name):
- """ Adds a Named Rule to a given LintConfig object.
- IMPORTANT: This method does *NOT* overwrite existing Named Rules with the same canonical id.
+ """Adds a Named Rule to a given LintConfig object.
+ IMPORTANT: This method does *NOT* overwrite existing Named Rules with the same canonical id.
"""
# Split up named rule in its parts: the name/id that specifies the parent rule,
@@ -475,13 +506,13 @@ class LintConfigBuilder:
# Add the rule to the collection of rules if it's not there already
if not config.rules.find_rule(canonical_id):
- config.rules.add_rule(parent_rule.__class__, canonical_id, {'is_named': True, 'name': canonical_name})
+ config.rules.add_rule(parent_rule.__class__, canonical_id, {"is_named": True, "name": canonical_name})
return canonical_id
def build(self, config=None):
- """ Build a real LintConfig object by normalizing and validating the options that were previously set on this
- factory. """
+ """Build a real LintConfig object by normalizing and validating the options that were previously set on this
+ factory."""
# If we are passed a config object, then rebuild that object instead of building a new lintconfig object from
# scratch
if not config:
@@ -490,7 +521,7 @@ class LintConfigBuilder:
config._config_path = self._config_path
# Set general options first as this might change the behavior or validity of the other options
- general_section = self._config_blueprint.get('general')
+ general_section = self._config_blueprint.get("general")
if general_section:
for option_name, option_value in general_section.items():
config.set_general_option(option_name, option_value)
@@ -499,7 +530,6 @@ class LintConfigBuilder:
for option_name, option_value in section_dict.items():
# Skip over the general section, as we've already done that above
if section_name != "general":
-
# If the section name contains a colon (:), then this section is defining a Named Rule
# Which means we need to instantiate that Named Rule in the config.
if self.RULE_QUALIFIER_SYMBOL in section_name:
@@ -510,7 +540,7 @@ class LintConfigBuilder:
return config
def clone(self):
- """ Creates an exact copy of a LintConfigBuilder. """
+ """Creates an exact copy of a LintConfigBuilder."""
builder = LintConfigBuilder()
builder._config_blueprint = copy.deepcopy(self._config_blueprint)
builder._config_path = self._config_path
@@ -523,6 +553,6 @@ GITLINT_CONFIG_TEMPLATE_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath
class LintConfigGenerator:
@staticmethod
def generate_config(dest):
- """ Generates a gitlint config file at the given destination location.
- Expects that the given ```dest``` points to a valid destination. """
+ """Generates a gitlint config file at the given destination location.
+ Expects that the given ```dest``` points to a valid destination."""
shutil.copyfile(GITLINT_CONFIG_TEMPLATE_SRC_PATH, dest)