summaryrefslogtreecommitdiffstats
path: root/gitlint-core
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2023-03-11 08:03:07 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2023-03-11 08:03:07 +0000
commitdcc28a9a987457acf9e2c8249a9df5e40143eba3 (patch)
treea3b44db00ff34f0dee0406875e7320c4dce3041e /gitlint-core
parentReleasing debian version 0.19.0~dev-1. (diff)
downloadgitlint-dcc28a9a987457acf9e2c8249a9df5e40143eba3.tar.xz
gitlint-dcc28a9a987457acf9e2c8249a9df5e40143eba3.zip
Merging upstream version 0.19.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gitlint-core')
-rw-r--r--[l---------]gitlint-core/LICENSE23
-rw-r--r--gitlint-core/MANIFEST.in3
-rw-r--r--[l---------]gitlint-core/README.md27
-rw-r--r--gitlint-core/gitlint/__init__.py9
-rw-r--r--gitlint-core/gitlint/cache.py2
-rw-r--r--gitlint-core/gitlint/cli.py70
-rw-r--r--gitlint-core/gitlint/config.py35
-rw-r--r--gitlint-core/gitlint/contrib/rules/authors_commit.py1
-rw-r--r--gitlint-core/gitlint/deprecation.py1
-rw-r--r--gitlint-core/gitlint/display.py14
-rw-r--r--gitlint-core/gitlint/exception.py2
-rw-r--r--gitlint-core/gitlint/git.py22
-rw-r--r--gitlint-core/gitlint/hooks.py10
-rw-r--r--gitlint-core/gitlint/lint.py4
-rw-r--r--gitlint-core/gitlint/options.py3
-rw-r--r--gitlint-core/gitlint/rule_finder.py27
-rw-r--r--gitlint-core/gitlint/rules.py38
-rw-r--r--gitlint-core/gitlint/shell.py19
-rw-r--r--gitlint-core/gitlint/tests/base.py49
-rw-r--r--gitlint-core/gitlint/tests/cli/test_cli.py101
-rw-r--r--gitlint-core/gitlint/tests/cli/test_cli_hooks.py135
-rw-r--r--gitlint-core/gitlint/tests/config/test_config.py12
-rw-r--r--gitlint-core/gitlint/tests/config/test_config_builder.py9
-rw-r--r--gitlint-core/gitlint/tests/config/test_config_precedence.py6
-rw-r--r--gitlint-core/gitlint/tests/config/test_rule_collection.py1
-rw-r--r--gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py9
-rw-r--r--gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py6
-rw-r--r--gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py7
-rw-r--r--gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py7
-rw-r--r--gitlint-core/gitlint/tests/contrib/test_contrib_rules.py4
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_112
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_23
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_18
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_26
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_26
-rw-r--r--gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_23
-rw-r--r--gitlint-core/gitlint/tests/git/test_git.py16
-rw-r--r--gitlint-core/gitlint/tests/git/test_git_commit.py22
-rw-r--r--gitlint-core/gitlint/tests/git/test_git_context.py4
-rw-r--r--gitlint-core/gitlint/tests/rules/test_body_rules.py6
-rw-r--r--gitlint-core/gitlint/tests/rules/test_configuration_rules.py26
-rw-r--r--gitlint-core/gitlint/tests/rules/test_meta_rules.py5
-rw-r--r--gitlint-core/gitlint/tests/rules/test_rules.py13
-rw-r--r--gitlint-core/gitlint/tests/rules/test_title_rules.py14
-rw-r--r--gitlint-core/gitlint/tests/rules/test_user_rules.py23
-rw-r--r--gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py4
-rw-r--r--gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py2
-rw-r--r--gitlint-core/gitlint/tests/test_cache.py2
-rw-r--r--gitlint-core/gitlint/tests/test_deprecation.py5
-rw-r--r--gitlint-core/gitlint/tests/test_display.py5
-rw-r--r--gitlint-core/gitlint/tests/test_hooks.py32
-rw-r--r--gitlint-core/gitlint/tests/test_lint.py7
-rw-r--r--gitlint-core/gitlint/tests/test_options.py17
-rw-r--r--gitlint-core/gitlint/tests/test_utils.py4
-rw-r--r--gitlint-core/gitlint/utils.py36
-rw-r--r--gitlint-core/pyproject.toml71
-rw-r--r--gitlint-core/setup.cfg2
-rw-r--r--gitlint-core/setup.py109
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)