From a2aa51f5702b18016c25d943499941323952704d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 19 Nov 2022 15:52:46 +0100 Subject: Adding upstream version 0.18.0. Signed-off-by: Daniel Baumann --- gitlint-core/gitlint/config.py | 260 +++++++++++++++++++++++------------------ 1 file changed, 145 insertions(+), 115 deletions(-) (limited to 'gitlint-core/gitlint/config.py') 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): @@ -118,6 +126,15 @@ class LintConfig: def ignore_fixup_commits(self, value): 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 @@ -182,6 +199,15 @@ class LintConfig: def fail_without_commits(self, value): 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 ".