diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-03-11 08:03:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2023-03-11 08:03:03 +0000 |
commit | de139943d8272773b5f19ed824d687b0232b9ba3 (patch) | |
tree | 47e73755bffd41bdde2d59d76cc595f5a1fa75d4 /gitlint-core | |
parent | Adding upstream version 0.19.0~dev. (diff) | |
download | gitlint-26eb11c7fcbb5bebaf58c97b2c41876c3ac4f3c0.tar.xz gitlint-26eb11c7fcbb5bebaf58c97b2c41876c3ac4f3c0.zip |
Adding upstream version 0.19.1.upstream/0.19.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gitlint-core')
58 files changed, 616 insertions, 503 deletions
diff --git a/gitlint-core/LICENSE b/gitlint-core/LICENSE index ea5b606..122bd28 120000..100644 --- a/gitlint-core/LICENSE +++ b/gitlint-core/LICENSE @@ -1 +1,22 @@ -../LICENSE
\ No newline at end of file +The MIT License (MIT) + +Copyright (c) 2015 Joris Roovers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/gitlint-core/MANIFEST.in b/gitlint-core/MANIFEST.in deleted file mode 100644 index 375cec1..0000000 --- a/gitlint-core/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md -include LICENSE -recursive-exclude gitlint/tests * diff --git a/gitlint-core/README.md b/gitlint-core/README.md index 32d46ee..dfbbe7f 120000..100644 --- a/gitlint-core/README.md +++ b/gitlint-core/README.md @@ -1 +1,26 @@ -../README.md
\ No newline at end of file +# Gitlint-core + +# gitlint: [jorisroovers.github.io/gitlint](http://jorisroovers.github.io/gitlint/) # + +[![Tests](https://github.com/jorisroovers/gitlint/workflows/Tests%20and%20Checks/badge.svg)](https://github.com/jorisroovers/gitlint/actions?query=workflow%3A%22Tests+and+Checks%22) +[![Coverage Status](https://coveralls.io/repos/github/jorisroovers/gitlint/badge.svg?branch=fix-coveralls)](https://coveralls.io/github/jorisroovers/gitlint?branch=fix-coveralls) +[![PyPi Package](https://img.shields.io/pypi/v/gitlint.png)](https://pypi.python.org/pypi/gitlint) +![Supported Python Versions](https://img.shields.io/pypi/pyversions/gitlint.svg) + +Git commit message linter written in python, checks your commit messages for style. + +**See [jorisroovers.github.io/gitlint](http://jorisroovers.github.io/gitlint/) for full documentation.** + +<a href="http://jorisroovers.github.io/gitlint/" target="_blank"> +<img src="https://raw.githubusercontent.com/jorisroovers/gitlint/main/docs/images/readme-gitlint.png" /> +</a> + +## Contributing ## +All contributions are welcome and very much appreciated! + +**I'm [looking for contributors](https://github.com/jorisroovers/gitlint/issues/134) that are interested in taking a more active co-maintainer role as it's becoming increasingly difficult for me to find time to maintain gitlint. Please leave a comment in [#134](https://github.com/jorisroovers/gitlint/issues/134) if you're interested!** + +See [jorisroovers.github.io/gitlint/contributing](http://jorisroovers.github.io/gitlint/contributing) for details on +how to get started - it's easy! + +We maintain a [loose project plan on Github Projects](https://github.com/users/jorisroovers/projects/1/views/1). diff --git a/gitlint-core/gitlint/__init__.py b/gitlint-core/gitlint/__init__.py index ad6b570..a2339fd 100644 --- a/gitlint-core/gitlint/__init__.py +++ b/gitlint-core/gitlint/__init__.py @@ -1 +1,8 @@ -__version__ = "0.19.0dev" +import sys + +if sys.version_info >= (3, 8): + from importlib import metadata # pragma: nocover +else: + import importlib_metadata as metadata # pragma: nocover + +__version__ = metadata.version("gitlint-core") diff --git a/gitlint-core/gitlint/cache.py b/gitlint-core/gitlint/cache.py index b84c904..a3dd0c8 100644 --- a/gitlint-core/gitlint/cache.py +++ b/gitlint-core/gitlint/cache.py @@ -13,7 +13,7 @@ class PropertyCache: return self._cache[cache_key] -def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument +def cache(original_func=None, cachekey=None): """Cache decorator. Caches function return values. Requires the parent class to extend and initialize PropertyCache. Usage: diff --git a/gitlint-core/gitlint/cli.py b/gitlint-core/gitlint/cli.py index 387072e..619f006 100644 --- a/gitlint-core/gitlint/cli.py +++ b/gitlint-core/gitlint/cli.py @@ -1,22 +1,22 @@ -# pylint: disable=bad-option-value,wrong-import-position -# We need to disable the import position checks because of the windows check that we need to do below import copy import logging import os import platform import stat import sys + import click import gitlint -from gitlint.lint import GitLinter +from gitlint import hooks from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator -from gitlint.deprecation import LOG as DEPRECATED_LOG, DEPRECATED_LOG_FORMAT +from gitlint.deprecation import DEPRECATED_LOG_FORMAT +from gitlint.deprecation import LOG as DEPRECATED_LOG +from gitlint.exception import GitlintError from gitlint.git import GitContext, GitContextError, git_version -from gitlint import hooks +from gitlint.lint import GitLinter from gitlint.shell import shell from gitlint.utils import LOG_FORMAT -from gitlint.exception import GitlintError # Error codes GITLINT_SUCCESS = 0 @@ -40,8 +40,6 @@ LOG = logging.getLogger("gitlint.cli") class GitLintUsageError(GitlintError): """Exception indicating there is an issue with how gitlint is used.""" - pass - def setup_logging(): """Setup gitlint logging""" @@ -49,7 +47,7 @@ def setup_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) + root_log.setLevel(logging.WARN) handler = logging.StreamHandler() formatter = logging.Formatter(LOG_FORMAT) handler.setFormatter(formatter) @@ -69,10 +67,11 @@ def log_system_info(): LOG.debug("Git version: %s", git_version()) LOG.debug("Gitlint version: %s", gitlint.__version__) LOG.debug("GITLINT_USE_SH_LIB: %s", os.environ.get("GITLINT_USE_SH_LIB", "[NOT SET]")) - LOG.debug("DEFAULT_ENCODING: %s", gitlint.utils.DEFAULT_ENCODING) + LOG.debug("TERMINAL_ENCODING: %s", gitlint.utils.TERMINAL_ENCODING) + LOG.debug("FILE_ENCODING: %s", gitlint.utils.FILE_ENCODING) -def build_config( # pylint: disable=too-many-arguments +def build_config( target, config_path, c, @@ -172,11 +171,9 @@ def build_git_context(lint_config, msg_filename, commit_hash, refspec): 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( # pylint: disable=unnecessary-lambda-assignment - message, lint_config.target - ) - ) + + def from_commit_msg(message): + return GitContext.from_staged_commit(message, lint_config.target) # Order of precedence: # 1. Any data specified via --msg-filename @@ -208,7 +205,7 @@ def build_git_context(lint_config, msg_filename, commit_hash, refspec): 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(",")] + commit_hashes = [hash.strip() for hash in refspec.split(",") if hash] 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) @@ -247,43 +244,43 @@ class ContextObj: # fmt: off -@click.group(invoke_without_command=True, context_settings={'max_content_width': 120}, +@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', +@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', envvar='GITLINT_CONFIG', +@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, +@click.option("-c", multiple=True, help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " + - "Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation -@click.option('--commit', envvar='GITLINT_COMMIT', default=None, help="Hash (SHA) of specific commit to lint.") -@click.option('--commits', envvar='GITLINT_COMMITS', default=None, + "Flag can be used multiple times to set multiple config values.") +@click.option("--commit", envvar="GITLINT_COMMIT", default=None, help="Hash (SHA) of specific commit to lint.") +@click.option("--commits", envvar="GITLINT_COMMITS", default=None, help="The range of commits (refspec or comma-separated hashes) to lint. [default: HEAD]") -@click.option('-e', '--extra-path', envvar='GITLINT_EXTRA_PATH', +@click.option("-e", "--extra-path", envvar="GITLINT_EXTRA_PATH", help="Path to a directory or python module with extra user-defined rules", type=click.Path(exists=True, resolve_path=True, readable=True)) -@click.option('--ignore', envvar='GITLINT_IGNORE', default="", help="Ignore rules (comma-separated by id or name).") -@click.option('--contrib', envvar='GITLINT_CONTRIB', default="", +@click.option("--ignore", envvar="GITLINT_IGNORE", default="", help="Ignore rules (comma-separated by id or name).") +@click.option("--contrib", envvar="GITLINT_CONTRIB", default="", help="Contrib rules to enable (comma-separated by id or name).") -@click.option('--msg-filename', type=click.File(encoding=gitlint.utils.DEFAULT_ENCODING), +@click.option("--msg-filename", type=click.File(encoding=gitlint.utils.FILE_ENCODING), help="Path to a file containing a commit-msg.") -@click.option('--ignore-stdin', envvar='GITLINT_IGNORE_STDIN', is_flag=True, +@click.option("--ignore-stdin", envvar="GITLINT_IGNORE_STDIN", is_flag=True, help="Ignore any stdin data. Useful for running in CI server.") -@click.option('--staged', envvar='GITLINT_STAGED', is_flag=True, +@click.option("--staged", envvar="GITLINT_STAGED", is_flag=True, help="Attempt smart guesses about meta info (like author name, email, branch, changed files, etc) " + "for staged commits.") -@click.option('--fail-without-commits', envvar='GITLINT_FAIL_WITHOUT_COMMITS', is_flag=True, +@click.option("--fail-without-commits", envvar="GITLINT_FAIL_WITHOUT_COMMITS", is_flag=True, help="Hard fail when the target commit range is empty.") -@click.option('-v', '--verbose', envvar='GITLINT_VERBOSITY', count=True, default=0, - help="Verbosity, more v's for more verbose output (e.g.: -v, -vv, -vvv). [default: -vvv]", ) -@click.option('-s', '--silent', envvar='GITLINT_SILENT', is_flag=True, +@click.option("-v", "--verbose", envvar="GITLINT_VERBOSITY", count=True, default=0, + help="Verbosity, use multiple times for more verbose output (e.g.: -v, -vv, -vvv). [default: -vvv]", ) +@click.option("-s", "--silent", envvar="GITLINT_SILENT", is_flag=True, help="Silent mode (no output). Takes precedence over -v, -vv, -vvv.") -@click.option('-d', '--debug', envvar='GITLINT_DEBUG', help="Enable debugging output.", is_flag=True) +@click.option("-d", "--debug", envvar="GITLINT_DEBUG", help="Enable debugging output.", is_flag=True) @click.version_option(version=gitlint.__version__) @click.pass_context -def cli( # pylint: disable=too-many-arguments +def cli( ctx, target, config, c, commit, commits, extra_path, ignore, contrib, msg_filename, ignore_stdin, staged, fail_without_commits, verbose, silent, debug, @@ -499,5 +496,4 @@ def generate_config(ctx): # Let's Party! setup_logging() if __name__ == "__main__": - # pylint: disable=no-value-for-parameter cli() # pragma: no cover diff --git a/gitlint-core/gitlint/config.py b/gitlint-core/gitlint/config.py index f038d4a..4b38d90 100644 --- a/gitlint-core/gitlint/config.py +++ b/gitlint-core/gitlint/config.py @@ -1,17 +1,19 @@ -from configparser import ConfigParser, Error as ConfigParserError - import copy -import re import os +import re import shutil - from collections import OrderedDict -from gitlint.utils import DEFAULT_ENCODING -from gitlint import rules # For some weird reason pylint complains about this, pylint: disable=unused-import -from gitlint import options -from gitlint import rule_finder +from configparser import ConfigParser +from configparser import Error as ConfigParserError + +from gitlint import ( + options, + rule_finder, + rules, +) from gitlint.contrib import rules as contrib_rules from gitlint.exception import GitlintError +from gitlint.utils import FILE_ENCODING def handle_option_error(func): @@ -31,7 +33,7 @@ class LintConfigError(GitlintError): pass -class LintConfig: # pylint: disable=too-many-instance-attributes +class LintConfig: """Class representing gitlint configuration. Contains active config as well as number of methods to easily get/set the config. """ @@ -105,7 +107,7 @@ class LintConfig: # pylint: disable=too-many-instance-attributes @handle_option_error def verbosity(self, value): self._verbosity.set(value) - if self.verbosity < 0 or self.verbosity > 3: + if self.verbosity < 0 or self.verbosity > 3: # noqa: PLR2004 (Magic value used in comparison) raise LintConfigError("Option 'verbosity' must be set between 0 and 3") @property @@ -294,7 +296,7 @@ class LintConfig: # pylint: disable=too-many-instance-attributes if not hasattr(self, attr_name) or attr_name[0] == "_": raise LintConfigError(f"'{option_name}' is not a valid gitlint option") - # else: + # else setattr(self, attr_name, option_value) def __eq__(self, other): @@ -384,7 +386,7 @@ class RuleCollection: """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 + for rule in list(self._rules.values()): if hasattr(rule, attr_name) and (getattr(rule, attr_name) == attr_val): del self._rules[rule.id] @@ -466,7 +468,7 @@ class LintConfigBuilder: try: parser = ConfigParser() - with open(filename, encoding=DEFAULT_ENCODING) as config_file: + with open(filename, encoding=FILE_ENCODING) as config_file: parser.read_file(config_file, filename) for section_name in parser.sections(): @@ -528,14 +530,15 @@ class LintConfigBuilder: for section_name, section_dict in self._config_blueprint.items(): for option_name, option_value in section_dict.items(): + qualified_section_name = section_name # Skip over the general section, as we've already done that above - if section_name != "general": + if qualified_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: - section_name = self._add_named_rule(config, section_name) + qualified_section_name = self._add_named_rule(config, qualified_section_name) - config.set_rule_option(section_name, option_name, option_value) + config.set_rule_option(qualified_section_name, option_name, option_value) return config diff --git a/gitlint-core/gitlint/contrib/rules/authors_commit.py b/gitlint-core/gitlint/contrib/rules/authors_commit.py index ce11663..5c4a150 100644 --- a/gitlint-core/gitlint/contrib/rules/authors_commit.py +++ b/gitlint-core/gitlint/contrib/rules/authors_commit.py @@ -2,7 +2,6 @@ import re from pathlib import Path from typing import Tuple - from gitlint.rules import CommitRule, RuleViolation diff --git a/gitlint-core/gitlint/deprecation.py b/gitlint-core/gitlint/deprecation.py index bf13460..b7c2f42 100644 --- a/gitlint-core/gitlint/deprecation.py +++ b/gitlint-core/gitlint/deprecation.py @@ -1,6 +1,5 @@ import logging - LOG = logging.getLogger("gitlint.deprecated") DEPRECATED_LOG_FORMAT = "%(levelname)s: %(message)s" diff --git a/gitlint-core/gitlint/display.py b/gitlint-core/gitlint/display.py index d21b6c3..1de8d08 100644 --- a/gitlint-core/gitlint/display.py +++ b/gitlint-core/gitlint/display.py @@ -1,4 +1,4 @@ -from sys import stdout, stderr +from sys import stderr, stdout class Display: @@ -17,20 +17,20 @@ class Display: if self.config.verbosity >= verbosity: stream.write(message + "\n") - def v(self, message, exact=False): # pylint: disable=invalid-name + def v(self, message, exact=False): self._output(message, 1, exact, stdout) - def vv(self, message, exact=False): # pylint: disable=invalid-name + def vv(self, message, exact=False): self._output(message, 2, exact, stdout) - def vvv(self, message, exact=False): # pylint: disable=invalid-name + def vvv(self, message, exact=False): self._output(message, 3, exact, stdout) - def e(self, message, exact=False): # pylint: disable=invalid-name + def e(self, message, exact=False): self._output(message, 1, exact, stderr) - def ee(self, message, exact=False): # pylint: disable=invalid-name + def ee(self, message, exact=False): self._output(message, 2, exact, stderr) - def eee(self, message, exact=False): # pylint: disable=invalid-name + def eee(self, message, exact=False): self._output(message, 3, exact, stderr) diff --git a/gitlint-core/gitlint/exception.py b/gitlint-core/gitlint/exception.py index bcba54e..d1e8c9c 100644 --- a/gitlint-core/gitlint/exception.py +++ b/gitlint-core/gitlint/exception.py @@ -1,4 +1,2 @@ class GitlintError(Exception): """Based Exception class for all gitlint exceptions""" - - pass diff --git a/gitlint-core/gitlint/git.py b/gitlint-core/gitlint/git.py index 4b292f0..6612a7d 100644 --- a/gitlint-core/gitlint/git.py +++ b/gitlint-core/gitlint/git.py @@ -5,13 +5,12 @@ from pathlib import Path import arrow from gitlint import shell as sh +from gitlint.cache import PropertyCache, cache +from gitlint.exception import GitlintError # import exceptions separately, this makes it a little easier to mock them out in the unit tests from gitlint.shell import CommandNotFound, ErrorReturnCode -from gitlint.cache import PropertyCache, cache -from gitlint.exception import GitlintError - # For now, the git date format we use is fixed, but technically this format is determined by `git config log.date` # We should fix this at some point :-) GIT_TIMEFORMAT = "YYYY-MM-DD HH:mm:ss Z" @@ -22,8 +21,6 @@ LOG = logging.getLogger(__name__) class GitContextError(GitlintError): """Exception indicating there is an issue with the git context""" - pass - class GitNotInstalledError(GitContextError): def __init__(self): @@ -46,7 +43,7 @@ def _git(*command_parts, **kwargs): git_kwargs.update(kwargs) try: LOG.debug(command_parts) - result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg + result = sh.git(*command_parts, **git_kwargs) # If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't # get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting # a non-zero exit code -> just return the entire result @@ -80,7 +77,7 @@ def git_commentchar(repository_path=None): """Shortcut for retrieving comment char from git config""" commentchar = _git("config", "--get", "core.commentchar", _cwd=repository_path, _ok_code=[0, 1]) # git will return an exit code of 1 if it can't find a config value, in this case we fall-back to # as commentchar - if hasattr(commentchar, "exit_code") and commentchar.exit_code == 1: # pylint: disable=no-member + if hasattr(commentchar, "exit_code") and commentchar.exit_code == 1: commentchar = "#" return commentchar.replace("\n", "") @@ -174,11 +171,6 @@ class GitChangedFileStats: def __str__(self) -> str: return f"{self.filepath}: {self.additions} additions, {self.deletions} deletions" - def __repr__(self) -> str: - return ( - f'GitChangedFileStats(filepath="{self.filepath}", additions={self.additions}, deletions={self.deletions})' - ) - class GitCommit: """Class representing a git commit. @@ -193,7 +185,7 @@ class GitCommit: message, sha=None, date=None, - author_name=None, # pylint: disable=too-many-arguments + author_name=None, author_email=None, parents=None, changed_files_stats=None, @@ -289,7 +281,7 @@ class LocalGitCommit(GitCommit, PropertyCache): startup time and reduces gitlint's memory footprint. """ - def __init__(self, context, sha): # pylint: disable=super-init-not-called + def __init__(self, context, sha): PropertyCache.__init__(self) self.context = context self.sha = sha @@ -382,7 +374,7 @@ class StagedLocalGitCommit(GitCommit, PropertyCache): information. """ - def __init__(self, context, commit_message): # pylint: disable=super-init-not-called + def __init__(self, context, commit_message): PropertyCache.__init__(self) self.context = context self.message = commit_message diff --git a/gitlint-core/gitlint/hooks.py b/gitlint-core/gitlint/hooks.py index 78c5e46..98ded18 100644 --- a/gitlint-core/gitlint/hooks.py +++ b/gitlint-core/gitlint/hooks.py @@ -1,10 +1,10 @@ -import shutil import os +import shutil import stat -from gitlint.utils import DEFAULT_ENCODING -from gitlint.git import git_hooks_dir from gitlint.exception import GitlintError +from gitlint.git import git_hooks_dir +from gitlint.utils import FILE_ENCODING COMMIT_MSG_HOOK_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "commit-msg") COMMIT_MSG_HOOK_DST_PATH = "commit-msg" @@ -52,9 +52,9 @@ class GitHookInstaller: if not os.path.exists(dest_path): raise GitHookInstallerError(f"There is no commit-msg hook present in {dest_path}.") - with open(dest_path, encoding=DEFAULT_ENCODING) as fp: + with open(dest_path, encoding=FILE_ENCODING) as fp: lines = fp.readlines() - if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER: + if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER: # noqa: PLR2004 (Magic value used in comparison) msg = ( f"The commit-msg hook in {dest_path} was not installed by gitlint (or it was modified).\n" "Uninstallation of 3th party or modified gitlint hooks is not supported." diff --git a/gitlint-core/gitlint/lint.py b/gitlint-core/gitlint/lint.py index 3bc1945..420d3ad 100644 --- a/gitlint-core/gitlint/lint.py +++ b/gitlint-core/gitlint/lint.py @@ -1,7 +1,7 @@ -# pylint: disable=logging-not-lazy import logging -from gitlint import rules as gitlint_rules + from gitlint import display +from gitlint import rules as gitlint_rules from gitlint.deprecation import Deprecation LOG = logging.getLogger(__name__) diff --git a/gitlint-core/gitlint/options.py b/gitlint-core/gitlint/options.py index 50565ea..ff7d9f1 100644 --- a/gitlint-core/gitlint/options.py +++ b/gitlint-core/gitlint/options.py @@ -1,6 +1,6 @@ -from abc import abstractmethod import os import re +from abc import abstractmethod from gitlint.exception import GitlintError @@ -37,7 +37,6 @@ class RuleOption: @abstractmethod def set(self, value): """Validates and sets the option's value""" - pass # pragma: no cover def __str__(self): return f"({self.name}: {self.value} ({self.description}))" diff --git a/gitlint-core/gitlint/rule_finder.py b/gitlint-core/gitlint/rule_finder.py index 11665cf..810faa9 100644 --- a/gitlint-core/gitlint/rule_finder.py +++ b/gitlint-core/gitlint/rule_finder.py @@ -1,10 +1,10 @@ import fnmatch +import importlib import inspect import os import sys -import importlib -from gitlint import rules, options +from gitlint import options, rules def find_rule_classes(extra_path): @@ -55,7 +55,7 @@ def find_rule_classes(extra_path): importlib.import_module(module) except Exception as e: - raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {e}") + raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {e}") from e # Find all rule classes in the module. We do this my inspecting all members of the module and checking # 1) is it a class, if not, skip @@ -67,11 +67,7 @@ def find_rule_classes(extra_path): for _, clazz in inspect.getmembers(sys.modules[module]) if inspect.isclass(clazz) # check isclass to ensure clazz.__module__ exists and clazz.__module__ == module # ignore imported classes - and ( - issubclass(clazz, rules.LineRule) - or issubclass(clazz, rules.CommitRule) - or issubclass(clazz, rules.ConfigurationRule) - ) + and (issubclass(clazz, (rules.LineRule, rules.CommitRule, rules.ConfigurationRule))) ] ) @@ -82,7 +78,7 @@ def find_rule_classes(extra_path): return rule_classes -def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable=too-many-branches +def assert_valid_rule_class(clazz, rule_type="User-defined"): # noqa: PLR0912 (too many branches) """ Asserts that a given rule clazz is valid by checking a number of its properties: - Rules must extend from LineRule, CommitRule or ConfigurationRule @@ -97,11 +93,7 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable """ # Rules must extend from LineRule, CommitRule or ConfigurationRule - if not ( - issubclass(clazz, rules.LineRule) - or issubclass(clazz, rules.CommitRule) - or issubclass(clazz, rules.ConfigurationRule) - ): + if not issubclass(clazz, (rules.LineRule, rules.CommitRule, rules.ConfigurationRule)): msg = ( f"{rule_type} rule class '{clazz.__name__}' " f"must extend from {rules.CommitRule.__module__}.{rules.LineRule.__name__}, " @@ -142,17 +134,18 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable # Line/Commit rules must have a `validate` method # We use isroutine() as it's both python 2 and 3 compatible. Details: http://stackoverflow.com/a/17019998/381010 - if issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule): + if issubclass(clazz, (rules.LineRule, rules.CommitRule)): if not hasattr(clazz, "validate") or not inspect.isroutine(clazz.validate): raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'validate' method") + # Configuration rules must have an `apply` method - elif issubclass(clazz, rules.ConfigurationRule): + elif issubclass(clazz, rules.ConfigurationRule): # noqa: SIM102 if not hasattr(clazz, "apply") or not inspect.isroutine(clazz.apply): msg = f"{rule_type} Configuration rule class '{clazz.__name__}' must have an 'apply' method" raise rules.UserRuleError(msg) # LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody - if issubclass(clazz, rules.LineRule): + if issubclass(clazz, rules.LineRule): # noqa: SIM102 if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]: msg = ( f"The target attribute of the {rule_type.lower()} LineRule class '{clazz.__name__}' " diff --git a/gitlint-core/gitlint/rules.py b/gitlint-core/gitlint/rules.py index 6d486a5..ca4a05b 100644 --- a/gitlint-core/gitlint/rules.py +++ b/gitlint-core/gitlint/rules.py @@ -1,11 +1,10 @@ -# pylint: disable=inconsistent-return-statements import copy import logging import re -from gitlint.options import IntOption, BoolOption, StrOption, ListOption, RegexOption -from gitlint.exception import GitlintError from gitlint.deprecation import Deprecation +from gitlint.exception import GitlintError +from gitlint.options import BoolOption, IntOption, ListOption, RegexOption, StrOption class Rule: @@ -50,40 +49,28 @@ class Rule: class ConfigurationRule(Rule): """Class representing rules that can dynamically change the configuration of gitlint during runtime.""" - pass - class CommitRule(Rule): """Class representing rules that act on an entire commit at once""" - pass - class LineRule(Rule): """Class representing rules that act on a line by line basis""" - pass - class LineRuleTarget: """Base class for LineRule targets. A LineRuleTarget specifies where a given rule will be applied (e.g. commit message title, commit message body). Each LineRule MUST have a target specified.""" - pass - class CommitMessageTitle(LineRuleTarget): """Target class used for rules that apply to a commit message title""" - pass - class CommitMessageBody(LineRuleTarget): """Target class used for rules that apply to a commit message body""" - pass - class RuleViolation: """Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class @@ -107,8 +94,6 @@ class RuleViolation: class UserRuleError(GitlintError): """Error used to indicate that an error occurred while trying to load a user rule""" - pass - class MaxLineLength(LineRule): name = "max-line-length" @@ -305,7 +290,7 @@ class BodyMissing(CommitRule): # 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 or not "".join(commit.message.body).strip(): + if len(commit.message.body) < 2 or not "".join(commit.message.body).strip(): # noqa: PLR2004 (Magic value) return [RuleViolation(self.id, "Body message is missing", None, 3)] @@ -319,7 +304,7 @@ class BodyChangedFileMention(CommitRule): 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 in commit.changed_files: # noqa: SIM102 if needs_mentioned_file not in " ".join(commit.message.body): violation_message = f"Body does not mention changed file '{needs_mentioned_file}'" violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1)) @@ -370,7 +355,7 @@ class AuthorValidEmail(CommitRule): # We're replacing regex match with search semantics, see https://github.com/jorisroovers/gitlint/issues/254 # In case the user is using the default regex, we can silently change to using search # If not, it depends on config (handled by Deprecation class) - if self.DEFAULT_AUTHOR_VALID_EMAIL_REGEX == self.options["regex"].value.pattern: + if self.options["regex"].value.pattern == self.DEFAULT_AUTHOR_VALID_EMAIL_REGEX: regex_method = self.options["regex"].value.search else: regex_method = Deprecation.get_regex_method(self, self.options["regex"]) @@ -458,7 +443,7 @@ class IgnoreBodyLines(ConfigurationRule): new_body.append(line) commit.message.body = new_body - commit.message.full = "\n".join([commit.message.title] + new_body) + commit.message.full = "\n".join([commit.message.title, *new_body]) class IgnoreByAuthorName(ConfigurationRule): @@ -474,6 +459,17 @@ class IgnoreByAuthorName(ConfigurationRule): if not self.options["regex"].value: return + # If commit.author_name is not available, log warning and return + if commit.author_name is None: + warning_msg = ( + "%s - %s: skipping - commit.author_name unknown. " + "Suggested fix: Use the --staged flag (or set general.staged=True in .gitlint). " + "More details: https://jorisroovers.com/gitlint/configuration/#staged" + ) + + self.log.warning(warning_msg, self.name, self.id) + return + regex_method = Deprecation.get_regex_method(self, self.options["regex"]) if regex_method(commit.author_name): diff --git a/gitlint-core/gitlint/shell.py b/gitlint-core/gitlint/shell.py index c378c1c..fddece0 100644 --- a/gitlint-core/gitlint/shell.py +++ b/gitlint-core/gitlint/shell.py @@ -5,7 +5,8 @@ capabilities wrt dealing with more edge-case environments on *nix systems that a """ import subprocess -from gitlint.utils import USE_SH_LIB, DEFAULT_ENCODING + +from gitlint.utils import TERMINAL_ENCODING, USE_SH_LIB def shell(cmd): @@ -15,17 +16,17 @@ def shell(cmd): if USE_SH_LIB: - from sh import git # pylint: disable=unused-import,import-error - # import exceptions separately, this makes it a little easier to mock them out in the unit tests - from sh import CommandNotFound, ErrorReturnCode # pylint: disable=import-error + from sh import ( + CommandNotFound, + ErrorReturnCode, + git, + ) else: class CommandNotFound(Exception): """Exception indicating a command was not found during execution""" - pass - class ShResult: """Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using the builtin subprocess module""" @@ -42,14 +43,12 @@ else: class ErrorReturnCode(ShResult, Exception): """ShResult subclass for unexpected results (acts as an exception).""" - pass - def git(*command_parts, **kwargs): """Git shell wrapper. Implemented as separate function here, so we can do a 'sh' style imports: `from shell import git` """ - args = ["git"] + list(command_parts) + args = ["git", *list(command_parts)] return _exec(*args, **kwargs) def _exec(*args, **kwargs): @@ -65,7 +64,7 @@ else: raise CommandNotFound from e exit_code = p.returncode - stdout = result[0].decode(DEFAULT_ENCODING) + stdout = result[0].decode(TERMINAL_ENCODING) stderr = result[1] # 'sh' does not decode the stderr bytes to unicode full_cmd = "" if args is None else " ".join(args) diff --git a/gitlint-core/gitlint/tests/base.py b/gitlint-core/gitlint/tests/base.py index 710efe2..3899a5f 100644 --- a/gitlint-core/gitlint/tests/base.py +++ b/gitlint-core/gitlint/tests/base.py @@ -5,15 +5,15 @@ import os import re import shutil import tempfile - import unittest - +from pathlib import Path from unittest.mock import patch from gitlint.config import LintConfig -from gitlint.deprecation import Deprecation, LOG as DEPRECATION_LOG -from gitlint.git import GitContext, GitChangedFileStats -from gitlint.utils import LOG_FORMAT, DEFAULT_ENCODING +from gitlint.deprecation import LOG as DEPRECATION_LOG +from gitlint.deprecation import Deprecation +from gitlint.git import GitChangedFileStats, GitContext +from gitlint.utils import FILE_ENCODING, LOG_FORMAT EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING = ( "WARNING: gitlint.deprecated.regex_style_search {0} - {1}: gitlint will be switching from using " @@ -30,10 +30,28 @@ class BaseTestCase(unittest.TestCase): # In case of assert failures, print the full error message maxDiff = None + # Working directory in which tests in this class are executed + working_dir = None + # Originally working dir when the test was started + original_working_dir = None + SAMPLES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "samples") EXPECTED_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected") GITLINT_USE_SH_LIB = os.environ.get("GITLINT_USE_SH_LIB", "[NOT SET]") + @classmethod + def setUpClass(cls): + # Run tests a temporary directory to shield them from any local git config + cls.original_working_dir = os.getcwd() + cls.working_dir = tempfile.mkdtemp() + os.chdir(cls.working_dir) + + @classmethod + def tearDownClass(cls): + # Go back to original working dir and remove our temp working dir + os.chdir(cls.original_working_dir) + shutil.rmtree(cls.working_dir) + def setUp(self): self.logcapture = LogCapture() self.logcapture.setFormatter(logging.Formatter(LOG_FORMAT)) @@ -77,9 +95,7 @@ class BaseTestCase(unittest.TestCase): def get_sample(filename=""): """Read and return the contents of a file in gitlint/tests/samples""" sample_path = BaseTestCase.get_sample_path(filename) - with open(sample_path, encoding=DEFAULT_ENCODING) as content: - sample = content.read() - return sample + return Path(sample_path).read_text(encoding=FILE_ENCODING) @staticmethod def patch_input(side_effect): @@ -93,8 +109,7 @@ class BaseTestCase(unittest.TestCase): """Utility method to read an expected file from gitlint/tests/expected and return it as a string. Optionally replace template variables specified by variable_dict.""" expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename) - with open(expected_path, encoding=DEFAULT_ENCODING) as content: - expected = content.read() + expected = Path(expected_path).read_text(encoding=FILE_ENCODING) if variable_dict: expected = expected.format(**variable_dict) @@ -150,22 +165,24 @@ class BaseTestCase(unittest.TestCase): self.logcapture.clear() @contextlib.contextmanager - def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name + def assertRaisesMessage(self, expected_exception, expected_msg): """Asserts an exception has occurred with a given error message""" try: yield except expected_exception as exc: exception_msg = str(exc) - if exception_msg != expected_msg: + if exception_msg != expected_msg: # pragma: nocover error = f"Right exception, wrong message:\n got: {exception_msg}\n expected: {expected_msg}" - raise self.fail(error) + raise self.fail(error) from exc # else: everything is fine, just return return - except Exception as exc: - raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'") + except Exception as exc: # pragma: nocover + raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'") from exc # No exception raised while we expected one - raise self.fail(f"Expected to raise {expected_exception.__name__}, didn't get an exception at all") + raise self.fail( + f"Expected to raise {expected_exception.__name__}, didn't get an exception at all" + ) # pragma: nocover def object_equality_test(self, obj, attr_list, ctor_kwargs=None): """Helper function to easily implement object equality tests. diff --git a/gitlint-core/gitlint/tests/cli/test_cli.py b/gitlint-core/gitlint/tests/cli/test_cli.py index d18efe9..c006375 100644 --- a/gitlint-core/gitlint/tests/cli/test_cli.py +++ b/gitlint-core/gitlint/tests/cli/test_cli.py @@ -1,22 +1,15 @@ -import io import os -import sys import platform - -import arrow - +import sys from io import StringIO - -from click.testing import CliRunner - from unittest.mock import patch +import arrow +from click.testing import CliRunner +from gitlint import __version__, cli from gitlint.shell import CommandNotFound - from gitlint.tests.base import BaseTestCase -from gitlint import cli -from gitlint import __version__ -from gitlint.utils import DEFAULT_ENCODING +from gitlint.utils import FILE_ENCODING, TERMINAL_ENCODING class CLITests(BaseTestCase): @@ -46,7 +39,8 @@ class CLITests(BaseTestCase): "gitlint_version": __version__, "GITLINT_USE_SH_LIB": BaseTestCase.GITLINT_USE_SH_LIB, "target": os.path.realpath(os.getcwd()), - "DEFAULT_ENCODING": DEFAULT_ENCODING, + "TERMINAL_ENCODING": TERMINAL_ENCODING, + "FILE_ENCODING": FILE_ENCODING, } def test_version(self): @@ -107,6 +101,40 @@ class CLITests(BaseTestCase): @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") + def test_lint_multiple_commits_csv(self, sh, _): + """Test for --commits option""" + + # fmt: off + sh.git.side_effect = [ + "6f29bf81a8322a04071bb794666e48c443a90360\n", # git rev-list <SHA> + "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n", + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", + # git log --pretty <FORMAT> <SHA> + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "3\t5\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + # git log --pretty <FORMAT> <SHA> + "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" + "commït-title2\n\ncommït-body2", + "8\t3\tcommit-2/file-1\n1\t5\tcommit-2/file-2\n", # git diff-tree + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> + # git log --pretty <FORMAT> <SHA> + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" + "commït-title3\n\ncommït-body3", + "7\t2\tcommit-3/file-1\n1\t7\tcommit-3/file-2\n", # git diff-tree + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> + ] + # fmt: on + + with patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["--commits", "6f29bf81,25053cce,4da2656b"]) + self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_csv_1")) + self.assertEqual(result.exit_code, 3) + + @patch("gitlint.cli.get_stdin_data", return_value=False) + @patch("gitlint.git.sh") def test_lint_multiple_commits_config(self, sh, _): """Test for --commits option where some of the commits have gitlint config in the commit message""" @@ -225,8 +253,7 @@ class CLITests(BaseTestCase): self.assertEqual(result.exit_code, 2) @patch("gitlint.cli.get_stdin_data", return_value=False) - @patch("gitlint.git.sh") - def test_lint_commit_negative(self, sh, _): + def test_lint_commit_negative(self, _): """Negative test for --commit option""" # Try using --commit and --commits at the same time (not allowed) @@ -298,6 +325,11 @@ class CLITests(BaseTestCase): self.assertEqual(result.output, "") expected_kwargs = self.get_system_info_dict() + changed_files_stats = ( + f" {os.path.join('commit-1', 'file-1')}: 1 additions, 5 deletions\n" + f" {os.path.join('commit-1', 'file-2')}: 8 additions, 9 deletions" + ) + expected_kwargs.update({"changed_files_stats": changed_files_stats}) expected_logs = self.get_expected("cli/test_cli/test_lint_staged_stdin_2", expected_kwargs) self.assert_logged(expected_logs) @@ -318,7 +350,7 @@ class CLITests(BaseTestCase): with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: f.write("WIP: msg-filename tïtle\n") with patch("gitlint.display.stderr", new=StringIO()) as stderr: @@ -328,6 +360,11 @@ class CLITests(BaseTestCase): self.assertEqual(result.output, "") expected_kwargs = self.get_system_info_dict() + changed_files_stats = ( + f" {os.path.join('commit-1', 'file-1')}: 3 additions, 4 deletions\n" + f" {os.path.join('commit-1', 'file-2')}: 4 additions, 7 deletions" + ) + expected_kwargs.update({"changed_files_stats": changed_files_stats}) expected_logs = self.get_expected("cli/test_cli/test_lint_staged_msg_filename_2", expected_kwargs) self.assert_logged(expected_logs) @@ -368,7 +405,7 @@ class CLITests(BaseTestCase): with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: f.write("Commït title\n") with patch("gitlint.display.stderr", new=StringIO()) as stderr: @@ -458,6 +495,25 @@ class CLITests(BaseTestCase): self.assertEqual(result.exit_code, 6) expected_kwargs = self.get_system_info_dict() + changed_files_stats1 = ( + f" {os.path.join('commit-1', 'file-1')}: 5 additions, 8 deletions\n" + f" {os.path.join('commit-1', 'file-2')}: 2 additions, 9 deletions" + ) + changed_files_stats2 = ( + f" {os.path.join('commit-2', 'file-1')}: 5 additions, 8 deletions\n" + f" {os.path.join('commit-2', 'file-2')}: 7 additions, 9 deletions" + ) + changed_files_stats3 = ( + f" {os.path.join('commit-3', 'file-1')}: 1 additions, 4 deletions\n" + f" {os.path.join('commit-3', 'file-2')}: 3 additions, 4 deletions" + ) + expected_kwargs.update( + { + "changed_files_stats1": changed_files_stats1, + "changed_files_stats2": changed_files_stats2, + "changed_files_stats3": changed_files_stats3, + } + ) expected_kwargs.update({"config_path": config_path}) expected_logs = self.get_expected("cli/test_cli/test_debug_1", expected_kwargs) self.assert_logged(expected_logs) @@ -548,7 +604,7 @@ class CLITests(BaseTestCase): # Non existing file config_path = self.get_sample_path("föo") result = self.cli.invoke(cli.cli, ["--config", config_path]) - expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' does not exist." + expected_string = f"Error: Invalid value for '-C' / '--config': File {config_path!r} does not exist." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) @@ -569,7 +625,7 @@ class CLITests(BaseTestCase): # Non existing file config_path = self.get_sample_path("föo") result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) - expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' does not exist." + expected_string = f"Error: Invalid value for '-C' / '--config': File {config_path!r} does not exist." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) @@ -578,6 +634,11 @@ class CLITests(BaseTestCase): result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) + def test_config_error(self): + result = self.cli.invoke(cli.cli, ["-c", "foo.bar=hur"]) + self.assertEqual(result.output, "Config Error: No such rule 'foo'\n") + self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) + @patch("gitlint.cli.get_stdin_data", return_value=False) def test_target(self, _): """Test for the --target option""" @@ -602,7 +663,7 @@ class CLITests(BaseTestCase): target_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["--target", target_path]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) - expected_msg = f"Error: Invalid value for '--target': Directory '{target_path}' is a file." + expected_msg = f"Error: Invalid value for '--target': Directory {target_path!r} is a file." self.assertEqual(result.output.split("\n")[3], expected_msg) @patch("gitlint.config.LintConfigGenerator.generate_config") diff --git a/gitlint-core/gitlint/tests/cli/test_cli_hooks.py b/gitlint-core/gitlint/tests/cli/test_cli_hooks.py index d4311c6..c9e4eba 100644 --- a/gitlint-core/gitlint/tests/cli/test_cli_hooks.py +++ b/gitlint-core/gitlint/tests/cli/test_cli_hooks.py @@ -1,18 +1,12 @@ -import io -from io import StringIO import os - -from click.testing import CliRunner - +from io import StringIO from unittest.mock import patch -from gitlint.tests.base import BaseTestCase -from gitlint import cli -from gitlint import hooks -from gitlint import config +from click.testing import CliRunner +from gitlint import cli, config, hooks from gitlint.shell import ErrorReturnCode - -from gitlint.utils import DEFAULT_ENCODING +from gitlint.tests.base import BaseTestCase +from gitlint.utils import FILE_ENCODING class CLIHookTests(BaseTestCase): @@ -108,7 +102,7 @@ class CLIHookTests(BaseTestCase): with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "hür") - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: f.write("WIP: tïtle\n") with patch("gitlint.display.stderr", new=StringIO()) as stderr: @@ -134,68 +128,65 @@ class CLIHookTests(BaseTestCase): # When set_editors[i] == None, ensure we don't fallback to EDITOR set in shell invocating the tests os.environ.pop("EDITOR", None) - with self.patch_input(["e", "e", "n"]): - with self.tempdir() as tmpdir: - msg_filename = os.path.realpath(os.path.join(tmpdir, "hür")) - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: - f.write(commit_messages[i] + "\n") - - with patch("gitlint.display.stderr", new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) - self.assertEqual( - result.output, - self.get_expected( - "cli/test_cli_hooks/test_hook_edit_1_stdout", {"commit_msg": commit_messages[i]} - ), - ) - expected = self.get_expected( - "cli/test_cli_hooks/test_hook_edit_1_stderr", {"commit_msg": commit_messages[i]} - ) - self.assertEqual(stderr.getvalue(), expected) - - # exit code = number of violations - self.assertEqual(result.exit_code, 2) - - shell.assert_called_with(expected_editors[i] + " " + msg_filename) - self.assert_log_contains("DEBUG: gitlint.cli run-hook: editing commit message") - self.assert_log_contains(f"DEBUG: gitlint.cli run-hook: {expected_editors[i]} {msg_filename}") + with self.patch_input(["e", "e", "n"]), self.tempdir() as tmpdir: + msg_filename = os.path.realpath(os.path.join(tmpdir, "hür")) + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: + f.write(commit_messages[i] + "\n") + + with patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) + self.assertEqual( + result.output, + self.get_expected( + "cli/test_cli_hooks/test_hook_edit_1_stdout", {"commit_msg": commit_messages[i]} + ), + ) + expected = self.get_expected( + "cli/test_cli_hooks/test_hook_edit_1_stderr", {"commit_msg": commit_messages[i]} + ) + self.assertEqual(stderr.getvalue(), expected) + + # exit code = number of violations + self.assertEqual(result.exit_code, 2) + + shell.assert_called_with(expected_editors[i] + " " + msg_filename) + self.assert_log_contains("DEBUG: gitlint.cli run-hook: editing commit message") + self.assert_log_contains(f"DEBUG: gitlint.cli run-hook: {expected_editors[i]} {msg_filename}") def test_run_hook_no(self): """Test for run-hook subcommand, answering 'n(o)' after commit-hook""" - with self.patch_input(["n"]): - with self.tempdir() as tmpdir: - msg_filename = os.path.join(tmpdir, "hür") - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: - f.write("WIP: höok no\n") + with self.patch_input(["n"]), self.tempdir() as tmpdir: + msg_filename = os.path.join(tmpdir, "hür") + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: + f.write("WIP: höok no\n") - with patch("gitlint.display.stderr", new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) - self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_no_1_stdout")) - self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_no_1_stderr")) + with patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) + self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_no_1_stdout")) + self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_no_1_stderr")) - # We decided not to keep the commit message: hook returns number of violations (>0) - # This will cause git to abort the commit - self.assertEqual(result.exit_code, 2) - self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined") + # We decided not to keep the commit message: hook returns number of violations (>0) + # This will cause git to abort the commit + self.assertEqual(result.exit_code, 2) + self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined") def test_run_hook_yes(self): """Test for run-hook subcommand, answering 'y(es)' after commit-hook""" - with self.patch_input(["y"]): - with self.tempdir() as tmpdir: - msg_filename = os.path.join(tmpdir, "hür") - with open(msg_filename, "w", encoding=DEFAULT_ENCODING) as f: - f.write("WIP: höok yes\n") + with self.patch_input(["y"]), self.tempdir() as tmpdir: + msg_filename = os.path.join(tmpdir, "hür") + with open(msg_filename, "w", encoding=FILE_ENCODING) as f: + f.write("WIP: höok yes\n") - with patch("gitlint.display.stderr", new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) - self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_yes_1_stdout")) - self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_yes_1_stderr")) + with patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) + self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_yes_1_stdout")) + self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_yes_1_stderr")) - # Exit code is 0 because we decide to keep the commit message - # This will cause git to keep the commit - self.assertEqual(result.exit_code, 0) - self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted") + # Exit code is 0 because we decide to keep the commit message + # This will cause git to keep the commit + self.assertEqual(result.exit_code, 0) + self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted") @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") @@ -207,7 +198,8 @@ class CLIHookTests(BaseTestCase): error_msg = b"fatal: not a git repository (or any of the parent directories): .git" sh.git.side_effect = ErrorReturnCode("full command", b"stdout", error_msg) result = self.cli.invoke(cli.cli, ["run-hook"]) - expected = self.get_expected("cli/test_cli_hooks/test_run_hook_negative_1", {"git_repo": os.getcwd()}) + expected_kwargs = {"git_repo": os.path.realpath(os.getcwd())} + expected = self.get_expected("cli/test_cli_hooks/test_run_hook_negative_1", expected_kwargs) self.assertEqual(result.output, expected) self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) @@ -276,11 +268,10 @@ class CLIHookTests(BaseTestCase): "commit-1-branch-1\ncommit-1-branch-2\n", ] - with self.patch_input(["e"]): - with patch("gitlint.display.stderr", new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["run-hook"]) - expected = self.get_expected("cli/test_cli_hooks/test_hook_local_commit_1_stderr") - self.assertEqual(stderr.getvalue(), expected) - self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_local_commit_1_stdout")) - # If we can't edit the message, run-hook follows regular gitlint behavior and exit code = # violations - self.assertEqual(result.exit_code, 2) + with self.patch_input(["e"]), patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["run-hook"]) + expected = self.get_expected("cli/test_cli_hooks/test_hook_local_commit_1_stderr") + self.assertEqual(stderr.getvalue(), expected) + self.assertEqual(result.output, self.get_expected("cli/test_cli_hooks/test_hook_local_commit_1_stdout")) + # If we can't edit the message, run-hook follows regular gitlint behavior and exit code = # violations + self.assertEqual(result.exit_code, 2) diff --git a/gitlint-core/gitlint/tests/config/test_config.py b/gitlint-core/gitlint/tests/config/test_config.py index 852bf75..439fd93 100644 --- a/gitlint-core/gitlint/tests/config/test_config.py +++ b/gitlint-core/gitlint/tests/config/test_config.py @@ -1,8 +1,12 @@ from unittest.mock import patch -from gitlint import rules -from gitlint.config import LintConfig, LintConfigError, LintConfigGenerator, GITLINT_CONFIG_TEMPLATE_SRC_PATH -from gitlint import options +from gitlint import options, rules +from gitlint.config import ( + GITLINT_CONFIG_TEMPLATE_SRC_PATH, + LintConfig, + LintConfigError, + LintConfigGenerator, +) from gitlint.tests.base import BaseTestCase @@ -166,7 +170,7 @@ class LintConfigTests(BaseTestCase): # UserRuleError, RuleOptionError should be re-raised as LintConfigErrors side_effects = [rules.UserRuleError("üser-rule"), options.RuleOptionError("rüle-option")] for side_effect in side_effects: - with patch("gitlint.config.rule_finder.find_rule_classes", side_effect=side_effect): + with patch("gitlint.config.rule_finder.find_rule_classes", side_effect=side_effect): # noqa: SIM117 with self.assertRaisesMessage(LintConfigError, str(side_effect)): config.contrib = "contrib-title-conventional-commits" diff --git a/gitlint-core/gitlint/tests/config/test_config_builder.py b/gitlint-core/gitlint/tests/config/test_config_builder.py index dfb77cd..ac2a896 100644 --- a/gitlint-core/gitlint/tests/config/test_config_builder.py +++ b/gitlint-core/gitlint/tests/config/test_config_builder.py @@ -1,10 +1,8 @@ import copy -from gitlint.tests.base import BaseTestCase - -from gitlint.config import LintConfig, LintConfigBuilder, LintConfigError - from gitlint import rules +from gitlint.config import LintConfig, LintConfigBuilder, LintConfigError +from gitlint.tests.base import BaseTestCase class LintConfigBuilderTests(BaseTestCase): @@ -256,8 +254,7 @@ class LintConfigBuilderTests(BaseTestCase): my_rule.options["regex"].set("wrong") def test_named_rules_negative(self): - # T7 = title-match-regex - # Invalid rule name + # Invalid rule name (T7 = title-match-regex) for invalid_name in ["", " ", " ", "\t", "\n", "å b", "å:b", "åb:", ":åb"]: config_builder = LintConfigBuilder() config_builder.set_option(f"T7:{invalid_name}", "regex", "tëst") diff --git a/gitlint-core/gitlint/tests/config/test_config_precedence.py b/gitlint-core/gitlint/tests/config/test_config_precedence.py index 22197e8..a7f94cf 100644 --- a/gitlint-core/gitlint/tests/config/test_config_precedence.py +++ b/gitlint-core/gitlint/tests/config/test_config_precedence.py @@ -1,12 +1,10 @@ from io import StringIO - -from click.testing import CliRunner - from unittest.mock import patch -from gitlint.tests.base import BaseTestCase +from click.testing import CliRunner from gitlint import cli from gitlint.config import LintConfigBuilder +from gitlint.tests.base import BaseTestCase class LintConfigPrecedenceTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/config/test_rule_collection.py b/gitlint-core/gitlint/tests/config/test_rule_collection.py index ea7039f..2cb0e5c 100644 --- a/gitlint-core/gitlint/tests/config/test_rule_collection.py +++ b/gitlint-core/gitlint/tests/config/test_rule_collection.py @@ -1,4 +1,5 @@ from collections import OrderedDict + from gitlint import rules from gitlint.config import RuleCollection from gitlint.tests.base import BaseTestCase diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py index 5ea9d8f..2bad2ed 100644 --- a/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py +++ b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py @@ -1,10 +1,10 @@ from collections import namedtuple from unittest.mock import patch -from gitlint.tests.base import BaseTestCase -from gitlint.rules import RuleViolation -from gitlint.config import LintConfig +from gitlint.config import LintConfig from gitlint.contrib.rules.authors_commit import AllowedAuthors +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase class ContribAuthorsCommitTests(BaseTestCase): @@ -101,6 +101,5 @@ class ContribAuthorsCommitTests(BaseTestCase): return_value=False, ) def test_read_authors_file_missing_file(self, _mock_iterdir): - with self.assertRaises(FileNotFoundError) as err: + with self.assertRaisesMessage(FileNotFoundError, "No AUTHORS file found!"): AllowedAuthors._read_authors_from_file(self.gitcontext) - self.assertEqual(err.exception.args[0], "AUTHORS file not found") diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py b/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py index 7ce9c89..cbab684 100644 --- a/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py +++ b/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py @@ -1,7 +1,7 @@ -from gitlint.tests.base import BaseTestCase -from gitlint.rules import RuleViolation -from gitlint.contrib.rules.conventional_commit import ConventionalCommit from gitlint.config import LintConfig +from gitlint.contrib.rules.conventional_commit import ConventionalCommit +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase class ContribConventionalCommitTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py b/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py index 841640a..1983367 100644 --- a/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py +++ b/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py @@ -1,8 +1,7 @@ -from gitlint.tests.base import BaseTestCase -from gitlint.rules import RuleViolation -from gitlint.contrib.rules.disallow_cleanup_commits import DisallowCleanupCommits - from gitlint.config import LintConfig +from gitlint.contrib.rules.disallow_cleanup_commits import DisallowCleanupCommits +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase class ContribDisallowCleanupCommitsTest(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py b/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py index 88ff1db..bf526a0 100644 --- a/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py +++ b/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py @@ -1,8 +1,7 @@ -from gitlint.tests.base import BaseTestCase -from gitlint.rules import RuleViolation -from gitlint.contrib.rules.signedoff_by import SignedOffBy - from gitlint.config import LintConfig +from gitlint.contrib.rules.signedoff_by import SignedOffBy +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase class ContribSignedOffByTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py b/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py index bd098c6..b0372d8 100644 --- a/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py +++ b/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py @@ -1,9 +1,9 @@ import os -from gitlint.tests.base import BaseTestCase +from gitlint import rule_finder, rules from gitlint.contrib import rules as contrib_rules +from gitlint.tests.base import BaseTestCase from gitlint.tests.contrib import rules as contrib_tests -from gitlint import rule_finder, rules class ContribRuleTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_1 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_1 index 4bd3b7d..046294c 100644 --- a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_1 +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_1 @@ -4,7 +4,8 @@ DEBUG: gitlint.cli Python version: {python_version} DEBUG: gitlint.cli Git version: git version 1.2.3 DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} -DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: {config_path} [GENERAL] @@ -88,8 +89,7 @@ Parents: ['a123'] Branches: ['commit-1-branch-1', 'commit-1-branch-2'] Changed Files: ['commit-1/file-1', 'commit-1/file-2'] Changed Files Stats: - commit-1/file-1: 5 additions, 8 deletions - commit-1/file-2: 2 additions, 9 deletions +{changed_files_stats1} ----------------------- DEBUG: gitlint.git ('log', '25053ccec5e28e1bb8f7551fdbb5ab213ada2401', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B') DEBUG: gitlint.lint Linting commit 25053ccec5e28e1bb8f7551fdbb5ab213ada2401 @@ -112,8 +112,7 @@ Parents: ['b123'] Branches: ['commit-2-branch-1', 'commit-2-branch-2'] Changed Files: ['commit-2/file-1', 'commit-2/file-2'] Changed Files Stats: - commit-2/file-1: 5 additions, 8 deletions - commit-2/file-2: 7 additions, 9 deletions +{changed_files_stats2} ----------------------- DEBUG: gitlint.git ('log', '4da2656b0dadc76c7ee3fd0243a96cb64007f125', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B') DEBUG: gitlint.lint Linting commit 4da2656b0dadc76c7ee3fd0243a96cb64007f125 @@ -135,7 +134,6 @@ Parents: ['c123'] Branches: ['commit-3-branch-1', 'commit-3-branch-2'] Changed Files: ['commit-3/file-1', 'commit-3/file-2'] Changed Files Stats: - commit-3/file-1: 1 additions, 4 deletions - commit-3/file-2: 3 additions, 4 deletions +{changed_files_stats3} ----------------------- DEBUG: gitlint.cli Exit Code = 6
\ No newline at end of file diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 index 6d6da43..46a8adf 100644 --- a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 @@ -4,7 +4,8 @@ DEBUG: gitlint.cli Python version: {python_version} DEBUG: gitlint.cli Git version: git version 1.2.3 DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} -DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 new file mode 100644 index 0000000..be3288b --- /dev/null +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 @@ -0,0 +1,8 @@ +Commit 6f29bf81a8: +3: B5 Body message is too short (12<20): "commït-body1" + +Commit 25053ccec5: +3: B5 Body message is too short (12<20): "commït-body2" + +Commit 4da2656b0d: +3: B5 Body message is too short (12<20): "commït-body3" diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 index 59b2414..6b96a45 100644 --- a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 @@ -4,7 +4,8 @@ DEBUG: gitlint.cli Python version: {python_version} DEBUG: gitlint.cli Git version: git version 1.2.3 DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} -DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] @@ -87,7 +88,6 @@ Parents: [] Branches: ['my-branch'] Changed Files: ['commit-1/file-1', 'commit-1/file-2'] Changed Files Stats: - commit-1/file-1: 3 additions, 4 deletions - commit-1/file-2: 4 additions, 7 deletions +{changed_files_stats} ----------------------- DEBUG: gitlint.cli Exit Code = 2
\ No newline at end of file diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 index 23df7b2..45d94e2 100644 --- a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 @@ -4,7 +4,8 @@ DEBUG: gitlint.cli Python version: {python_version} DEBUG: gitlint.cli Git version: git version 1.2.3 DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} -DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] @@ -89,7 +90,6 @@ Parents: [] Branches: ['my-branch'] Changed Files: ['commit-1/file-1', 'commit-1/file-2'] Changed Files Stats: - commit-1/file-1: 1 additions, 5 deletions - commit-1/file-2: 8 additions, 9 deletions +{changed_files_stats} ----------------------- DEBUG: gitlint.cli Exit Code = 3
\ No newline at end of file diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_2 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_2 index c4491f1..f4df46e 100644 --- a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_2 +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_2 @@ -4,7 +4,8 @@ DEBUG: gitlint.cli Python version: {python_version} DEBUG: gitlint.cli Git version: git version 1.2.3 DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} -DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: {config_path} [GENERAL] diff --git a/gitlint-core/gitlint/tests/git/test_git.py b/gitlint-core/gitlint/tests/git/test_git.py index 9c73bd9..b6a146a 100644 --- a/gitlint-core/gitlint/tests/git/test_git.py +++ b/gitlint-core/gitlint/tests/git/test_git.py @@ -1,11 +1,15 @@ import os - -from unittest.mock import patch, call - -from gitlint.shell import ErrorReturnCode, CommandNotFound - +from unittest.mock import call, patch + +from gitlint.git import ( + GitContext, + GitContextError, + GitNotInstalledError, + git_commentchar, + git_hooks_dir, +) +from gitlint.shell import CommandNotFound, ErrorReturnCode from gitlint.tests.base import BaseTestCase -from gitlint.git import GitContext, GitContextError, GitNotInstalledError, git_commentchar, git_hooks_dir class GitTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/git/test_git_commit.py b/gitlint-core/gitlint/tests/git/test_git_commit.py index b27deaf..e6b0b2c 100644 --- a/gitlint-core/gitlint/tests/git/test_git_commit.py +++ b/gitlint-core/gitlint/tests/git/test_git_commit.py @@ -1,25 +1,21 @@ import copy import datetime from pathlib import Path - -import dateutil +from unittest.mock import call, patch import arrow - -from unittest.mock import patch, call - -from gitlint.tests.base import BaseTestCase +import dateutil from gitlint.git import ( GitChangedFileStats, - GitContext, GitCommit, + GitCommitMessage, + GitContext, GitContextError, LocalGitCommit, StagedLocalGitCommit, - GitCommitMessage, - GitChangedFileStats, ) from gitlint.shell import ErrorReturnCode +from gitlint.tests.base import BaseTestCase class GitCommitTests(BaseTestCase): @@ -383,7 +379,7 @@ class GitCommitTests(BaseTestCase): @patch("gitlint.git.sh") def test_get_latest_commit_fixup_squash_commit(self, sh): commit_prefixes = {"fixup": "is_fixup_commit", "squash": "is_squash_commit", "amend": "is_fixup_amend_commit"} - for commit_type in commit_prefixes.keys(): + for commit_type in commit_prefixes: sample_sha = "d8ac47e9f2923c7f22d8668e3a1ed04eb4cdbca9" sh.git.side_effect = [ @@ -616,7 +612,7 @@ class GitCommitTests(BaseTestCase): # mapping between cleanup commit prefixes and the commit object attribute commit_prefixes = {"fixup": "is_fixup_commit", "squash": "is_squash_commit", "amend": "is_fixup_amend_commit"} - for commit_type in commit_prefixes.keys(): + for commit_type in commit_prefixes: commit_msg = f"{commit_type}! Test message" gitcontext = GitContext.from_commit_msg(commit_msg) commit = gitcontext.commits[-1] @@ -642,7 +638,7 @@ class GitCommitTests(BaseTestCase): @patch("gitlint.git.sh") @patch("arrow.now") def test_staged_commit(self, now, sh): - # StagedLocalGitCommit() + """Test for StagedLocalGitCommit()""" sh.git.side_effect = [ "#", # git config --get core.commentchar @@ -744,7 +740,7 @@ class GitCommitTests(BaseTestCase): git.return_value = "foöbar" # Test simple equality case - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.timezone.utc) context1 = GitContext() commit_message1 = GitCommitMessage(context1, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) commit1 = GitCommit( diff --git a/gitlint-core/gitlint/tests/git/test_git_context.py b/gitlint-core/gitlint/tests/git/test_git_context.py index 3dcbe4a..751136c 100644 --- a/gitlint-core/gitlint/tests/git/test_git_context.py +++ b/gitlint-core/gitlint/tests/git/test_git_context.py @@ -1,7 +1,7 @@ -from unittest.mock import patch, call +from unittest.mock import call, patch -from gitlint.tests.base import BaseTestCase from gitlint.git import GitContext +from gitlint.tests.base import BaseTestCase class GitContextTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/rules/test_body_rules.py b/gitlint-core/gitlint/tests/rules/test_body_rules.py index 94b1edf..c142e6e 100644 --- a/gitlint-core/gitlint/tests/rules/test_body_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_body_rules.py @@ -1,5 +1,5 @@ -from gitlint.tests.base import BaseTestCase from gitlint import rules +from gitlint.tests.base import BaseTestCase class BodyRuleTests(BaseTestCase): @@ -100,13 +100,13 @@ class BodyRuleTests(BaseTestCase): expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", "å" * 21, 3) rule = rules.BodyMinLength({"min-length": 120}) - commit = self.gitcommit("Title\n\n{}\n".format("å" * 21)) # pylint: disable=consider-using-f-string + commit = self.gitcommit("Title\n\n{}\n".format("å" * 21)) violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) # Make sure we don't get the error if the body-length is exactly the min-length rule = rules.BodyMinLength({"min-length": 8}) - commit = self.gitcommit("Tïtle\n\n{}\n".format("å" * 8)) # pylint: disable=consider-using-f-string + commit = self.gitcommit("Tïtle\n\n{}\n".format("å" * 8)) violations = rule.validate(commit) self.assertIsNone(violations) diff --git a/gitlint-core/gitlint/tests/rules/test_configuration_rules.py b/gitlint-core/gitlint/tests/rules/test_configuration_rules.py index 9e3b07c..5935a4a 100644 --- a/gitlint-core/gitlint/tests/rules/test_configuration_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_configuration_rules.py @@ -1,6 +1,9 @@ -from gitlint.tests.base import BaseTestCase, EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING from gitlint import rules from gitlint.config import LintConfig +from gitlint.tests.base import ( + EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING, + BaseTestCase, +) class ConfigurationRuleTests(BaseTestCase): @@ -89,6 +92,25 @@ class ConfigurationRuleTests(BaseTestCase): self.assertEqual(config, LintConfig()) self.assert_logged([]) # nothing logged -> nothing ignored + # No author available -> rule is skipped and warning logged + staged_commit = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line") + rule = rules.IgnoreByAuthorName({"regex": "foo"}) + config = LintConfig() + rule.apply(config, staged_commit) + self.assertEqual(config, LintConfig()) + expected_log_messages = [ + "WARNING: gitlint.rules ignore-by-author-name - I4: skipping - commit.author_name unknown. " + "Suggested fix: Use the --staged flag (or set general.staged=True in .gitlint). " + "More details: https://jorisroovers.com/gitlint/configuration/#staged" + ] + self.assert_logged(expected_log_messages) + + # Non-Matching regex -> expect config to stay the same + rule = rules.IgnoreByAuthorName({"regex": "foo"}) + expected_config = LintConfig() + rule.apply(config, commit) + self.assertEqual(config, LintConfig()) + # Matching regex -> expect config to ignore all rules rule = rules.IgnoreByAuthorName({"regex": "(.*)ëst(.*)"}) expected_config = LintConfig() @@ -96,7 +118,7 @@ class ConfigurationRuleTests(BaseTestCase): rule.apply(config, commit) self.assertEqual(config, expected_config) - expected_log_messages = [ + expected_log_messages += [ EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING.format("I4", "ignore-by-author-name"), "DEBUG: gitlint.rules Ignoring commit because of rule 'I4': " "Commit Author Name 'Tëst nåme' matches the regex '(.*)ëst(.*)'," diff --git a/gitlint-core/gitlint/tests/rules/test_meta_rules.py b/gitlint-core/gitlint/tests/rules/test_meta_rules.py index 0b8a10a..a574aa3 100644 --- a/gitlint-core/gitlint/tests/rules/test_meta_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_meta_rules.py @@ -1,5 +1,8 @@ -from gitlint.tests.base import BaseTestCase, EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING from gitlint.rules import AuthorValidEmail, RuleViolation +from gitlint.tests.base import ( + EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING, + BaseTestCase, +) class MetaRuleTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/rules/test_rules.py b/gitlint-core/gitlint/tests/rules/test_rules.py index 199cc7e..b401372 100644 --- a/gitlint-core/gitlint/tests/rules/test_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_rules.py @@ -1,8 +1,12 @@ -from gitlint.tests.base import BaseTestCase from gitlint.rules import Rule, RuleViolation +from gitlint.tests.base import BaseTestCase class RuleTests(BaseTestCase): + def test_ruleviolation__str__(self): + expected = '57: rule-ïd Tēst message: "Tēst content"' + self.assertEqual(str(RuleViolation("rule-ïd", "Tēst message", "Tēst content", 57)), expected) + def test_rule_equality(self): self.assertEqual(Rule(), Rule()) # Ensure rules are not equal if they differ on their attributes @@ -13,9 +17,16 @@ class RuleTests(BaseTestCase): def test_rule_log(self): rule = Rule() + self.assertIsNone(rule._log) rule.log.debug("Tēst message") self.assert_log_contains("DEBUG: gitlint.rules Tēst message") + # Assert the same logger is reused when logging multiple messages + log = rule._log + rule.log.debug("Anöther message") + self.assertEqual(log, rule._log) + self.assert_log_contains("DEBUG: gitlint.rules Anöther message") + def test_rule_violation_equality(self): violation1 = RuleViolation("ïd1", "My messåge", "My cöntent", 1) self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"]) diff --git a/gitlint-core/gitlint/tests/rules/test_title_rules.py b/gitlint-core/gitlint/tests/rules/test_title_rules.py index 4796e54..cba3851 100644 --- a/gitlint-core/gitlint/tests/rules/test_title_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_title_rules.py @@ -1,15 +1,15 @@ -from gitlint.tests.base import BaseTestCase from gitlint.rules import ( - TitleMaxLength, - TitleTrailingWhitespace, + RuleViolation, TitleHardTab, - TitleMustNotContainWord, - TitleTrailingPunctuation, TitleLeadingWhitespace, - TitleRegexMatches, - RuleViolation, + TitleMaxLength, TitleMinLength, + TitleMustNotContainWord, + TitleRegexMatches, + TitleTrailingPunctuation, + TitleTrailingWhitespace, ) +from gitlint.tests.base import BaseTestCase class TitleRuleTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/rules/test_user_rules.py b/gitlint-core/gitlint/tests/rules/test_user_rules.py index fc8d423..8086bea 100644 --- a/gitlint-core/gitlint/tests/rules/test_user_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_user_rules.py @@ -1,11 +1,10 @@ import os import sys -from gitlint.tests.base import BaseTestCase -from gitlint.rule_finder import find_rule_classes, assert_valid_rule_class -from gitlint.rules import UserRuleError - from gitlint import options, rules +from gitlint.rule_finder import assert_valid_rule_class, find_rule_classes +from gitlint.rules import UserRuleError +from gitlint.tests.base import BaseTestCase class UserRuleTests(BaseTestCase): @@ -104,21 +103,21 @@ class UserRuleTests(BaseTestCase): target = rules.CommitMessageTitle def validate(self): - pass + pass # pragma: nocover class MyCommitRuleClass(rules.CommitRule): id = "UC2" name = "my-cömmit-rule" def validate(self): - pass + pass # pragma: nocover class MyConfigurationRuleClass(rules.ConfigurationRule): id = "UC3" name = "my-cönfiguration-rule" def apply(self): - pass + pass # pragma: nocover # Just assert that no error is raised self.assertIsNone(assert_valid_rule_class(MyLineRuleClass)) @@ -203,7 +202,7 @@ class UserRuleTests(BaseTestCase): assert_valid_rule_class(MyRuleClass) # option_spec is a list, but not of gitlint options - MyRuleClass.options_spec = ["föo", 123] # pylint: disable=bad-option-value,redefined-variable-type + MyRuleClass.options_spec = ["föo", 123] with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) @@ -236,8 +235,8 @@ class UserRuleTests(BaseTestCase): with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) - # validate attribute - not a method - MyRuleClass.validate = "föo" + # apply attribute - not a method + MyRuleClass.apply = "föo" with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) @@ -247,7 +246,7 @@ class UserRuleTests(BaseTestCase): name = "my-rüle-class" def validate(self): - pass + pass # pragma: nocover # no target expected_msg = ( @@ -263,5 +262,5 @@ class UserRuleTests(BaseTestCase): assert_valid_rule_class(MyRuleClass) # valid target, no exception should be raised - MyRuleClass.target = rules.CommitMessageTitle # pylint: disable=bad-option-value,redefined-variable-type + MyRuleClass.target = rules.CommitMessageTitle self.assertIsNone(assert_valid_rule_class(MyRuleClass)) diff --git a/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py b/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py index 02c922d..c947250 100644 --- a/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py +++ b/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py @@ -1,5 +1,5 @@ -from gitlint.rules import CommitRule, RuleViolation from gitlint.options import IntOption +from gitlint.rules import CommitRule, RuleViolation class MyUserCommitRule(CommitRule): @@ -19,7 +19,7 @@ class MyUserCommitRule(CommitRule): def func_should_be_ignored(): - pass + pass # pragma: nocover global_variable_should_be_ignored = True diff --git a/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py b/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py index 22c3f65..c2863fe 100644 --- a/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py +++ b/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py @@ -9,4 +9,4 @@ class InitFileRule(CommitRule): options_spec = [] def validate(self, _commit): - return [] + return [] # pragma: nocover diff --git a/gitlint-core/gitlint/tests/test_cache.py b/gitlint-core/gitlint/tests/test_cache.py index 9c327dc..08b821e 100644 --- a/gitlint-core/gitlint/tests/test_cache.py +++ b/gitlint-core/gitlint/tests/test_cache.py @@ -1,5 +1,5 @@ -from gitlint.tests.base import BaseTestCase from gitlint.cache import PropertyCache, cache +from gitlint.tests.base import BaseTestCase class CacheTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/test_deprecation.py b/gitlint-core/gitlint/tests/test_deprecation.py index d85593a..bfe5934 100644 --- a/gitlint-core/gitlint/tests/test_deprecation.py +++ b/gitlint-core/gitlint/tests/test_deprecation.py @@ -1,7 +1,10 @@ from gitlint.config import LintConfig from gitlint.deprecation import Deprecation from gitlint.rules import IgnoreByTitle -from gitlint.tests.base import EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING, BaseTestCase +from gitlint.tests.base import ( + EXPECTED_REGEX_STYLE_SEARCH_DEPRECATION_WARNING, + BaseTestCase, +) class DeprecationTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/test_display.py b/gitlint-core/gitlint/tests/test_display.py index 1f759d2..e669cdb 100644 --- a/gitlint-core/gitlint/tests/test_display.py +++ b/gitlint-core/gitlint/tests/test_display.py @@ -1,9 +1,8 @@ from io import StringIO +from unittest.mock import patch -from unittest.mock import patch # pylint: disable=no-name-in-module, import-error - -from gitlint.display import Display from gitlint.config import LintConfig +from gitlint.display import Display from gitlint.tests.base import BaseTestCase diff --git a/gitlint-core/gitlint/tests/test_hooks.py b/gitlint-core/gitlint/tests/test_hooks.py index f92b148..7390f14 100644 --- a/gitlint-core/gitlint/tests/test_hooks.py +++ b/gitlint-core/gitlint/tests/test_hooks.py @@ -1,16 +1,15 @@ import os +from unittest.mock import ANY, mock_open, patch -from unittest.mock import patch, ANY, mock_open - -from gitlint.tests.base import BaseTestCase from gitlint.config import LintConfig from gitlint.hooks import ( - GitHookInstaller, - GitHookInstallerError, - COMMIT_MSG_HOOK_SRC_PATH, COMMIT_MSG_HOOK_DST_PATH, + COMMIT_MSG_HOOK_SRC_PATH, GITLINT_HOOK_IDENTIFIER, + GitHookInstaller, + GitHookInstallerError, ) +from gitlint.tests.base import BaseTestCase class HookTests(BaseTestCase): @@ -58,9 +57,10 @@ class HookTests(BaseTestCase): expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_not_called() - copy.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_not_called() + copy.assert_not_called() # mock that there is already a commit hook present isdir.return_value = True @@ -106,9 +106,10 @@ class HookTests(BaseTestCase): expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_not_called() - remove.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_not_called() + remove.assert_not_called() # mock that there is no commit hook present isdir.return_value = True @@ -117,9 +118,10 @@ class HookTests(BaseTestCase): expected_msg = f"There is no commit-msg hook present in {expected_dst}." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_called_once_with(expected_dst) - remove.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_called_once_with(expected_dst) + remove.assert_not_called() # mock that there is a different (=not gitlint) commit hook isdir.return_value = True diff --git a/gitlint-core/gitlint/tests/test_lint.py b/gitlint-core/gitlint/tests/test_lint.py index 2af4615..1cf3772 100644 --- a/gitlint-core/gitlint/tests/test_lint.py +++ b/gitlint-core/gitlint/tests/test_lint.py @@ -1,11 +1,10 @@ from io import StringIO +from unittest.mock import patch -from unittest.mock import patch # pylint: disable=no-name-in-module, import-error - -from gitlint.tests.base import BaseTestCase +from gitlint.config import LintConfig, LintConfigBuilder from gitlint.lint import GitLinter from gitlint.rules import RuleViolation, TitleMustNotContainWord -from gitlint.config import LintConfig, LintConfigBuilder +from gitlint.tests.base import BaseTestCase class LintTests(BaseTestCase): diff --git a/gitlint-core/gitlint/tests/test_options.py b/gitlint-core/gitlint/tests/test_options.py index 7b146e7..deff723 100644 --- a/gitlint-core/gitlint/tests/test_options.py +++ b/gitlint-core/gitlint/tests/test_options.py @@ -1,12 +1,23 @@ import os import re +from gitlint.options import ( + BoolOption, + IntOption, + ListOption, + PathOption, + RegexOption, + RuleOptionError, + StrOption, +) from gitlint.tests.base import BaseTestCase -from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOption, RegexOption, RuleOptionError - class RuleOptionTests(BaseTestCase): + def test_option__str__(self): + option = StrOption("tëst-option", "åbc", "Test Dëscription") + self.assertEqual(str(option), "(tëst-option: åbc (Test Dëscription))") + def test_option_equality(self): options = { IntOption: 123, @@ -158,7 +169,7 @@ class RuleOptionTests(BaseTestCase): option = PathOption("tëst-directory", ".", "Tëst Description", type="dir") self.assertEqual(option.name, "tëst-directory") self.assertEqual(option.description, "Tëst Description") - self.assertEqual(option.value, os.getcwd()) + self.assertEqual(option.value, os.path.realpath(".")) self.assertEqual(option.type, "dir") # re-set value diff --git a/gitlint-core/gitlint/tests/test_utils.py b/gitlint-core/gitlint/tests/test_utils.py index 27036d3..d21ec3f 100644 --- a/gitlint-core/gitlint/tests/test_utils.py +++ b/gitlint-core/gitlint/tests/test_utils.py @@ -27,7 +27,7 @@ class UtilsTests(BaseTestCase): self.assertEqual(utils.use_sh_library(), False) @patch("gitlint.utils.locale") - def test_default_encoding_non_windows(self, mocked_locale): + def test_terminal_encoding_non_windows(self, mocked_locale): utils.PLATFORM_IS_WINDOWS = False mocked_locale.getpreferredencoding.return_value = "foöbar" self.assertEqual(utils.getpreferredencoding(), "foöbar") @@ -37,7 +37,7 @@ class UtilsTests(BaseTestCase): self.assertEqual(utils.getpreferredencoding(), "UTF-8") @patch("os.environ") - def test_default_encoding_windows(self, patched_env): + def test_terminal_encoding_windows(self, patched_env): utils.PLATFORM_IS_WINDOWS = True # Mock out os.environ mock_env = {} diff --git a/gitlint-core/gitlint/utils.py b/gitlint-core/gitlint/utils.py index 697b472..3ccb78b 100644 --- a/gitlint-core/gitlint/utils.py +++ b/gitlint-core/gitlint/utils.py @@ -1,9 +1,7 @@ -# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return import codecs -import platform -import os - import locale +import os +import platform # Note: While we can easily inline the logic related to the constants set in this module, we deliberately create # small functions that encapsulate that logic as this enables easy unit testing. In particular, by creating functions @@ -40,30 +38,28 @@ def use_sh_library(): USE_SH_LIB = use_sh_library() ######################################################################################################################## -# DEFAULT_ENCODING +# TERMINAL_ENCODING +# Encoding used for terminal encoding/decoding. def getpreferredencoding(): """Modified version of local.getpreferredencoding() that takes into account LC_ALL, LC_CTYPE, LANG env vars on windows and falls back to UTF-8.""" fallback_encoding = "UTF-8" - default_encoding = locale.getpreferredencoding() or fallback_encoding + preferred_encoding = locale.getpreferredencoding() or fallback_encoding # On Windows, we mimic git/linux by trying to read the LC_ALL, LC_CTYPE, LANG env vars manually # (on Linux/MacOS the `getpreferredencoding()` call will take care of this). # We fallback to UTF-8 if PLATFORM_IS_WINDOWS: - default_encoding = fallback_encoding + preferred_encoding = fallback_encoding for env_var in ["LC_ALL", "LC_CTYPE", "LANG"]: encoding = os.environ.get(env_var, False) if encoding: # Support dotted (C.UTF-8) and non-dotted (C or UTF-8) charsets: # If encoding contains a dot: split and use second part, otherwise use everything dot_index = encoding.find(".") - if dot_index != -1: - default_encoding = encoding[dot_index + 1 :] - else: - default_encoding = encoding + preferred_encoding = encoding[dot_index + 1 :] if dot_index != -1 else encoding break # We've determined what encoding the user *wants*, let's now check if it's actually a valid encoding on the @@ -71,11 +67,21 @@ def getpreferredencoding(): # This scenario is fairly common on Windows where git sets LC_CTYPE=C when invoking the commit-msg hook, which # is not a valid encoding in Python on Windows. try: - codecs.lookup(default_encoding) # pylint: disable=no-member + codecs.lookup(preferred_encoding) except LookupError: - default_encoding = fallback_encoding + preferred_encoding = fallback_encoding + + return preferred_encoding - return default_encoding +TERMINAL_ENCODING = getpreferredencoding() -DEFAULT_ENCODING = getpreferredencoding() +######################################################################################################################## +# FILE_ENCODING +# Gitlint assumes UTF-8 encoding for all file operations: +# - reading/writing its own hook and config files +# - reading/writing git commit messages +# Git does have i18n.commitEncoding and i18n.logOutputEncoding options which we might want to take into account, +# but that's not supported today. + +FILE_ENCODING = "UTF-8" diff --git a/gitlint-core/pyproject.toml b/gitlint-core/pyproject.toml new file mode 100644 index 0000000..e65b7b0 --- /dev/null +++ b/gitlint-core/pyproject.toml @@ -0,0 +1,71 @@ +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "gitlint-core" +dynamic = ["version", "urls"] +description = "Git commit message linter written in python, checks your commit messages for style." +readme = "README.md" +license = "MIT" +requires-python = ">=3.7" +authors = [{ name = "Joris Roovers" }] +keywords = [ + "git", + "gitlint", + "lint", # +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", +] +dependencies = [ + "arrow>=1", + "Click>=8", + "importlib-metadata >= 1.0 ; python_version < \"3.8\"", + "sh>=1.13.0 ; sys_platform != \"win32\"", +] + +[project.optional-dependencies] +trusted-deps = [ + "arrow==1.2.3", + "Click==8.1.3", + "sh==1.14.3 ; sys_platform != \"win32\"", +] + +[project.scripts] +gitlint = "gitlint.cli:cli" + +[tool.hatch.version] +source = "vcs" +raw-options = { root = ".." } + +[tool.hatch.build] +include = [ + "/gitlint", # +] + +exclude = [ + "/gitlint/tests", # +] + +[tool.hatch.metadata.hooks.vcs.urls] +Homepage = "https://jorisroovers.github.io/gitlint" +Documentation = "https://jorisroovers.github.io/gitlint" +Source = "https://github.com/jorisroovers/gitlint/tree/main/gitlint-core" +Changelog = "https://github.com/jorisroovers/gitlint/blob/main/CHANGELOG.md" +# TODO(jorisroovers): Temporary disable until fixed in hatch-vcs (see #460) +# 'Source Commit' = "https://github.com/jorisroovers/gitlint/tree/{commit_hash}/gitlint-core"
\ No newline at end of file diff --git a/gitlint-core/setup.cfg b/gitlint-core/setup.cfg deleted file mode 100644 index 2a9acf1..0000000 --- a/gitlint-core/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/gitlint-core/setup.py b/gitlint-core/setup.py deleted file mode 100644 index 8917e27..0000000 --- a/gitlint-core/setup.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup, find_packages -import io -import re -import os -import platform -import sys - - -description = "Git commit message linter written in python, checks your commit messages for style." -long_description = """ -Great for use as a commit-msg git hook or as part of your gating script in a CI pipeline (e.g. jenkins, github actions). -Many of the gitlint validations are based on `well-known`_ community_ `standards`_, others are based on checks that -we've found useful throughout the years. Gitlint has sane defaults, but you can also easily customize it to your -own liking. - -Demo and full documentation on `jorisroovers.github.io/gitlint`_. -To see what's new in the latest release, visit the CHANGELOG_. - -Source code on `github.com/jorisroovers/gitlint`_. - -.. _well-known: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html -.. _community: http://addamhardy.com/blog/2013/06/05/good-commit-messages-and-enforcing-them-with-git-hooks/ -.. _standards: http://chris.beams.io/posts/git-commit/ -.. _jorisroovers.github.io/gitlint: https://jorisroovers.github.io/gitlint -.. _CHANGELOG: https://github.com/jorisroovers/gitlint/blob/main/CHANGELOG.md -.. _github.com/jorisroovers/gitlint: https://github.com/jorisroovers/gitlint -""" - - -# shamelessly stolen from mkdocs' setup.py: https://github.com/mkdocs/mkdocs/blob/master/setup.py -def get_version(package): - """Return package version as listed in `__version__` in `init.py`.""" - init_py = open(os.path.join(package, "__init__.py"), encoding="UTF-8").read() - return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) - - -setup( - name="gitlint-core", - version=get_version("gitlint"), - description=description, - long_description=long_description, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Environment :: Console", - "Intended Audience :: Developers", - "Topic :: Software Development :: Quality Assurance", - "Topic :: Software Development :: Testing", - "License :: OSI Approved :: MIT License", - ], - python_requires=">=3.6", - install_requires=[ - "Click>=8", - "arrow>=1", - 'sh>=1.13.0 ; sys_platform != "win32"', - ], - extras_require={ - "trusted-deps": [ - "Click==8.0.3", - "arrow==1.2.1", - 'sh==1.14.2 ; sys_platform != "win32"', - ], - }, - keywords="gitlint git lint", - author="Joris Roovers", - url="https://jorisroovers.github.io/gitlint", - project_urls={ - "Documentation": "https://jorisroovers.github.io/gitlint", - "Source": "https://github.com/jorisroovers/gitlint", - }, - license="MIT", - package_data={"gitlint": ["files/*"]}, - packages=find_packages(exclude=["examples"]), - entry_points={ - "console_scripts": [ - "gitlint = gitlint.cli:cli", - ], - }, -) - -# Print a red deprecation warning for python < 3.6 users -if sys.version_info[:2] < (3, 6): - msg = ( - "\033[31mDEPRECATION: You're using a python version that has reached end-of-life. " - + "Gitlint does not support Python < 3.6" - + "Please upgrade your Python to 3.6 or above.\033[0m" - ) - print(msg) - -# Print a warning message for Windows users -PLATFORM_IS_WINDOWS = "windows" in platform.system().lower() -if PLATFORM_IS_WINDOWS: - msg = ( - "\n\n\n\n\n****************\n" - + "WARNING: Gitlint support for Windows is still experimental and there are some known issues: " - + "https://github.com/jorisroovers/gitlint/issues?q=is%3Aissue+is%3Aopen+label%3Awindows " - + "\n*******************" - ) - print(msg) |