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/__init__.py | 2 +- gitlint-core/gitlint/cache.py | 33 +- gitlint-core/gitlint/cli.py | 135 ++++-- gitlint-core/gitlint/config.py | 260 ++++++------ .../gitlint/contrib/rules/authors_commit.py | 46 +++ .../gitlint/contrib/rules/conventional_commit.py | 4 +- .../contrib/rules/disallow_cleanup_commits.py | 22 + gitlint-core/gitlint/contrib/rules/signedoff_by.py | 3 +- gitlint-core/gitlint/deprecation.py | 40 ++ gitlint-core/gitlint/display.py | 6 +- gitlint-core/gitlint/exception.py | 4 +- gitlint-core/gitlint/files/gitlint | 10 +- gitlint-core/gitlint/git.py | 286 +++++++++---- gitlint-core/gitlint/hooks.py | 18 +- gitlint-core/gitlint/lint.py | 57 ++- gitlint-core/gitlint/options.py | 34 +- gitlint-core/gitlint/rule_finder.py | 71 ++-- gitlint-core/gitlint/rules.py | 181 +++++--- gitlint-core/gitlint/shell.py | 30 +- gitlint-core/gitlint/tests/base.py | 81 ++-- gitlint-core/gitlint/tests/cli/test_cli.py | 444 ++++++++++++-------- gitlint-core/gitlint/tests/cli/test_cli_hooks.py | 157 +++---- gitlint-core/gitlint/tests/config/test_config.py | 109 +++-- .../gitlint/tests/config/test_config_builder.py | 88 ++-- .../gitlint/tests/config/test_config_precedence.py | 46 ++- .../gitlint/tests/config/test_rule_collection.py | 9 +- .../tests/contrib/rules/test_authors_commit.py | 106 +++++ .../contrib/rules/test_conventional_commit.py | 39 +- .../contrib/rules/test_disallow_cleanup_commits.py | 35 ++ .../tests/contrib/rules/test_signedoff_by.py | 5 +- .../gitlint/tests/contrib/test_contrib_rules.py | 34 +- .../tests/expected/cli/test_cli/test_debug_1 | 25 +- .../cli/test_cli/test_input_stream_debug_2 | 7 +- .../cli/test_cli/test_lint_staged_msg_filename_2 | 11 +- .../expected/cli/test_cli/test_lint_staged_stdin_2 | 11 +- .../tests/expected/cli/test_cli/test_named_rules_2 | 7 +- gitlint-core/gitlint/tests/git/test_git.py | 65 +-- gitlint-core/gitlint/tests/git/test_git_commit.py | 460 +++++++++++++++------ gitlint-core/gitlint/tests/git/test_git_context.py | 41 +- .../gitlint/tests/rules/test_body_rules.py | 27 +- .../tests/rules/test_configuration_rules.py | 66 +-- .../gitlint/tests/rules/test_meta_rules.py | 48 ++- gitlint-core/gitlint/tests/rules/test_rules.py | 2 - .../gitlint/tests/rules/test_title_rules.py | 56 ++- .../gitlint/tests/rules/test_user_rules.py | 69 ++-- .../tests/samples/commit_message/fixup_amend | 1 + gitlint-core/gitlint/tests/samples/config/AUTHORS | 2 + .../user_rules/import_exception/invalid_python.py | 1 - .../user_rules/incorrect_linerule/my_line_rule.py | 2 - .../tests/samples/user_rules/my_commit_rules.py | 7 +- .../samples/user_rules/parent_package/__init__.py | 1 - .../user_rules/parent_package/my_commit_rules.py | 2 - gitlint-core/gitlint/tests/test_cache.py | 4 +- gitlint-core/gitlint/tests/test_deprecation.py | 23 ++ gitlint-core/gitlint/tests/test_display.py | 22 +- gitlint-core/gitlint/tests/test_hooks.py | 68 +-- gitlint-core/gitlint/tests/test_lint.py | 188 +++++---- gitlint-core/gitlint/tests/test_options.py | 15 +- gitlint-core/gitlint/tests/test_utils.py | 15 +- gitlint-core/gitlint/utils.py | 12 +- gitlint-core/setup.py | 50 +-- 61 files changed, 2370 insertions(+), 1333 deletions(-) create mode 100644 gitlint-core/gitlint/contrib/rules/authors_commit.py create mode 100644 gitlint-core/gitlint/contrib/rules/disallow_cleanup_commits.py create mode 100644 gitlint-core/gitlint/deprecation.py create mode 100644 gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py create mode 100644 gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/fixup_amend create mode 100644 gitlint-core/gitlint/tests/samples/config/AUTHORS create mode 100644 gitlint-core/gitlint/tests/test_deprecation.py (limited to 'gitlint-core') diff --git a/gitlint-core/gitlint/__init__.py b/gitlint-core/gitlint/__init__.py index fd86b3e..1317d75 100644 --- a/gitlint-core/gitlint/__init__.py +++ b/gitlint-core/gitlint/__init__.py @@ -1 +1 @@ -__version__ = "0.17.0" +__version__ = "0.18.0" diff --git a/gitlint-core/gitlint/cache.py b/gitlint-core/gitlint/cache.py index 1b6558f..b84c904 100644 --- a/gitlint-core/gitlint/cache.py +++ b/gitlint-core/gitlint/cache.py @@ -1,31 +1,31 @@ class PropertyCache: - """ Mixin class providing a simple cache. """ + """Mixin class providing a simple cache.""" def __init__(self): self._cache = {} def _try_cache(self, cache_key, cache_populate_func): - """ Tries to get a value from the cache identified by `cache_key`. - If no value is found in the cache, do a function call to `cache_populate_func` to populate the cache - and then return the value from the cache. """ + """Tries to get a value from the cache identified by `cache_key`. + If no value is found in the cache, do a function call to `cache_populate_func` to populate the cache + and then return the value from the cache.""" if cache_key not in self._cache: cache_populate_func() return self._cache[cache_key] def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument - """ Cache decorator. Caches function return values. - Requires the parent class to extend and initialize PropertyCache. - Usage: - # Use function name as cache key - @cache - def myfunc(args): - ... - - # Specify cache key - @cache(cachekey="foobar") - def myfunc(args): - ... + """Cache decorator. Caches function return values. + Requires the parent class to extend and initialize PropertyCache. + Usage: + # Use function name as cache key + @cache + def myfunc(args): + ... + + # Specify cache key + @cache(cachekey="foobar") + def myfunc(args): + ... """ # Decorators with optional arguments are a bit convoluted in python, see some of the links below for details. @@ -41,6 +41,7 @@ def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument def cache_func_result(): # Call decorated function and store its result in the cache args[0]._cache[cachekey] = func(*args) + return args[0]._try_cache(cachekey, cache_func_result) return wrapped diff --git a/gitlint-core/gitlint/cli.py b/gitlint-core/gitlint/cli.py index 19676b3..387072e 100644 --- a/gitlint-core/gitlint/cli.py +++ b/gitlint-core/gitlint/cli.py @@ -11,6 +11,7 @@ import click import gitlint from gitlint.lint import GitLinter from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator +from gitlint.deprecation import LOG as DEPRECATED_LOG, DEPRECATED_LOG_FORMAT from gitlint.git import GitContext, GitContextError, git_version from gitlint import hooks from gitlint.shell import shell @@ -37,19 +38,29 @@ LOG = logging.getLogger("gitlint.cli") class GitLintUsageError(GitlintError): - """ Exception indicating there is an issue with how gitlint is used. """ + """Exception indicating there is an issue with how gitlint is used.""" + pass def setup_logging(): - """ Setup gitlint logging """ + """Setup gitlint logging""" + + # Root log, mostly used for debug root_log = logging.getLogger("gitlint") root_log.propagate = False # Don't propagate to child loggers, the gitlint root logger handles everything + root_log.setLevel(logging.ERROR) handler = logging.StreamHandler() formatter = logging.Formatter(LOG_FORMAT) handler.setFormatter(formatter) root_log.addHandler(handler) - root_log.setLevel(logging.ERROR) + + # Deprecated log, to log deprecation warnings + DEPRECATED_LOG.propagate = False # Don't propagate to child logger + DEPRECATED_LOG.setLevel(logging.WARNING) + deprecated_log_handler = logging.StreamHandler() + deprecated_log_handler.setFormatter(logging.Formatter(DEPRECATED_LOG_FORMAT)) + DEPRECATED_LOG.addHandler(deprecated_log_handler) def log_system_info(): @@ -62,10 +73,20 @@ def log_system_info(): def build_config( # pylint: disable=too-many-arguments - target, config_path, c, extra_path, ignore, contrib, ignore_stdin, staged, fail_without_commits, verbose, - silent, debug + target, + config_path, + c, + extra_path, + ignore, + contrib, + ignore_stdin, + staged, + fail_without_commits, + verbose, + silent, + debug, ): - """ Creates a LintConfig object based on a set of commandline parameters. """ + """Creates a LintConfig object based on a set of commandline parameters.""" config_builder = LintConfigBuilder() # Config precedence: # First, load default config or config from configfile @@ -79,33 +100,33 @@ def build_config( # pylint: disable=too-many-arguments # Finally, overwrite with any convenience commandline flags if ignore: - config_builder.set_option('general', 'ignore', ignore) + config_builder.set_option("general", "ignore", ignore) if contrib: - config_builder.set_option('general', 'contrib', contrib) + config_builder.set_option("general", "contrib", contrib) if ignore_stdin: - config_builder.set_option('general', 'ignore-stdin', ignore_stdin) + config_builder.set_option("general", "ignore-stdin", ignore_stdin) if silent: - config_builder.set_option('general', 'verbosity', 0) + config_builder.set_option("general", "verbosity", 0) elif verbose > 0: - config_builder.set_option('general', 'verbosity', verbose) + config_builder.set_option("general", "verbosity", verbose) if extra_path: - config_builder.set_option('general', 'extra-path', extra_path) + config_builder.set_option("general", "extra-path", extra_path) if target: - config_builder.set_option('general', 'target', target) + config_builder.set_option("general", "target", target) if debug: - config_builder.set_option('general', 'debug', debug) + config_builder.set_option("general", "debug", debug) if staged: - config_builder.set_option('general', 'staged', staged) + config_builder.set_option("general", "staged", staged) if fail_without_commits: - config_builder.set_option('general', 'fail-without-commits', fail_without_commits) + config_builder.set_option("general", "fail-without-commits", fail_without_commits) config = config_builder.build() @@ -113,7 +134,7 @@ def build_config( # pylint: disable=too-many-arguments def get_stdin_data(): - """ Helper function that returns data send to stdin or False if nothing is send """ + """Helper function that returns data sent to stdin or False if nothing is sent""" # STDIN can only be 3 different types of things ("modes") # 1. An interactive terminal device (i.e. a TTY -> sys.stdin.isatty() or stat.S_ISCHR) # 2. A (named) pipe (stat.S_ISFIFO) @@ -145,13 +166,17 @@ def get_stdin_data(): def build_git_context(lint_config, msg_filename, commit_hash, refspec): - """ Builds a git context based on passed parameters and order of precedence """ + """Builds a git context based on passed parameters and order of precedence""" # Determine which GitContext method to use if a custom message is passed from_commit_msg = GitContext.from_commit_msg if lint_config.staged: LOG.debug("Fetching additional meta-data from staged commit") - from_commit_msg = lambda message: GitContext.from_staged_commit(message, lint_config.target) # noqa + from_commit_msg = ( + lambda message: GitContext.from_staged_commit( # pylint: disable=unnecessary-lambda-assignment + message, lint_config.target + ) + ) # Order of precedence: # 1. Any data specified via --msg-filename @@ -168,8 +193,10 @@ def build_git_context(lint_config, msg_filename, commit_hash, refspec): return from_commit_msg(stdin_input) if lint_config.staged: - raise GitLintUsageError("The 'staged' option (--staged) can only be used when using '--msg-filename' or " - "when piping data to gitlint via stdin.") + raise GitLintUsageError( + "The 'staged' option (--staged) can only be used when using '--msg-filename' or " + "when piping data to gitlint via stdin." + ) # 3. Fallback to reading from local repository LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Using the local repo.") @@ -177,11 +204,25 @@ def build_git_context(lint_config, msg_filename, commit_hash, refspec): if commit_hash and refspec: raise GitLintUsageError("--commit and --commits are mutually exclusive, use one or the other.") - return GitContext.from_local_repository(lint_config.target, refspec=refspec, commit_hash=commit_hash) + # 3.1 Linting a range of commits + if refspec: + # 3.1.1 Not real refspec, but comma-separated list of commit hashes + if "," in refspec: + commit_hashes = [hash.strip() for hash in refspec.split(",")] + return GitContext.from_local_repository(lint_config.target, commit_hashes=commit_hashes) + # 3.1.2 Real refspec + return GitContext.from_local_repository(lint_config.target, refspec=refspec) + + # 3.2 Linting a specific commit + if commit_hash: + return GitContext.from_local_repository(lint_config.target, commit_hashes=[commit_hash]) + + # 3.3 Fallback to linting the current HEAD + return GitContext.from_local_repository(lint_config.target) def handle_gitlint_error(ctx, exc): - """ Helper function to handle exceptions """ + """Helper function to handle exceptions""" if isinstance(exc, GitContextError): click.echo(exc) ctx.exit(GIT_CONTEXT_ERROR_CODE) @@ -194,7 +235,7 @@ def handle_gitlint_error(ctx, exc): class ContextObj: - """ Simple class to hold data that is passed between Click commands via the Click context. """ + """Simple class to hold data that is passed between Click commands via the Click context.""" def __init__(self, config, config_builder, commit_hash, refspec, msg_filename, gitcontext=None): self.config = config @@ -205,29 +246,34 @@ class ContextObj: self.gitcontext = gitcontext +# fmt: off @click.group(invoke_without_command=True, context_settings={'max_content_width': 120}, epilog="When no COMMAND is specified, gitlint defaults to 'gitlint lint'.") @click.option('--target', envvar='GITLINT_TARGET', type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True), help="Path of the target git repository. [default: current working directory]") -@click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True), +@click.option('-C', '--config', envvar='GITLINT_CONFIG', + type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True), help=f"Config file location [default: {DEFAULT_CONFIG_FILE}]") @click.option('-c', multiple=True, help="Config flags in format .