Merging upstream version 0.19.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
61e6dccee9
commit
2efee3d3ab
111 changed files with 2058 additions and 1676 deletions
|
@ -1 +0,0 @@
|
|||
../LICENSE
|
22
gitlint-core/LICENSE
Normal file
22
gitlint-core/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
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.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
include README.md
|
||||
include LICENSE
|
||||
recursive-exclude gitlint/tests *
|
|
@ -1 +0,0 @@
|
|||
../README.md
|
26
gitlint-core/README.md
Normal file
26
gitlint-core/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Gitlint-core
|
||||
|
||||
# gitlint: [jorisroovers.github.io/gitlint](http://jorisroovers.github.io/gitlint/) #
|
||||
|
||||
[](https://github.com/jorisroovers/gitlint/actions?query=workflow%3A%22Tests+and+Checks%22)
|
||||
[](https://coveralls.io/github/jorisroovers/gitlint?branch=fix-coveralls)
|
||||
[](https://pypi.python.org/pypi/gitlint)
|
||||

|
||||
|
||||
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).
|
|
@ -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")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.config import LintConfigBuilder, LintConfigError, LintConfigGenerator
|
||||
from gitlint.deprecation import LOG as DEPRECATED_LOG, DEPRECATED_LOG_FORMAT
|
||||
from gitlint.git import GitContext, GitContextError, git_version
|
||||
from gitlint import hooks
|
||||
from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator
|
||||
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.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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import re
|
|||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
from gitlint.rules import CommitRule, RuleViolation
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
|
||||
|
||||
LOG = logging.getLogger("gitlint.deprecated")
|
||||
DEPRECATED_LOG_FORMAT = "%(levelname)s: %(message)s"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
class GitlintError(Exception):
|
||||
"""Based Exception class for all gitlint exceptions"""
|
||||
|
||||
pass
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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}))"
|
||||
|
|
|
@ -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__}' "
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
@ -105,6 +99,40 @@ class CLITests(BaseTestCase):
|
|||
self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_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_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, _):
|
||||
|
@ -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")
|
||||
|
|
|
@ -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 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)
|
||||
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)
|
||||
# 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}")
|
||||
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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from gitlint import rules
|
||||
from gitlint.config import RuleCollection
|
||||
from gitlint.tests.base import BaseTestCase
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import os
|
||||
|
||||
from gitlint.tests.base import BaseTestCase
|
||||
from gitlint.contrib import rules as contrib_rules
|
||||
from gitlint.tests.contrib import rules as contrib_tests
|
||||
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
|
||||
|
||||
|
||||
class ContribRuleTests(BaseTestCase):
|
||||
|
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import os
|
||||
from unittest.mock import call, patch
|
||||
|
||||
from unittest.mock import patch, call
|
||||
|
||||
from gitlint.shell import ErrorReturnCode, CommandNotFound
|
||||
|
||||
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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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(.*)',"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
from gitlint.tests.base import BaseTestCase
|
||||
from gitlint.rules import (
|
||||
TitleMaxLength,
|
||||
TitleTrailingWhitespace,
|
||||
TitleHardTab,
|
||||
TitleMustNotContainWord,
|
||||
TitleTrailingPunctuation,
|
||||
TitleLeadingWhitespace,
|
||||
TitleRegexMatches,
|
||||
RuleViolation,
|
||||
TitleHardTab,
|
||||
TitleLeadingWhitespace,
|
||||
TitleMaxLength,
|
||||
TitleMinLength,
|
||||
TitleMustNotContainWord,
|
||||
TitleRegexMatches,
|
||||
TitleTrailingPunctuation,
|
||||
TitleTrailingWhitespace,
|
||||
)
|
||||
from gitlint.tests.base import BaseTestCase
|
||||
|
||||
|
||||
class TitleRuleTests(BaseTestCase):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,4 +9,4 @@ class InitFileRule(CommitRule):
|
|||
options_spec = []
|
||||
|
||||
def validate(self, _commit):
|
||||
return []
|
||||
return [] # pragma: nocover
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 (
|
||||
COMMIT_MSG_HOOK_DST_PATH,
|
||||
COMMIT_MSG_HOOK_SRC_PATH,
|
||||
GITLINT_HOOK_IDENTIFIER,
|
||||
GitHookInstaller,
|
||||
GitHookInstallerError,
|
||||
COMMIT_MSG_HOOK_SRC_PATH,
|
||||
COMMIT_MSG_HOOK_DST_PATH,
|
||||
GITLINT_HOOK_IDENTIFIER,
|
||||
)
|
||||
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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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 default_encoding
|
||||
return preferred_encoding
|
||||
|
||||
|
||||
DEFAULT_ENCODING = getpreferredencoding()
|
||||
TERMINAL_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"
|
||||
|
|
71
gitlint-core/pyproject.toml
Normal file
71
gitlint-core/pyproject.toml
Normal file
|
@ -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"
|
|
@ -1,2 +0,0 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue