summaryrefslogtreecommitdiffstats
path: root/gitlint
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-01-25 13:26:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-01-25 13:26:35 +0000
commit4fee3e091a8d79a40f70ff9c1f87b29b9340049a (patch)
tree465ad9629a8ee56548bd6c51c3fc710907564911 /gitlint
parentReleasing debian version 0.14.0-1. (diff)
downloadgitlint-4fee3e091a8d79a40f70ff9c1f87b29b9340049a.tar.xz
gitlint-4fee3e091a8d79a40f70ff9c1f87b29b9340049a.zip
Merging upstream version 0.15.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gitlint')
-rw-r--r--gitlint/__init__.py2
-rw-r--r--gitlint/cache.py24
-rw-r--r--gitlint/cli.py100
-rw-r--r--gitlint/config.py117
-rw-r--r--gitlint/contrib/rules/conventional_commit.py7
-rw-r--r--gitlint/display.py13
-rw-r--r--gitlint/exception.py4
-rw-r--r--gitlint/git.py118
-rw-r--r--gitlint/hooks.py19
-rw-r--r--gitlint/lint.py18
-rw-r--r--gitlint/options.py58
-rw-r--r--gitlint/rule_finder.py49
-rw-r--r--gitlint/rules.py62
-rw-r--r--gitlint/shell.py15
-rw-r--r--gitlint/tests/base.py60
-rw-r--r--gitlint/tests/cli/test_cli.py234
-rw-r--r--gitlint/tests/cli/test_cli_hooks.py121
-rw-r--r--gitlint/tests/config/test_config.py69
-rw-r--r--gitlint/tests/config/test_config_builder.py80
-rw-r--r--gitlint/tests/config/test_config_precedence.py30
-rw-r--r--gitlint/tests/config/test_rule_collection.py22
-rw-r--r--gitlint/tests/contrib/rules/test_conventional_commit.py20
-rw-r--r--gitlint/tests/contrib/rules/test_signedoff_by.py6
-rw-r--r--gitlint/tests/contrib/test_contrib_rules.py9
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_12
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_22
-rw-r--r--gitlint/tests/git/test_git.py45
-rw-r--r--gitlint/tests/git/test_git_commit.py230
-rw-r--r--gitlint/tests/git/test_git_context.py55
-rw-r--r--gitlint/tests/rules/test_body_rules.py92
-rw-r--r--gitlint/tests/rules/test_configuration_rules.py46
-rw-r--r--gitlint/tests/rules/test_meta_rules.py30
-rw-r--r--gitlint/tests/rules/test_rules.py8
-rw-r--r--gitlint/tests/rules/test_title_rules.py90
-rw-r--r--gitlint/tests/rules/test_user_rules.py48
-rw-r--r--gitlint/tests/samples/user_rules/my_commit_rules.py6
-rw-r--r--gitlint/tests/samples/user_rules/parent_package/__init__.py2
-rw-r--r--gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py2
-rw-r--r--gitlint/tests/test_cache.py22
-rw-r--r--gitlint/tests/test_display.py55
-rw-r--r--gitlint/tests/test_hooks.py35
-rw-r--r--gitlint/tests/test_lint.py110
-rw-r--r--gitlint/tests/test_options.py113
-rw-r--r--gitlint/tests/test_utils.py39
-rw-r--r--gitlint/utils.py47
45 files changed, 1056 insertions, 1280 deletions
diff --git a/gitlint/__init__.py b/gitlint/__init__.py
index 9e78220..9da2f8f 100644
--- a/gitlint/__init__.py
+++ b/gitlint/__init__.py
@@ -1 +1 @@
-__version__ = "0.14.0"
+__version__ = "0.15.0"
diff --git a/gitlint/cache.py b/gitlint/cache.py
index b7f9e6c..1b6558f 100644
--- a/gitlint/cache.py
+++ b/gitlint/cache.py
@@ -1,4 +1,4 @@
-class PropertyCache(object):
+class PropertyCache:
""" Mixin class providing a simple cache. """
def __init__(self):
@@ -13,7 +13,7 @@ class PropertyCache(object):
return self._cache[cache_key]
-def cache(original_func=None, cachekey=None):
+def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument
""" Cache decorator. Caches function return values.
Requires the parent class to extend and initialize PropertyCache.
Usage:
@@ -28,27 +28,23 @@ def cache(original_func=None, cachekey=None):
...
"""
- # Decorators with optional arguments are a bit convoluted in python, especially if you want to support both
- # Python 2 and 3. See some of the links below for details.
+ # Decorators with optional arguments are a bit convoluted in python, see some of the links below for details.
def cache_decorator(func):
-
- # If no specific cache key is given, use the function name as cache key
- if not cache_decorator.cachekey:
- cache_decorator.cachekey = func.__name__
+ # Use 'nonlocal' keyword to access parent function variable:
+ # https://stackoverflow.com/a/14678445/381010
+ nonlocal cachekey
+ if not cachekey:
+ cachekey = func.__name__
def wrapped(*args):
def cache_func_result():
# Call decorated function and store its result in the cache
- args[0]._cache[cache_decorator.cachekey] = func(*args)
- return args[0]._try_cache(cache_decorator.cachekey, cache_func_result)
+ args[0]._cache[cachekey] = func(*args)
+ return args[0]._try_cache(cachekey, cache_func_result)
return wrapped
- # Passing parent function variables to child functions requires special voodoo in python2:
- # https://stackoverflow.com/a/14678445/381010
- cache_decorator.cachekey = cachekey # attribute on the function
-
# To support optional kwargs for decorators, we need to check if a function is passed as first argument or not.
# https://stackoverflow.com/a/24617244/381010
if original_func:
diff --git a/gitlint/cli.py b/gitlint/cli.py
index f284792..b162e5b 100644
--- a/gitlint/cli.py
+++ b/gitlint/cli.py
@@ -8,19 +8,20 @@ import stat
import sys
import click
-# Error codes
-MAX_VIOLATION_ERROR_CODE = 252 # noqa
-USAGE_ERROR_CODE = 253 # noqa
-GIT_CONTEXT_ERROR_CODE = 254 # noqa
-CONFIG_ERROR_CODE = 255 # noqa
-
import gitlint
from gitlint.lint import GitLinter
from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator
from gitlint.git import GitContext, GitContextError, git_version
from gitlint import hooks
from gitlint.shell import shell
-from gitlint.utils import ustr, LOG_FORMAT, IS_PY2
+from gitlint.utils import LOG_FORMAT
+from gitlint.exception import GitlintError
+
+# Error codes
+MAX_VIOLATION_ERROR_CODE = 252
+USAGE_ERROR_CODE = 253
+GIT_CONTEXT_ERROR_CODE = 254
+CONFIG_ERROR_CODE = 255
DEFAULT_CONFIG_FILE = ".gitlint"
# -n: disable swap files. This fixes a vim error on windows (E303: Unable to open swap file for <path>)
@@ -34,7 +35,7 @@ click.UsageError.exit_code = USAGE_ERROR_CODE
LOG = logging.getLogger("gitlint.cli")
-class GitLintUsageError(Exception):
+class GitLintUsageError(GitlintError):
""" Exception indicating there is an issue with how gitlint is used. """
pass
@@ -134,7 +135,7 @@ def get_stdin_data():
# Only return the input data if there's actually something passed
# i.e. don't consider empty piped data
if input_data:
- return ustr(input_data)
+ return str(input_data)
return False
@@ -151,7 +152,7 @@ def build_git_context(lint_config, msg_filename, refspec):
# 1. Any data specified via --msg-filename
if msg_filename:
LOG.debug("Using --msg-filename.")
- return from_commit_msg(ustr(msg_filename.read()))
+ return from_commit_msg(str(msg_filename.read()))
# 2. Any data sent to stdin (unless stdin is being ignored)
if not lint_config.ignore_stdin:
@@ -162,15 +163,28 @@ def build_git_context(lint_config, msg_filename, refspec):
return from_commit_msg(stdin_input)
if lint_config.staged:
- raise GitLintUsageError(u"The 'staged' option (--staged) can only be used when using '--msg-filename' or "
- u"when piping data to gitlint via stdin.")
+ raise GitLintUsageError("The 'staged' option (--staged) can only be used when using '--msg-filename' or "
+ "when piping data to gitlint via stdin.")
# 3. Fallback to reading from local repository
LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Using the local repo.")
return GitContext.from_local_repository(lint_config.target, refspec)
-class ContextObj(object):
+def handle_gitlint_error(ctx, exc):
+ """ Helper function to handle exceptions """
+ if isinstance(exc, GitContextError):
+ click.echo(exc)
+ ctx.exit(GIT_CONTEXT_ERROR_CODE)
+ elif isinstance(exc, GitLintUsageError):
+ click.echo(f"Error: {exc}")
+ ctx.exit(USAGE_ERROR_CODE)
+ elif isinstance(exc, LintConfigError):
+ click.echo(f"Config Error: {exc}")
+ ctx.exit(CONFIG_ERROR_CODE)
+
+
+class ContextObj:
""" Simple class to hold data that is passed between Click commands via the Click context. """
def __init__(self, config, config_builder, refspec, msg_filename, gitcontext=None):
@@ -187,7 +201,7 @@ class ContextObj(object):
type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True),
help="Path of the target git repository. [default: current working directory]")
@click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True),
- help="Config file location [default: {0}]".format(DEFAULT_CONFIG_FILE))
+ help=f"Config file location [default: {DEFAULT_CONFIG_FILE}]")
@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
@@ -230,7 +244,7 @@ def cli( # pylint: disable=too-many-arguments
# store it in the context (click allows storing an arbitrary object in ctx.obj).
config, config_builder = build_config(target, config, c, extra_path, ignore, contrib,
ignore_stdin, staged, verbose, silent, debug)
- LOG.debug(u"Configuration\n%s", ustr(config))
+ LOG.debug("Configuration\n%s", config)
ctx.obj = ContextObj(config, config_builder, commits, msg_filename)
@@ -238,15 +252,8 @@ def cli( # pylint: disable=too-many-arguments
if ctx.invoked_subcommand is None:
ctx.invoke(lint)
- except GitContextError as e:
- click.echo(ustr(e))
- ctx.exit(GIT_CONTEXT_ERROR_CODE)
- except GitLintUsageError as e:
- click.echo(u"Error: {0}".format(ustr(e)))
- ctx.exit(USAGE_ERROR_CODE)
- except LintConfigError as e:
- click.echo(u"Config Error: {0}".format(ustr(e)))
- ctx.exit(CONFIG_ERROR_CODE)
+ except GitlintError as e:
+ handle_gitlint_error(ctx, e)
@cli.command("lint")
@@ -294,7 +301,7 @@ def lint(ctx):
if violations:
# Display the commit hash & new lines intelligently
if number_of_commits > 1 and commit.sha:
- linter.display.e(u"{0}Commit {1}:".format(
+ linter.display.e("{0}Commit {1}:".format(
"\n" if not first_violation or commit is last_commit else "",
commit.sha[:10]
))
@@ -315,10 +322,10 @@ def install_hook(ctx):
try:
hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
- click.echo(u"Successfully installed gitlint commit-msg hook in {0}".format(hook_path))
+ click.echo(f"Successfully installed gitlint commit-msg hook in {hook_path}")
ctx.exit(0)
except hooks.GitHookInstallerError as e:
- click.echo(ustr(e), err=True)
+ click.echo(e, err=True)
ctx.exit(GIT_CONTEXT_ERROR_CODE)
@@ -329,10 +336,10 @@ def uninstall_hook(ctx):
try:
hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
- click.echo(u"Successfully uninstalled gitlint commit-msg hook from {0}".format(hook_path))
+ click.echo(f"Successfully uninstalled gitlint commit-msg hook from {hook_path}")
ctx.exit(0)
except hooks.GitHookInstallerError as e:
- click.echo(ustr(e), err=True)
+ click.echo(e, err=True)
ctx.exit(GIT_CONTEXT_ERROR_CODE)
@@ -344,8 +351,10 @@ def run_hook(ctx):
exit_code = 1
while exit_code > 0:
try:
- click.echo(u"gitlint: checking commit message...")
+ click.echo("gitlint: checking commit message...")
ctx.invoke(lint)
+ except GitlintError as e:
+ handle_gitlint_error(ctx, e)
except click.exceptions.Exit as e:
# Flush stderr andstdout, this resolves an issue with output ordering in Cygwin
sys.stderr.flush()
@@ -353,11 +362,11 @@ def run_hook(ctx):
exit_code = e.exit_code
if exit_code == 0:
- click.echo(u"gitlint: " + click.style("OK", fg='green') + u" (no violations in commit message)")
+ click.echo("gitlint: " + click.style("OK", fg='green') + " (no violations in commit message)")
continue
- click.echo(u"-----------------------------------------------")
- click.echo(u"gitlint: " + click.style("Your commit message contains the above violations.", fg='red'))
+ click.echo("-----------------------------------------------")
+ click.echo("gitlint: " + click.style("Your commit message contains the above violations.", fg='red'))
value = None
while value not in ["y", "n", "e"]:
@@ -374,14 +383,7 @@ def run_hook(ctx):
# - https://github.com/pallets/click/pull/1372
# - From https://click.palletsprojects.com/en/7.x/utils/#getting-characters-from-terminal
# Note that this function will always read from the terminal, even if stdin is instead a pipe.
- #
- # We also need a to use raw_input() in Python2 as input() is unsafe (and raw_input() doesn't exist in
- # Python3). See https://stackoverflow.com/a/4960216/381010
- input_func = input
- if IS_PY2:
- input_func = raw_input # noqa pylint: disable=undefined-variable
-
- value = input_func()
+ value = input()
if value == "y":
LOG.debug("run-hook: commit message accepted")
@@ -396,15 +398,15 @@ def run_hook(ctx):
LOG.debug("run-hook: %s %s", editor, msg_filename_path)
shell(editor + " " + msg_filename_path)
else:
- click.echo(u"Editing only possible when --msg-filename is specified.")
+ click.echo("Editing only possible when --msg-filename is specified.")
ctx.exit(exit_code)
elif value == "n":
LOG.debug("run-hook: commit message declined")
- click.echo(u"Commit aborted.")
- click.echo(u"Your commit message: ")
- click.echo(u"-----------------------------------------------")
+ click.echo("Commit aborted.")
+ click.echo("Your commit message: ")
+ click.echo("-----------------------------------------------")
click.echo(ctx.obj.gitcontext.commits[0].message.full)
- click.echo(u"-----------------------------------------------")
+ click.echo("-----------------------------------------------")
ctx.exit(exit_code)
ctx.exit(exit_code)
@@ -418,14 +420,14 @@ def generate_config(ctx):
path = os.path.realpath(path)
dir_name = os.path.dirname(path)
if not os.path.exists(dir_name):
- click.echo(u"Error: Directory '{0}' does not exist.".format(dir_name), err=True)
+ click.echo(f"Error: Directory '{dir_name}' does not exist.", err=True)
ctx.exit(USAGE_ERROR_CODE)
elif os.path.exists(path):
- click.echo(u"Error: File \"{0}\" already exists.".format(path), err=True)
+ click.echo(f"Error: File \"{path}\" already exists.", err=True)
ctx.exit(USAGE_ERROR_CODE)
LintConfigGenerator.generate_config(path)
- click.echo(u"Successfully generated {0}".format(path))
+ click.echo(f"Successfully generated {path}")
ctx.exit(0)
diff --git a/gitlint/config.py b/gitlint/config.py
index 4dad707..1eeb35d 100644
--- a/gitlint/config.py
+++ b/gitlint/config.py
@@ -1,9 +1,4 @@
-try:
- # python 2.x
- from ConfigParser import ConfigParser, Error as ConfigParserError
-except ImportError: # pragma: no cover
- # python 3.x
- from configparser import ConfigParser, Error as ConfigParserError # pragma: no cover, pylint: disable=import-error
+from configparser import ConfigParser, Error as ConfigParserError
import copy
import io
@@ -12,11 +7,12 @@ import os
import shutil
from collections import OrderedDict
-from gitlint.utils import ustr, sstr, DEFAULT_ENCODING
+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 gitlint.contrib import rules as contrib_rules
+from gitlint.exception import GitlintError
def handle_option_error(func):
@@ -27,16 +23,16 @@ def handle_option_error(func):
try:
return func(*args)
except options.RuleOptionError as e:
- raise LintConfigError(ustr(e))
+ raise LintConfigError(str(e)) from e
return wrapped
-class LintConfigError(Exception):
+class LintConfigError(GitlintError):
pass
-class LintConfig(object):
+class LintConfig:
""" Class representing gitlint configuration.
Contains active config as well as number of methods to easily get/set the config.
"""
@@ -198,7 +194,7 @@ class LintConfig(object):
self.rules.add_rules(rule_classes, {'is_user_defined': True})
except (options.RuleOptionError, rules.UserRuleError) as e:
- raise LintConfigError(ustr(e))
+ raise LintConfigError(str(e)) from e
@property
def contrib(self):
@@ -219,27 +215,25 @@ class LintConfig(object):
# For each specified contrib rule, check whether it exists among the contrib classes
for rule_id_or_name in self.contrib:
rule_class = next((rc for rc in rule_classes if
- rc.id == ustr(rule_id_or_name) or rc.name == ustr(rule_id_or_name)), False)
+ rule_id_or_name in (rc.id, rc.name)), False)
# If contrib rule exists, instantiate it and add it to the rules list
if rule_class:
self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True})
else:
- raise LintConfigError(u"No contrib rule with id or name '{0}' found.".format(ustr(rule_id_or_name)))
+ raise LintConfigError(f"No contrib rule with id or name '{rule_id_or_name}' found.")
except (options.RuleOptionError, rules.UserRuleError) as e:
- raise LintConfigError(ustr(e))
+ raise LintConfigError(str(e)) from e
def _get_option(self, rule_name_or_id, option_name):
- rule_name_or_id = ustr(rule_name_or_id) # convert to unicode first
- option_name = ustr(option_name)
rule = self.rules.find_rule(rule_name_or_id)
if not rule:
- raise LintConfigError(u"No such rule '{0}'".format(rule_name_or_id))
+ raise LintConfigError(f"No such rule '{rule_name_or_id}'")
option = rule.options.get(option_name)
if not option:
- raise LintConfigError(u"Rule '{0}' has no option '{1}'".format(rule_name_or_id, option_name))
+ raise LintConfigError(f"Rule '{rule_name_or_id}' has no option '{option_name}'")
return option
@@ -256,14 +250,14 @@ class LintConfig(object):
try:
option.set(option_value)
except options.RuleOptionError as e:
- msg = u"'{0}' is not a valid value for option '{1}.{2}'. {3}."
- raise LintConfigError(msg.format(option_value, rule_name_or_id, option_name, ustr(e)))
+ msg = f"'{option_value}' is not a valid value for option '{rule_name_or_id}.{option_name}'. {e}."
+ raise LintConfigError(msg) from e
def set_general_option(self, option_name, option_value):
attr_name = option_name.replace("-", "_")
# only allow setting general options that exist and don't start with an underscore
if not hasattr(self, attr_name) or attr_name[0] == "_":
- raise LintConfigError(u"'{0}' is not a valid gitlint option".format(option_name))
+ raise LintConfigError(f"'{option_name}' is not a valid gitlint option")
# else:
setattr(self, attr_name, option_value)
@@ -285,30 +279,26 @@ class LintConfig(object):
self.ignore == other.ignore and \
self._config_path == other._config_path # noqa
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
def __str__(self):
# config-path is not a user exposed variable, so don't print it under the general section
- return_str = u"config-path: {0}\n".format(self._config_path)
- return_str += u"[GENERAL]\n"
- return_str += u"extra-path: {0}\n".format(self.extra_path)
- return_str += u"contrib: {0}\n".format(sstr(self.contrib))
- return_str += u"ignore: {0}\n".format(",".join(self.ignore))
- return_str += u"ignore-merge-commits: {0}\n".format(self.ignore_merge_commits)
- return_str += u"ignore-fixup-commits: {0}\n".format(self.ignore_fixup_commits)
- return_str += u"ignore-squash-commits: {0}\n".format(self.ignore_squash_commits)
- return_str += u"ignore-revert-commits: {0}\n".format(self.ignore_revert_commits)
- return_str += u"ignore-stdin: {0}\n".format(self.ignore_stdin)
- return_str += u"staged: {0}\n".format(self.staged)
- return_str += u"verbosity: {0}\n".format(self.verbosity)
- return_str += u"debug: {0}\n".format(self.debug)
- return_str += u"target: {0}\n".format(self.target)
- return_str += u"[RULES]\n{0}".format(self.rules)
- return return_str
-
-
-class RuleCollection(object):
+ return (f"config-path: {self._config_path}\n"
+ f"[GENERAL]\n"
+ f"extra-path: {self.extra_path}\n"
+ f"contrib: {self.contrib}\n"
+ f"ignore: {','.join(self.ignore)}\n"
+ f"ignore-merge-commits: {self.ignore_merge_commits}\n"
+ f"ignore-fixup-commits: {self.ignore_fixup_commits}\n"
+ f"ignore-squash-commits: {self.ignore_squash_commits}\n"
+ f"ignore-revert-commits: {self.ignore_revert_commits}\n"
+ f"ignore-stdin: {self.ignore_stdin}\n"
+ f"staged: {self.staged}\n"
+ f"verbosity: {self.verbosity}\n"
+ f"debug: {self.debug}\n"
+ f"target: {self.target}\n"
+ f"[RULES]\n{self.rules}")
+
+
+class RuleCollection:
""" Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules. """
def __init__(self, rule_classes=None, rule_attrs=None):
@@ -318,8 +308,6 @@ class RuleCollection(object):
self.add_rules(rule_classes, rule_attrs)
def find_rule(self, rule_id_or_name):
- # try finding rule by id
- rule_id_or_name = ustr(rule_id_or_name) # convert to unicode first
rule = self._rules.get(rule_id_or_name)
# if not found, try finding rule by name
if not rule:
@@ -351,7 +339,7 @@ class RuleCollection(object):
""" 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()]:
+ for rule in [r for r in self._rules.values()]: # pylint: disable=unnecessary-comprehension
if hasattr(rule, attr_name) and (getattr(rule, attr_name) == attr_val):
del self._rules[rule.id]
@@ -362,19 +350,13 @@ class RuleCollection(object):
def __eq__(self, other):
return isinstance(other, RuleCollection) and self._rules == other._rules
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
def __len__(self):
return len(self._rules)
- def __repr__(self):
- return self.__unicode__() # pragma: no cover
-
- def __unicode__(self):
+ def __str__(self):
return_str = ""
for rule in self._rules.values():
- return_str += u" {0}: {1}\n".format(rule.id, rule.name)
+ return_str += f" {rule.id}: {rule.name}\n"
for option_name, option_value in sorted(rule.options.items()):
if option_value.value is None:
option_val_repr = None
@@ -384,11 +366,11 @@ class RuleCollection(object):
option_val_repr = option_value.value.pattern
else:
option_val_repr = option_value.value
- return_str += u" {0}={1}\n".format(option_name, option_val_repr)
+ return_str += f" {option_name}={option_val_repr}\n"
return return_str
-class LintConfigBuilder(object):
+class LintConfigBuilder:
""" Factory class that can build gitlint config.
This is primarily useful to deal with complex configuration scenarios where configuration can be set and overridden
from various sources (typically according to certain precedence rules) before the actual config should be
@@ -427,28 +409,27 @@ class LintConfigBuilder(object):
raise ValueError()
rule_name, option_name = config_name.split(".", 1)
self.set_option(rule_name, option_name, option_value)
- except ValueError: # raised if the config string is invalid
+ except ValueError as e: # raised if the config string is invalid
raise LintConfigError(
- u"'{0}' is an invalid configuration option. Use '<rule>.<option>=<value>'".format(config_option))
+ f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'") from e
def set_from_config_file(self, filename):
""" Loads lint config from a ini-style config file """
if not os.path.exists(filename):
- raise LintConfigError(u"Invalid file path: {0}".format(filename))
+ raise LintConfigError(f"Invalid file path: {filename}")
self._config_path = os.path.realpath(filename)
try:
parser = ConfigParser()
with io.open(filename, encoding=DEFAULT_ENCODING) as config_file:
- # readfp() is deprecated in python 3.2+, but compatible with 2.7
- parser.readfp(config_file, filename) # pylint: disable=deprecated-method
+ parser.read_file(config_file, filename)
for section_name in parser.sections():
for option_name, option_value in parser.items(section_name):
- self.set_option(section_name, option_name, ustr(option_value))
+ self.set_option(section_name, option_name, str(option_value))
except ConfigParserError as e:
- raise LintConfigError(ustr(e))
+ raise LintConfigError(str(e)) from e
def _add_named_rule(self, config, qualified_rule_name):
""" Adds a Named Rule to a given LintConfig object.
@@ -465,14 +446,14 @@ class LintConfigBuilder(object):
# - not empty
# - no whitespace or colons
if rule_name == "" or bool(re.search("\\s|:", rule_name, re.UNICODE)):
- msg = u"The rule-name part in '{0}' cannot contain whitespace, colons or be empty"
- raise LintConfigError(msg.format(qualified_rule_name))
+ msg = f"The rule-name part in '{qualified_rule_name}' cannot contain whitespace, colons or be empty"
+ raise LintConfigError(msg)
# find parent rule
parent_rule = config.rules.find_rule(parent_rule_specifier)
if not parent_rule:
- msg = u"No such rule '{0}' (named rule: '{1}')"
- raise LintConfigError(msg.format(parent_rule_specifier, qualified_rule_name))
+ msg = f"No such rule '{parent_rule_specifier}' (named rule: '{qualified_rule_name}')"
+ raise LintConfigError(msg)
# Determine canonical id and name by recombining the parent id/name and instance name parts.
canonical_id = parent_rule.__class__.id + self.RULE_QUALIFIER_SYMBOL + rule_name
@@ -525,7 +506,7 @@ class LintConfigBuilder(object):
GITLINT_CONFIG_TEMPLATE_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files/gitlint")
-class LintConfigGenerator(object):
+class LintConfigGenerator:
@staticmethod
def generate_config(dest):
""" Generates a gitlint config file at the given destination location.
diff --git a/gitlint/contrib/rules/conventional_commit.py b/gitlint/contrib/rules/conventional_commit.py
index 8530343..71f6adf 100644
--- a/gitlint/contrib/rules/conventional_commit.py
+++ b/gitlint/contrib/rules/conventional_commit.py
@@ -2,7 +2,6 @@ import re
from gitlint.options import ListOption
from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation
-from gitlint.utils import ustr
RULE_REGEX = re.compile(r"[^(]+?(\([^)]+?\))?: .+")
@@ -26,14 +25,14 @@ class ConventionalCommit(LineRule):
violations = []
for commit_type in self.options["types"].value:
- if line.startswith(ustr(commit_type)):
+ if line.startswith(commit_type):
break
else:
- msg = u"Title does not start with one of {0}".format(', '.join(self.options['types'].value))
+ msg = "Title does not start with one of {0}".format(', '.join(self.options['types'].value))
violations.append(RuleViolation(self.id, msg, line))
if not RULE_REGEX.match(line):
- msg = u"Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
+ msg = "Title does not follow ConventionalCommits.org format 'type(optional-scope): description'"
violations.append(RuleViolation(self.id, msg, line))
return violations
diff --git a/gitlint/display.py b/gitlint/display.py
index c66a256..c9bcb01 100644
--- a/gitlint/display.py
+++ b/gitlint/display.py
@@ -1,18 +1,7 @@
-import codecs
-import locale
from sys import stdout, stderr
-from gitlint.utils import IS_PY2
-# For some reason, python 2.x sometimes messes up with printing unicode chars to stdout/stderr
-# This is mostly when there is a mismatch between the terminal encoding and the python encoding.
-# This use-case is primarily triggered when piping input between commands, in particular our integration tests
-# tend to trip over this.
-if IS_PY2:
- stdout = codecs.getwriter(locale.getpreferredencoding())(stdout) # pylint: disable=invalid-name
- stderr = codecs.getwriter(locale.getpreferredencoding())(stderr) # pylint: disable=invalid-name
-
-class Display(object):
+class Display:
""" Utility class to print stuff to an output stream (stdout by default) based on the config's verbosity """
def __init__(self, lint_config):
diff --git a/gitlint/exception.py b/gitlint/exception.py
new file mode 100644
index 0000000..aee3fe2
--- /dev/null
+++ b/gitlint/exception.py
@@ -0,0 +1,4 @@
+
+class GitlintError(Exception):
+ """ Based Exception class for all gitlint exceptions """
+ pass
diff --git a/gitlint/git.py b/gitlint/git.py
index 8e00f89..a9609d0 100644
--- a/gitlint/git.py
+++ b/gitlint/git.py
@@ -8,7 +8,7 @@ from gitlint import shell as sh
from gitlint.shell import CommandNotFound, ErrorReturnCode
from gitlint.cache import PropertyCache, cache
-from gitlint.utils import ustr, sstr
+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 :-)
@@ -17,24 +17,23 @@ GIT_TIMEFORMAT = "YYYY-MM-DD HH:mm:ss Z"
LOG = logging.getLogger(__name__)
-class GitContextError(Exception):
+class GitContextError(GitlintError):
""" Exception indicating there is an issue with the git context """
pass
class GitNotInstalledError(GitContextError):
def __init__(self):
- super(GitNotInstalledError, self).__init__(
- u"'git' command not found. You need to install git to use gitlint on a local repository. " +
- u"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.")
+ super().__init__(
+ "'git' command not found. You need to install git to use gitlint on a local repository. " +
+ "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.")
class GitExitCodeError(GitContextError):
def __init__(self, command, stderr):
self.command = command
self.stderr = stderr
- super(GitExitCodeError, self).__init__(
- u"An error occurred while executing '{0}': {1}".format(command, stderr))
+ super().__init__(f"An error occurred while executing '{command}': {stderr}")
def _git(*command_parts, **kwargs):
@@ -42,33 +41,33 @@ def _git(*command_parts, **kwargs):
git_kwargs = {'_tty_out': False}
git_kwargs.update(kwargs)
try:
- LOG.debug(sstr(command_parts))
+ LOG.debug(command_parts)
result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg
# 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
if hasattr(result, 'exit_code') and result.exit_code > 0:
return result
- return ustr(result)
- except CommandNotFound:
- raise GitNotInstalledError()
+ return str(result)
+ except CommandNotFound as e:
+ raise GitNotInstalledError from e
except ErrorReturnCode as e: # Something went wrong while executing the git command
error_msg = e.stderr.strip()
error_msg_lower = error_msg.lower()
if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower:
- error_msg = u"{0} is not a git repository.".format(git_kwargs['_cwd'])
- raise GitContextError(error_msg)
+ raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e
if (b"does not have any commits yet" in error_msg_lower or
b"ambiguous argument 'head': unknown revision" in error_msg_lower):
- raise GitContextError(u"Current branch has no commits. Gitlint requires at least one commit to function.")
+ msg = "Current branch has no commits. Gitlint requires at least one commit to function."
+ raise GitContextError(msg) from e
- raise GitExitCodeError(e.full_cmd, error_msg)
+ raise GitExitCodeError(e.full_cmd, error_msg) from e
def git_version():
""" Determine the git version installed on this host by calling git --version"""
- return _git("--version").replace(u"\n", u"")
+ return _git("--version").replace("\n", "")
def git_commentchar(repository_path=None):
@@ -77,17 +76,17 @@ def git_commentchar(repository_path=None):
# 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
commentchar = "#"
- return ustr(commentchar).replace(u"\n", u"")
+ return commentchar.replace("\n", "")
def git_hooks_dir(repository_path):
""" Determine hooks directory for a given target dir """
hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path)
- hooks_dir = ustr(hooks_dir).replace(u"\n", u"")
+ hooks_dir = hooks_dir.replace("\n", "")
return os.path.realpath(os.path.join(repository_path, hooks_dir))
-class GitCommitMessage(object):
+class GitCommitMessage:
""" Class representing a git commit message. A commit message consists of the following:
- context: The `GitContext` this commit message is part of
- original: The actual commit message as returned by `git log`
@@ -106,35 +105,26 @@ class GitCommitMessage(object):
def from_full_message(context, commit_msg_str):
""" Parses a full git commit message by parsing a given string into the different parts of a commit message """
all_lines = commit_msg_str.splitlines()
- cutline = u"{0} ------------------------ >8 ------------------------".format(context.commentchar)
+ cutline = f"{context.commentchar} ------------------------ >8 ------------------------"
try:
cutline_index = all_lines.index(cutline)
except ValueError:
cutline_index = None
- lines = [ustr(line) for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)]
+ lines = [line for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)]
full = "\n".join(lines)
title = lines[0] if lines else ""
body = lines[1:] if len(lines) > 1 else []
return GitCommitMessage(context=context, original=commit_msg_str, full=full, title=title, body=body)
- def __unicode__(self):
- return self.full # pragma: no cover
-
def __str__(self):
- return sstr(self.__unicode__()) # pragma: no cover
-
- def __repr__(self):
- return self.__str__() # pragma: no cover
+ return self.full
def __eq__(self, other):
return (isinstance(other, GitCommitMessage) and self.original == other.original
and self.full == other.full and self.title == other.title and self.body == other.body) # noqa
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
-class GitCommit(object):
+class GitCommit:
""" Class representing a git commit.
A commit consists of: context, message, author name, author email, date, list of parent commit shas,
list of changed files, list of branch names.
@@ -155,39 +145,33 @@ class GitCommit(object):
@property
def is_merge_commit(self):
- return self.message.title.startswith(u"Merge")
+ return self.message.title.startswith("Merge")
@property
def is_fixup_commit(self):
- return self.message.title.startswith(u"fixup!")
+ return self.message.title.startswith("fixup!")
@property
def is_squash_commit(self):
- return self.message.title.startswith(u"squash!")
+ return self.message.title.startswith("squash!")
@property
def is_revert_commit(self):
- return self.message.title.startswith(u"Revert")
-
- def __unicode__(self):
- format_str = (u"--- Commit Message ----\n%s\n"
- u"--- Meta info ---------\n"
- u"Author: %s <%s>\nDate: %s\n"
- u"is-merge-commit: %s\nis-fixup-commit: %s\n"
- u"is-squash-commit: %s\nis-revert-commit: %s\n"
- u"Branches: %s\n"
- u"Changed Files: %s\n"
- u"-----------------------") # pragma: no cover
- date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None
- return format_str % (ustr(self.message), self.author_name, self.author_email, date_str,
- self.is_merge_commit, self.is_fixup_commit, self.is_squash_commit,
- self.is_revert_commit, sstr(self.branches), sstr(self.changed_files)) # pragma: no cover
+ return self.message.title.startswith("Revert")
def __str__(self):
- return sstr(self.__unicode__()) # pragma: no cover
-
- def __repr__(self):
- return self.__str__() # pragma: no cover
+ date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None
+ return (f"--- Commit Message ----\n{self.message}\n"
+ "--- Meta info ---------\n"
+ f"Author: {self.author_name} <{self.author_email}>\n"
+ f"Date: {date_str}\n"
+ f"is-merge-commit: {self.is_merge_commit}\n"
+ f"is-fixup-commit: {self.is_fixup_commit}\n"
+ f"is-squash-commit: {self.is_squash_commit}\n"
+ f"is-revert-commit: {self.is_revert_commit}\n"
+ f"Branches: {self.branches}\n"
+ f"Changed Files: {self.changed_files}\n"
+ "-----------------------")
def __eq__(self, other):
# skip checking the context as context refers back to this obj, this will trigger a cyclic dependency
@@ -199,9 +183,6 @@ class GitCommit(object):
and self.is_squash_commit == other.is_squash_commit and self.is_revert_commit == other.is_revert_commit
and self.changed_files == other.changed_files and self.branches == other.branches) # noqa
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
class LocalGitCommit(GitCommit, PropertyCache):
""" Class representing a git commit that exists in the local git repository.
@@ -230,7 +211,7 @@ class LocalGitCommit(GitCommit, PropertyCache):
# "YYYY-MM-DD HH:mm:ss Z" -> ISO 8601-like format
# Use arrow for datetime parsing, because apparently python is quirky around ISO-8601 dates:
# http://stackoverflow.com/a/30696682/381010
- commit_date = arrow.get(ustr(date), GIT_TIMEFORMAT).datetime
+ commit_date = arrow.get(date, GIT_TIMEFORMAT).datetime
# Create Git commit object with the retrieved info
commit_msg_obj = GitCommitMessage.from_full_message(self.context, commit_msg)
@@ -270,7 +251,7 @@ class LocalGitCommit(GitCommit, PropertyCache):
# safely do this since git branches cannot contain '*' anywhere, so if we find an '*' we know it's output
# from the git CLI and not part of the branch name. See https://git-scm.com/docs/git-check-ref-format
# We also drop the last empty line from the output.
- self._cache['branches'] = [ustr(branch.replace("*", "").strip()) for branch in branches[:-1]]
+ self._cache['branches'] = [branch.replace("*", "").strip() for branch in branches[:-1]]
return self._try_cache("branches", cache_branches)
@@ -306,17 +287,17 @@ class StagedLocalGitCommit(GitCommit, PropertyCache):
@cache
def author_name(self):
try:
- return ustr(_git("config", "--get", "user.name", _cwd=self.context.repository_path)).strip()
- except GitExitCodeError:
- raise GitContextError("Missing git configuration: please set user.name")
+ return _git("config", "--get", "user.name", _cwd=self.context.repository_path).strip()
+ except GitExitCodeError as e:
+ raise GitContextError("Missing git configuration: please set user.name") from e
@property
@cache
def author_email(self):
try:
- return ustr(_git("config", "--get", "user.email", _cwd=self.context.repository_path)).strip()
- except GitExitCodeError:
- raise GitContextError("Missing git configuration: please set user.email")
+ return _git("config", "--get", "user.email", _cwd=self.context.repository_path).strip()
+ except GitExitCodeError as e:
+ raise GitContextError("Missing git configuration: please set user.email") from e
@property
@cache
@@ -356,7 +337,7 @@ class GitContext(PropertyCache):
@property
@cache
def current_branch(self):
- current_branch = ustr(_git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path)).strip()
+ current_branch = _git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path).strip()
return current_branch
@staticmethod
@@ -396,7 +377,7 @@ class GitContext(PropertyCache):
# We tried many things here e.g.: defaulting to e.g. HEAD or HEAD^... (incl. dealing with
# repos that only have a single commit - HEAD^... doesn't work there), but then we still get into
# problems with e.g. merge commits. Easiest solution is just taking the SHA from `git log -1`.
- sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace(u"\n", u"")]
+ sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace("\n", "")]
else:
sha_list = _git("rev-list", refspec, _cwd=repository_path).split()
@@ -410,6 +391,3 @@ class GitContext(PropertyCache):
return (isinstance(other, GitContext) and self.commits == other.commits
and self.repository_path == other.repository_path
and self.commentchar == other.commentchar and self.current_branch == other.current_branch) # noqa
-
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
diff --git a/gitlint/hooks.py b/gitlint/hooks.py
index fc4dc4e..87611e0 100644
--- a/gitlint/hooks.py
+++ b/gitlint/hooks.py
@@ -5,17 +5,18 @@ import stat
from gitlint.utils import DEFAULT_ENCODING
from gitlint.git import git_hooks_dir
+from gitlint.exception import GitlintError
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"
GITLINT_HOOK_IDENTIFIER = "### gitlint commit-msg hook start ###\n"
-class GitHookInstallerError(Exception):
+class GitHookInstallerError(GitlintError):
pass
-class GitHookInstaller(object):
+class GitHookInstaller:
""" Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook. """
@staticmethod
@@ -27,7 +28,7 @@ class GitHookInstaller(object):
""" Asserts that a given target directory is a git repository """
hooks_dir = git_hooks_dir(target)
if not os.path.isdir(hooks_dir):
- raise GitHookInstallerError(u"{0} is not a git repository.".format(target))
+ raise GitHookInstallerError(f"{target} is not a git repository.")
@staticmethod
def install_commit_msg_hook(lint_config):
@@ -35,8 +36,8 @@ class GitHookInstaller(object):
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
if os.path.exists(dest_path):
raise GitHookInstallerError(
- u"There is already a commit-msg hook file present in {0}.\n".format(dest_path) +
- u"gitlint currently does not support appending to an existing commit-msg file.")
+ f"There is already a commit-msg hook file present in {dest_path}.\n" +
+ "gitlint currently does not support appending to an existing commit-msg file.")
# copy hook file
shutil.copy(COMMIT_MSG_HOOK_SRC_PATH, dest_path)
@@ -49,14 +50,14 @@ class GitHookInstaller(object):
GitHookInstaller._assert_git_repo(lint_config.target)
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
if not os.path.exists(dest_path):
- raise GitHookInstallerError(u"There is no commit-msg hook present in {0}.".format(dest_path))
+ raise GitHookInstallerError(f"There is no commit-msg hook present in {dest_path}.")
with io.open(dest_path, encoding=DEFAULT_ENCODING) as fp:
lines = fp.readlines()
if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER:
- msg = u"The commit-msg hook in {0} was not installed by gitlint (or it was modified).\n" + \
- u"Uninstallation of 3th party or modified gitlint hooks is not supported."
- raise GitHookInstallerError(msg.format(dest_path))
+ 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."
+ raise GitHookInstallerError(msg)
# If we are sure it's a gitlint hook, go ahead and remove it
os.remove(dest_path)
diff --git a/gitlint/lint.py b/gitlint/lint.py
index 6ef7174..4b6c8a3 100644
--- a/gitlint/lint.py
+++ b/gitlint/lint.py
@@ -2,13 +2,12 @@
import logging
from gitlint import rules as gitlint_rules
from gitlint import display
-from gitlint.utils import ustr
LOG = logging.getLogger(__name__)
logging.basicConfig()
-class GitLinter(object):
+class GitLinter:
""" Main linter class. This is where rules actually get applied. See the lint() method. """
def __init__(self, config):
@@ -70,7 +69,7 @@ class GitLinter(object):
def lint(self, commit):
""" Lint the last commit in a given git context by applying all ignore, title, body and commit rules. """
LOG.debug("Linting commit %s", commit.sha or "[SHA UNKNOWN]")
- LOG.debug("Commit Object\n" + ustr(commit))
+ LOG.debug("Commit Object\n" + str(commit))
# Apply config rules
for rule in self.configuration_rules:
@@ -79,8 +78,8 @@ class GitLinter(object):
# Skip linting if this is a special commit type that is configured to be ignored
ignore_commit_types = ["merge", "squash", "fixup", "revert"]
for commit_type in ignore_commit_types:
- if getattr(commit, "is_{0}_commit".format(commit_type)) and \
- getattr(self.config, "ignore_{0}_commits".format(commit_type)):
+ if getattr(commit, f"is_{commit_type}_commit") and \
+ getattr(self.config, f"ignore_{commit_type}_commits"):
return []
violations = []
@@ -99,10 +98,9 @@ class GitLinter(object):
""" Print a given set of violations to the standard error output """
for v in violations:
line_nr = v.line_nr if v.line_nr else "-"
- self.display.e(u"{0}: {1}".format(line_nr, v.rule_id), exact=True)
- self.display.ee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
+ self.display.e(f"{line_nr}: {v.rule_id}", exact=True)
+ self.display.ee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
if v.content:
- self.display.eee(u"{0}: {1} {2}: \"{3}\"".format(line_nr, v.rule_id, v.message, v.content),
- exact=True)
+ self.display.eee(f"{line_nr}: {v.rule_id} {v.message}: \"{v.content}\"", exact=True)
else:
- self.display.eee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
+ self.display.eee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
diff --git a/gitlint/options.py b/gitlint/options.py
index 3ea8310..9fdac8f 100644
--- a/gitlint/options.py
+++ b/gitlint/options.py
@@ -2,7 +2,7 @@ from abc import abstractmethod
import os
import re
-from gitlint.utils import ustr, sstr
+from gitlint.exception import GitlintError
def allow_none(func):
@@ -17,11 +17,11 @@ def allow_none(func):
return wrapped
-class RuleOptionError(Exception):
+class RuleOptionError(GitlintError):
pass
-class RuleOption(object):
+class RuleOption:
""" Base class representing a configurable part (i.e. option) of a rule (e.g. the max-length of the title-max-line
rule).
This class should not be used directly. Instead, use on the derived classes like StrOption, IntOption to set
@@ -29,8 +29,8 @@ class RuleOption(object):
"""
def __init__(self, name, value, description):
- self.name = ustr(name)
- self.description = ustr(description)
+ self.name = name
+ self.description = description
self.value = None
self.set(value)
@@ -40,37 +40,28 @@ class RuleOption(object):
pass # pragma: no cover
def __str__(self):
- return sstr(self) # pragma: no cover
-
- def __unicode__(self):
- return u"({0}: {1} ({2}))".format(self.name, self.value, self.description) # pragma: no cover
-
- def __repr__(self):
- return self.__str__() # pragma: no cover
+ return f"({self.name}: {self.value} ({self.description}))"
def __eq__(self, other):
return self.name == other.name and self.description == other.description and self.value == other.value
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
class StrOption(RuleOption):
@allow_none
def set(self, value):
- self.value = ustr(value)
+ self.value = str(value)
class IntOption(RuleOption):
def __init__(self, name, value, description, allow_negative=False):
self.allow_negative = allow_negative
- super(IntOption, self).__init__(name, value, description)
+ super().__init__(name, value, description)
def _raise_exception(self, value):
if self.allow_negative:
- error_msg = u"Option '{0}' must be an integer (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option '{self.name}' must be an integer (current value: '{value}')"
else:
- error_msg = u"Option '{0}' must be a positive integer (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option '{self.name}' must be a positive integer (current value: '{value}')"
raise RuleOptionError(error_msg)
@allow_none
@@ -88,9 +79,9 @@ class BoolOption(RuleOption):
# explicit choice to not annotate with @allow_none: Booleans must be False or True, they cannot be unset.
def set(self, value):
- value = ustr(value).strip().lower()
+ value = str(value).strip().lower()
if value not in ['true', 'false']:
- raise RuleOptionError(u"Option '{0}' must be either 'true' or 'false'".format(self.name))
+ raise RuleOptionError(f"Option '{self.name}' must be either 'true' or 'false'")
self.value = value == 'true'
@@ -103,37 +94,36 @@ class ListOption(RuleOption):
if isinstance(value, list):
the_list = value
else:
- the_list = ustr(value).split(",")
+ the_list = str(value).split(",")
- self.value = [ustr(item.strip()) for item in the_list if item.strip() != ""]
+ self.value = [str(item.strip()) for item in the_list if item.strip() != ""]
class PathOption(RuleOption):
""" Option that accepts either a directory or both a directory and a file. """
- def __init__(self, name, value, description, type=u"dir"):
+ def __init__(self, name, value, description, type="dir"):
self.type = type
- super(PathOption, self).__init__(name, value, description)
+ super().__init__(name, value, description)
@allow_none
def set(self, value):
- value = ustr(value)
+ value = str(value)
- error_msg = u""
+ error_msg = ""
if self.type == 'dir':
if not os.path.isdir(value):
- error_msg = u"Option {0} must be an existing directory (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option {self.name} must be an existing directory (current value: '{value}')"
elif self.type == 'file':
if not os.path.isfile(value):
- error_msg = u"Option {0} must be an existing file (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option {self.name} must be an existing file (current value: '{value}')"
elif self.type == 'both':
if not os.path.isdir(value) and not os.path.isfile(value):
- error_msg = (u"Option {0} must be either an existing directory or file "
- u"(current value: '{1}')").format(self.name, value)
+ error_msg = (f"Option {self.name} must be either an existing directory or file "
+ f"(current value: '{value}')")
else:
- error_msg = u"Option {0} type must be one of: 'file', 'dir', 'both' (current: '{1}')".format(self.name,
- self.type)
+ error_msg = f"Option {self.name} type must be one of: 'file', 'dir', 'both' (current: '{self.type}')"
if error_msg:
raise RuleOptionError(error_msg)
@@ -148,7 +138,7 @@ class RegexOption(RuleOption):
try:
self.value = re.compile(value, re.UNICODE)
except (re.error, TypeError) as exc:
- raise RuleOptionError("Invalid regular expression: '{0}'".format(exc))
+ raise RuleOptionError(f"Invalid regular expression: '{exc}'") from exc
def __deepcopy__(self, _):
# copy.deepcopy() - used in rules.py - doesn't support copying regex objects prior to Python 3.7
diff --git a/gitlint/rule_finder.py b/gitlint/rule_finder.py
index d7d700b..e1c5e77 100644
--- a/gitlint/rule_finder.py
+++ b/gitlint/rule_finder.py
@@ -5,7 +5,6 @@ import sys
import importlib
from gitlint import rules, options
-from gitlint.utils import ustr
def find_rule_classes(extra_path):
@@ -28,7 +27,7 @@ def find_rule_classes(extra_path):
files = os.listdir(extra_path)
directory = extra_path
else:
- raise rules.UserRuleError(u"Invalid extra-path: {0}".format(extra_path))
+ raise rules.UserRuleError(f"Invalid extra-path: {extra_path}")
# Filter out files that are not python modules
for filename in files:
@@ -56,7 +55,7 @@ def find_rule_classes(extra_path):
importlib.import_module(module)
except Exception as e:
- raise rules.UserRuleError(u"Error while importing extra-path module '{0}': {1}".format(module, ustr(e)))
+ raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {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
@@ -94,55 +93,53 @@ 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)):
- msg = u"{0} rule class '{1}' must extend from {2}.{3}, {2}.{4} or {2}.{5}"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__, rules.CommitRule.__module__,
- rules.LineRule.__name__, rules.CommitRule.__name__,
- rules.ConfigurationRule.__name__))
+ msg = f"{rule_type} rule class '{clazz.__name__}' " + \
+ f"must extend from {rules.CommitRule.__module__}.{rules.LineRule.__name__}, " + \
+ f"{rules.CommitRule.__module__}.{rules.CommitRule.__name__} or " + \
+ f"{rules.CommitRule.__module__}.{rules.ConfigurationRule.__name__}"
+ raise rules.UserRuleError(msg)
# Rules must have an id attribute
if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id:
- msg = u"{0} rule class '{1}' must have an 'id' attribute"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have an 'id' attribute")
# Rule id's cannot start with gitlint reserved letters
if clazz.id[0].upper() in ['R', 'T', 'B', 'M', 'I']:
- msg = u"The id '{1}' of '{0}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
- raise rules.UserRuleError(msg.format(clazz.__name__, clazz.id[0]))
+ msg = f"The id '{clazz.id[0]}' of '{clazz.__name__}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
+ raise rules.UserRuleError(msg)
# Rules must have a name attribute
if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name:
- msg = u"{0} rule class '{1}' must have a 'name' attribute"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'name' attribute")
# if set, options_spec must be a list of RuleOption
if not isinstance(clazz.options_spec, list):
- msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
- raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
- options.RuleOption.__module__, options.RuleOption.__name__))
+ msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
+ f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
+ raise rules.UserRuleError(msg)
# check that all items in options_spec are actual gitlint options
for option in clazz.options_spec:
if not isinstance(option, options.RuleOption):
- msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
- raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
- options.RuleOption.__module__, options.RuleOption.__name__))
+ msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
+ f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
+ raise rules.UserRuleError(msg)
# 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 not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate):
- msg = u"{0} rule class '{1}' must have a 'validate' method"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ 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):
if not hasattr(clazz, 'apply') or not inspect.isroutine(clazz.apply):
- msg = u"{0} Configuration rule class '{1}' must have an 'apply' method"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ 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 clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]:
- msg = u"The target attribute of the {0} LineRule class '{1}' must be either {2}.{3} or {2}.{4}"
- msg = msg.format(rule_type.lower(), clazz.__name__, rules.CommitMessageTitle.__module__,
- rules.CommitMessageTitle.__name__, rules.CommitMessageBody.__name__)
+ msg = f"The target attribute of the {rule_type.lower()} LineRule class '{clazz.__name__}' " + \
+ f"must be either {rules.CommitMessageTitle.__module__}.{rules.CommitMessageTitle.__name__} " + \
+ f"or {rules.CommitMessageTitle.__module__}.{rules.CommitMessageBody.__name__}"
raise rules.UserRuleError(msg)
diff --git a/gitlint/rules.py b/gitlint/rules.py
index 1cb50da..3dc85b7 100644
--- a/gitlint/rules.py
+++ b/gitlint/rules.py
@@ -4,10 +4,10 @@ import logging
import re
from gitlint.options import IntOption, BoolOption, StrOption, ListOption, RegexOption
-from gitlint.utils import sstr
+from gitlint.exception import GitlintError
-class Rule(object):
+class Rule:
""" Class representing gitlint rules. """
options_spec = []
id = None
@@ -36,17 +36,8 @@ class Rule(object):
return self.id == other.id and self.name == other.name and \
self.options == other.options and self.target == other.target # noqa
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
def __str__(self):
- return sstr(self) # pragma: no cover
-
- def __unicode__(self):
- return u"{0} {1}".format(self.id, self.name) # pragma: no cover
-
- def __repr__(self):
- return self.__str__() # pragma: no cover
+ return f"{self.id} {self.name}" # pragma: no cover
class ConfigurationRule(Rule):
@@ -64,7 +55,7 @@ class LineRule(Rule):
pass
-class LineRuleTarget(object):
+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. """
@@ -81,7 +72,7 @@ class CommitMessageBody(LineRuleTarget):
pass
-class RuleViolation(object):
+class RuleViolation:
""" Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class
to indicate how and where the rule was broken. """
@@ -96,21 +87,11 @@ class RuleViolation(object):
equal = equal and self.content == other.content and self.line_nr == other.line_nr
return equal
- def __ne__(self, other):
- return not self.__eq__(other) # required for py2
-
def __str__(self):
- return sstr(self) # pragma: no cover
-
- def __unicode__(self):
- return u"{0}: {1} {2}: \"{3}\"".format(self.line_nr, self.rule_id, self.message,
- self.content) # pragma: no cover
-
- def __repr__(self):
- return self.__unicode__() # pragma: no cover
+ return f"{self.line_nr}: {self.rule_id} {self.message}: \"{self.content}\""
-class UserRuleError(Exception):
+class UserRuleError(GitlintError):
""" Error used to indicate that an error occurred while trying to load a user rule """
pass
@@ -154,7 +135,7 @@ class LineMustNotContainWord(LineRule):
name = "line-must-not-contain"
id = "R5"
options_spec = [ListOption('words', [], "Comma separated list of words that should not be found")]
- violation_message = u"Line contains {0}"
+ violation_message = "Line contains {0}"
def validate(self, line, _commit):
strings = self.options['words'].value
@@ -202,7 +183,7 @@ class TitleTrailingPunctuation(LineRule):
punctuation_marks = '?:!.,;'
for punctuation_mark in punctuation_marks:
if title.endswith(punctuation_mark):
- return [RuleViolation(self.id, u"Title has trailing punctuation ({0})".format(punctuation_mark), title)]
+ return [RuleViolation(self.id, f"Title has trailing punctuation ({punctuation_mark})", title)]
class TitleHardTab(HardTab):
@@ -217,7 +198,7 @@ class TitleMustNotContainWord(LineMustNotContainWord):
id = "T5"
target = CommitMessageTitle
options_spec = [ListOption('words', ["WIP"], "Must not contain word")]
- violation_message = u"Title contains the word '{0}' (case-insensitive)"
+ violation_message = "Title contains the word '{0}' (case-insensitive)"
class TitleLeadingWhitespace(LeadingWhiteSpace):
@@ -239,7 +220,7 @@ class TitleRegexMatches(LineRule):
return
if not self.options['regex'].value.search(title):
- violation_msg = u"Title does not match regex ({0})".format(self.options['regex'].value.pattern)
+ violation_msg = f"Title does not match regex ({self.options['regex'].value.pattern})"
return [RuleViolation(self.id, violation_msg, title)]
@@ -253,7 +234,7 @@ class TitleMinLength(LineRule):
min_length = self.options['min-length'].value
actual_length = len(title)
if actual_length < min_length:
- violation_message = "Title is too short ({0}<{1})".format(actual_length, min_length)
+ violation_message = f"Title is too short ({actual_length}<{min_length})"
return [RuleViolation(self.id, violation_message, title, 1)]
@@ -296,7 +277,7 @@ class BodyMinLength(CommitRule):
body_message_no_newline = "".join([line for line in commit.message.body if line is not None])
actual_length = len(body_message_no_newline)
if 0 < actual_length < min_length:
- violation_message = "Body message is too short ({0}<{1})".format(actual_length, min_length)
+ violation_message = f"Body message is too short ({actual_length}<{min_length})"
return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)]
@@ -325,7 +306,7 @@ class BodyChangedFileMention(CommitRule):
# in the commit msg body
if needs_mentioned_file in commit.changed_files:
if needs_mentioned_file not in " ".join(commit.message.body):
- violation_message = u"Body does not mention changed file '{0}'".format(needs_mentioned_file)
+ 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))
return violations if violations else None
@@ -354,7 +335,7 @@ class BodyRegexMatches(CommitRule):
full_body = "\n".join(body_lines)
if not self.options['regex'].value.search(full_body):
- violation_msg = u"Body does not match regex ({0})".format(self.options['regex'].value.pattern)
+ violation_msg = f"Body does not match regex ({self.options['regex'].value.pattern})"
return [RuleViolation(self.id, violation_msg, None, len(commit.message.body) + 1)]
@@ -386,9 +367,8 @@ class IgnoreByTitle(ConfigurationRule):
if self.options['regex'].value.match(commit.message.title):
config.ignore = self.options['ignore'].value
- message = u"Commit title '{0}' matches the regex '{1}', ignoring rules: {2}"
- message = message.format(commit.message.title, self.options['regex'].value.pattern,
- self.options['ignore'].value)
+ message = f"Commit title '{commit.message.title}' matches the regex " + \
+ f"'{self.options['regex'].value.pattern}', ignoring rules: {self.options['ignore'].value}"
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
@@ -408,8 +388,8 @@ class IgnoreByBody(ConfigurationRule):
if self.options['regex'].value.match(line):
config.ignore = self.options['ignore'].value
- message = u"Commit message line '{0}' matches the regex '{1}', ignoring rules: {2}"
- message = message.format(line, self.options['regex'].value.pattern, self.options['ignore'].value)
+ message = f"Commit message line '{line}' matches the regex '{self.options['regex'].value.pattern}'," + \
+ f" ignoring rules: {self.options['ignore'].value}"
self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message)
# No need to check other lines if we found a match
@@ -429,10 +409,10 @@ class IgnoreBodyLines(ConfigurationRule):
new_body = []
for line in commit.message.body:
if self.options['regex'].value.match(line):
- debug_msg = u"Ignoring line '%s' because it matches '%s'"
+ debug_msg = "Ignoring line '%s' because it matches '%s'"
self.log.debug(debug_msg, line, self.options['regex'].value.pattern)
else:
new_body.append(line)
commit.message.body = new_body
- commit.message.full = u"\n".join([commit.message.title] + new_body)
+ commit.message.full = "\n".join([commit.message.title] + new_body)
diff --git a/gitlint/shell.py b/gitlint/shell.py
index 2601b04..7f598ae 100644
--- a/gitlint/shell.py
+++ b/gitlint/shell.py
@@ -6,7 +6,7 @@ capabilities wrt dealing with more edge-case environments on *nix systems that a
"""
import subprocess
-from gitlint.utils import ustr, IS_PY2, USE_SH_LIB
+from gitlint.utils import USE_SH_LIB, DEFAULT_ENCODING
def shell(cmd):
@@ -25,7 +25,7 @@ else:
""" Exception indicating a command was not found during execution """
pass
- class ShResult(object):
+ 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 """
@@ -51,11 +51,6 @@ else:
return _exec(*args, **kwargs)
def _exec(*args, **kwargs):
- if IS_PY2:
- no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name
- else:
- no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable
-
pipe = subprocess.PIPE
popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
if '_cwd' in kwargs:
@@ -64,11 +59,11 @@ else:
try:
p = subprocess.Popen(args, **popen_kwargs)
result = p.communicate()
- except no_command_error:
- raise CommandNotFound
+ except FileNotFoundError as e:
+ raise CommandNotFound from e
exit_code = p.returncode
- stdout = ustr(result[0])
+ stdout = result[0].decode(DEFAULT_ENCODING)
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
full_cmd = '' if args is None else ' '.join(args)
diff --git a/gitlint/tests/base.py b/gitlint/tests/base.py
index c8f68c4..9406240 100644
--- a/gitlint/tests/base.py
+++ b/gitlint/tests/base.py
@@ -9,33 +9,12 @@ import re
import shutil
import tempfile
-try:
- # python 2.x
- import unittest2 as unittest
-except ImportError:
- # python 3.x
- import unittest
-
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
-
-from gitlint.git import GitContext
-from gitlint.utils import ustr, IS_PY2, LOG_FORMAT, DEFAULT_ENCODING
+import unittest
+from unittest.mock import patch
-# unittest2's assertRaisesRegex doesn't do unicode comparison.
-# Let's monkeypatch the str() function to point to unicode() so that it does :)
-# For reference, this is where this patch is required:
-# https://hg.python.org/unittest2/file/tip/unittest2/case.py#l227
-try:
- # python 2.x
- unittest.case.str = unicode
-except (AttributeError, NameError):
- pass # python 3.x
+from gitlint.git import GitContext
+from gitlint.utils import LOG_FORMAT, DEFAULT_ENCODING
class BaseTestCase(unittest.TestCase):
@@ -72,24 +51,22 @@ class BaseTestCase(unittest.TestCase):
def get_sample_path(filename=""):
# Don't join up empty files names because this will add a trailing slash
if filename == "":
- return ustr(BaseTestCase.SAMPLES_DIR)
+ return BaseTestCase.SAMPLES_DIR
- return ustr(os.path.join(BaseTestCase.SAMPLES_DIR, filename))
+ return os.path.join(BaseTestCase.SAMPLES_DIR, filename)
@staticmethod
def get_sample(filename=""):
""" Read and return the contents of a file in gitlint/tests/samples """
sample_path = BaseTestCase.get_sample_path(filename)
with io.open(sample_path, encoding=DEFAULT_ENCODING) as content:
- sample = ustr(content.read())
+ sample = content.read()
return sample
@staticmethod
def patch_input(side_effect):
""" Patches the built-in input() with a provided side-effect """
module_path = "builtins.input"
- if IS_PY2:
- module_path = "__builtin__.raw_input"
patched_module = patch(module_path, side_effect=side_effect)
return patched_module
@@ -99,7 +76,7 @@ class BaseTestCase(unittest.TestCase):
Optionally replace template variables specified by variable_dict. """
expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename)
with io.open(expected_path, encoding=DEFAULT_ENCODING) as content:
- expected = ustr(content.read())
+ expected = content.read()
if variable_dict:
expected = expected.format(**variable_dict)
@@ -114,7 +91,7 @@ class BaseTestCase(unittest.TestCase):
""" Utility method to easily create gitcontext objects based on a given commit msg string and an optional set of
changed files"""
with patch("gitlint.git.git_commentchar") as comment_char:
- comment_char.return_value = u"#"
+ comment_char.return_value = "#"
gitcontext = GitContext.from_commit_msg(commit_msg_str)
commit = gitcontext.commits[-1]
if changed_files:
@@ -147,8 +124,7 @@ class BaseTestCase(unittest.TestCase):
""" Pass-through method to unittest.TestCase.assertRaisesRegex that applies re.escape() to the passed
`expected_regex`. This is useful to automatically escape all file paths that might be present in the regex.
"""
- return super(BaseTestCase, self).assertRaisesRegex(expected_exception, re.escape(expected_regex),
- *args, **kwargs)
+ return super().assertRaisesRegex(expected_exception, re.escape(expected_regex), *args, **kwargs)
@contextlib.contextmanager
def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name
@@ -156,17 +132,17 @@ class BaseTestCase(unittest.TestCase):
try:
yield
except expected_exception as exc:
- exception_msg = ustr(exc)
+ exception_msg = str(exc)
if exception_msg != expected_msg:
- error = u"Right exception, wrong message:\n got: {0}\n expected: {1}"
- raise self.fail(error.format(exception_msg, expected_msg))
+ error = f"Right exception, wrong message:\n got: {exception_msg}\n expected: {expected_msg}"
+ raise self.fail(error)
# else: everything is fine, just return
return
except Exception as exc:
- raise self.fail(u"Expected '{0}' got '{1}'".format(expected_exception.__name__, exc.__class__.__name__))
+ raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'")
# No exception raised while we expected one
- raise self.fail("Expected to raise {0}, didn't get an exception at all".format(expected_exception.__name__))
+ raise self.fail(f"Expected to raise {expected_exception.__name__}, didn't get an exception at all")
def object_equality_test(self, obj, attr_list, ctor_kwargs=None):
""" Helper function to easily implement object equality tests.
@@ -190,9 +166,9 @@ class BaseTestCase(unittest.TestCase):
self.assertEqual(obj, clone)
# Change attribute and assert objects are different (via both attribute set and ctor)
- setattr(clone, attr, u"föo")
+ setattr(clone, attr, "föo")
self.assertNotEqual(obj, clone)
- attr_kwargs_copy[attr] = u"föo"
+ attr_kwargs_copy[attr] = "föo"
self.assertNotEqual(obj, obj.__class__(**attr_kwargs_copy))
@@ -205,4 +181,4 @@ class LogCapture(logging.Handler):
self.messages = []
def emit(self, record):
- self.messages.append(ustr(self.format(record)))
+ self.messages.append(self.format(record))
diff --git a/gitlint/tests/cli/test_cli.py b/gitlint/tests/cli/test_cli.py
index 88bcfb7..bf35e96 100644
--- a/gitlint/tests/cli/test_cli.py
+++ b/gitlint/tests/cli/test_cli.py
@@ -8,21 +8,11 @@ import platform
import arrow
-try:
- # python 2.x
- from StringIO import StringIO
-except ImportError:
- # python 3.x
- from io import StringIO # pylint: disable=ungrouped-imports
+from io import StringIO
from click.testing import CliRunner
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch
from gitlint.shell import CommandNotFound
@@ -59,7 +49,7 @@ class CLITests(BaseTestCase):
def test_version(self):
""" Test for --version option """
result = self.cli.invoke(cli.cli, ["--version"])
- self.assertEqual(result.output.split("\n")[0], "cli, version {0}".format(__version__))
+ self.assertEqual(result.output.split("\n")[0], f"cli, version {__version__}")
@patch('gitlint.cli.get_stdin_data', return_value=False)
@patch('gitlint.git.sh')
@@ -67,11 +57,11 @@ class CLITests(BaseTestCase):
""" Test for basic simple linting functionality """
sh.git.side_effect = [
"6f29bf81a8322a04071bb794666e48c443a90360",
- u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"commït-title\n\ncommït-body",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n",
- u"file1.txt\npåth/to/file2.txt\n"
+ "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ "commït-title\n\ncommït-body",
+ "#", # git config --get core.commentchar
+ "commit-1-branch-1\ncommit-1-branch-2\n",
+ "file1.txt\npåth/to/file2.txt\n"
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -89,21 +79,21 @@ class CLITests(BaseTestCase):
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
# git log --pretty <FORMAT> <SHA>
- u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"commït-title1\n\ncommït-body1",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "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
+ "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
- u"commït-title2\n\ncommït-body2",
- u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
- u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
+ "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
+ "commït-title2\n\ncommït-body2",
+ "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
+ "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
- u"commït-title3\n\ncommït-body3",
- u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
- u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
+ "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
+ "commït-title3\n\ncommït-body3",
+ "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
+ "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -122,21 +112,21 @@ class CLITests(BaseTestCase):
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
# git log --pretty <FORMAT> <SHA>
- u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"commït-title1\n\ncommït-body1",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "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
+ "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
- u"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
- u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
- u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
+ "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
+ "commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n",
+ "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
+ "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
- u"commït-title3.\n\ncommït-body3",
- u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
- u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
+ "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
+ "commït-title3.\n\ncommït-body3",
+ "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
+ "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -157,24 +147,24 @@ class CLITests(BaseTestCase):
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" +
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
# git log --pretty <FORMAT> <SHA>
- u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"commït-title1\n\ncommït-body1",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "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
+ "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
+ "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n"
# Normally T3 violation (trailing punctuation), but this commit is ignored because of
# config below
- u"commït-title2.\n\ncommït-body2\n",
- u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
- u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
+ "commït-title2.\n\ncommït-body2\n",
+ "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
+ "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
# git log --pretty <FORMAT> <SHA>
- u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
+ "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n"
# Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below
- u"commït-title3.\n\ncommït-body3 foo",
- u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
- u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
+ "commït-title3.\n\ncommït-body3 foo",
+ "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
+ "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -183,9 +173,9 @@ class CLITests(BaseTestCase):
# We expect that the second commit has no failures because of it matching against I1.regex
# Because we do test for the 3th commit to return violations, this test also ensures that a unique
# config object is passed to each commit lint call
- expected = (u"Commit 6f29bf81a8:\n"
+ expected = ("Commit 6f29bf81a8:\n"
u'3: B5 Body message is too short (12<20): "commït-body1"\n\n'
- u"Commit 4da2656b0d:\n"
+ "Commit 4da2656b0d:\n"
u'1: T3 Title has trailing punctuation (.): "commït-title3."\n')
self.assertEqual(stderr.getvalue(), expected)
self.assertEqual(result.exit_code, 2)
@@ -218,11 +208,11 @@ class CLITests(BaseTestCase):
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
sh.git.side_effect = [
"6f29bf81a8322a04071bb794666e48c443a90360",
- u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"commït-title\n\ncommït-body",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
- u"file1.txt\npåth/to/file2.txt\n" # git diff-tree
+ "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ "commït-title\n\ncommït-body",
+ "#", # git config --get core.commentchar
+ "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
+ "file1.txt\npåth/to/file2.txt\n" # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -240,11 +230,11 @@ class CLITests(BaseTestCase):
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
- u"föo user\n", # git config --get user.name
- u"föo@bar.com\n", # git config --get user.email
- u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "#", # git config --get core.commentchar
+ "föo user\n", # git config --get user.name
+ "föo@bar.com\n", # git config --get user.email
+ "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -263,17 +253,17 @@ class CLITests(BaseTestCase):
""" Test for ignoring stdin when --ignore-stdin flag is enabled"""
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
- u"föo user\n", # git config --get user.name
- u"föo@bar.com\n", # git config --get user.email
- u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "#", # git config --get core.commentchar
+ "föo user\n", # git config --get user.name
+ "föo@bar.com\n", # git config --get user.email
+ "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch)
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
]
with self.tempdir() as tmpdir:
msg_filename = os.path.join(tmpdir, "msg")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
- f.write(u"WIP: msg-filename tïtle\n")
+ f.write("WIP: msg-filename tïtle\n")
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename])
@@ -289,17 +279,17 @@ class CLITests(BaseTestCase):
def test_lint_staged_negative(self, _):
result = self.cli.invoke(cli.cli, ["--staged"])
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
- self.assertEqual(result.output, (u"Error: The 'staged' option (--staged) can only be used when using "
- u"'--msg-filename' or when piping data to gitlint via stdin.\n"))
+ self.assertEqual(result.output, ("Error: The 'staged' option (--staged) can only be used when using "
+ "'--msg-filename' or when piping data to gitlint via stdin.\n"))
@patch('gitlint.cli.get_stdin_data', return_value=False)
def test_msg_filename(self, _):
- expected_output = u"3: B6 Body message is missing\n"
+ expected_output = "3: B6 Body message is missing\n"
with self.tempdir() as tmpdir:
msg_filename = os.path.join(tmpdir, "msg")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
- f.write(u"Commït title\n")
+ f.write("Commït title\n")
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename])
@@ -307,7 +297,7 @@ class CLITests(BaseTestCase):
self.assertEqual(result.exit_code, 1)
self.assertEqual(result.output, "")
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n")
def test_silent_mode(self, _):
""" Test for --silent option """
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -316,7 +306,7 @@ class CLITests(BaseTestCase):
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n")
def test_verbosity(self, _):
""" Test for --verbosity option """
# We only test -v and -vv, more testing is really not required here
@@ -333,7 +323,7 @@ class CLITests(BaseTestCase):
"3: B6 Body message is missing\n"
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- result = self.cli.invoke(cli.cli, ["-vv"], input=u"WIP: tïtle \n")
+ result = self.cli.invoke(cli.cli, ["-vv"], input="WIP: tïtle \n")
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
@@ -355,19 +345,19 @@ class CLITests(BaseTestCase):
"25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n"
"4da2656b0dadc76c7ee3fd0243a96cb64007f125\n",
# git log --pretty <FORMAT> <SHA>
- u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n"
- u"commït-title1\n\ncommït-body1",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
- u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
- u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n"
- u"commït-title2.\n\ncommït-body2",
- u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
- u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
- u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
- u"föobar\nbar",
- u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
- u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
+ "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n"
+ "commït-title1\n\ncommït-body1",
+ "#", # git config --get core.commentchar
+ "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha>
+ "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
+ "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n"
+ "commït-title2.\n\ncommït-body2",
+ "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
+ "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
+ "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
+ "föobar\nbar",
+ "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
+ "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -387,14 +377,14 @@ class CLITests(BaseTestCase):
expected_logs = self.get_expected('cli/test_cli/test_debug_1', expected_kwargs)
self.assert_logged(expected_logs)
- @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
+ @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n")
def test_extra_path(self, _):
""" Test for --extra-path flag """
# Test extra-path pointing to a directory
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
extra_path = self.get_sample_path("user_rules")
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
- expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
+ expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
"3: B6 Body message is missing\n"
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 2)
@@ -403,12 +393,12 @@ class CLITests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py"))
result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
- expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
+ expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
"3: B6 Body message is missing\n"
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 2)
- @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nMy body that is long enough")
+ @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nMy body that is long enough")
def test_contrib(self, _):
# Test enabled contrib rules
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -417,13 +407,13 @@ class CLITests(BaseTestCase):
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 3)
- @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
+ @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n")
def test_contrib_negative(self, _):
- result = self.cli.invoke(cli.cli, ["--contrib", u"föobar,CC1"])
- self.assertEqual(result.output, u"Config Error: No contrib rule with id or name 'föobar' found.\n")
+ result = self.cli.invoke(cli.cli, ["--contrib", "föobar,CC1"])
+ self.assertEqual(result.output, "Config Error: No contrib rule with id or name 'föobar' found.\n")
self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst")
def test_config_file(self, _):
""" Test for --config option """
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -438,16 +428,14 @@ class CLITests(BaseTestCase):
# Directory as config file
config_path = self.get_sample_path("config")
result = self.cli.invoke(cli.cli, ["--config", config_path])
- expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" is a directory.".format(
- config_path)
+ expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' is a directory."
self.assertEqual(result.output.split("\n")[3], expected_string)
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
# Non existing file
- config_path = self.get_sample_path(u"föo")
+ config_path = self.get_sample_path("föo")
result = self.cli.invoke(cli.cli, ["--config", config_path])
- expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" does not exist.".format(
- config_path)
+ expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' does not exist."
self.assertEqual(result.output.split("\n")[3], expected_string)
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
@@ -471,37 +459,37 @@ class CLITests(BaseTestCase):
def test_target_negative(self):
""" Negative test for the --target option """
# try setting a non-existing target
- result = self.cli.invoke(cli.cli, ["--target", u"/föo/bar"])
+ result = self.cli.invoke(cli.cli, ["--target", "/föo/bar"])
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
- expected_msg = u"Error: Invalid value for \"--target\": Directory \"/föo/bar\" does not exist."
+ expected_msg = "Error: Invalid value for '--target': Directory '/föo/bar' does not exist."
self.assertEqual(result.output.split("\n")[3], expected_msg)
# try setting a file as target
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 = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(target_path)
+ expected_msg = f"Error: Invalid value for '--target': Directory '{target_path}' is a file."
self.assertEqual(result.output.split("\n")[3], expected_msg)
@patch('gitlint.config.LintConfigGenerator.generate_config')
def test_generate_config(self, generate_config):
""" Test for the generate-config subcommand """
- result = self.cli.invoke(cli.cli, ["generate-config"], input=u"tëstfile\n")
+ result = self.cli.invoke(cli.cli, ["generate-config"], input="tëstfile\n")
self.assertEqual(result.exit_code, 0)
- expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
- u"Successfully generated {0}\n".format(os.path.realpath(u"tëstfile"))
+ expected_msg = "Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \
+ f"Successfully generated {os.path.realpath('tëstfile')}\n"
self.assertEqual(result.output, expected_msg)
- generate_config.assert_called_once_with(os.path.realpath(u"tëstfile"))
+ generate_config.assert_called_once_with(os.path.realpath("tëstfile"))
def test_generate_config_negative(self):
""" Negative test for the generate-config subcommand """
# Non-existing directory
- fake_dir = os.path.abspath(u"/föo")
- fake_path = os.path.join(fake_dir, u"bar")
+ fake_dir = os.path.abspath("/föo")
+ fake_path = os.path.join(fake_dir, "bar")
result = self.cli.invoke(cli.cli, ["generate-config"], input=fake_path)
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
- expected_msg = (u"Please specify a location for the sample gitlint config file [.gitlint]: {0}\n"
- + u"Error: Directory '{1}' does not exist.\n").format(fake_path, fake_dir)
+ expected_msg = f"Please specify a location for the sample gitlint config file [.gitlint]: {fake_path}\n" + \
+ f"Error: Directory '{fake_dir}' does not exist.\n"
self.assertEqual(result.output, expected_msg)
# Existing file
@@ -509,8 +497,8 @@ class CLITests(BaseTestCase):
result = self.cli.invoke(cli.cli, ["generate-config"], input=sample_path)
self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
expected_msg = "Please specify a location for the sample gitlint " + \
- "config file [.gitlint]: {0}\n".format(sample_path) + \
- "Error: File \"{0}\" already exists.\n".format(sample_path)
+ f"config file [.gitlint]: {sample_path}\n" + \
+ f"Error: File \"{sample_path}\" already exists.\n"
self.assertEqual(result.output, expected_msg)
@patch('gitlint.cli.get_stdin_data', return_value=False)
@@ -528,10 +516,10 @@ class CLITests(BaseTestCase):
sh.git.side_effect = lambda *_args, **_kwargs: ""
result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"])
- self.assert_log_contains(u"DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
+ self.assert_log_contains("DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
self.assertEqual(result.exit_code, 0)
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst tïtle")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst tïtle")
def test_named_rules(self, _):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
config_path = self.get_sample_path(os.path.join("config", "named-rules"))
diff --git a/gitlint/tests/cli/test_cli_hooks.py b/gitlint/tests/cli/test_cli_hooks.py
index b5e7fc4..825345f 100644
--- a/gitlint/tests/cli/test_cli_hooks.py
+++ b/gitlint/tests/cli/test_cli_hooks.py
@@ -1,28 +1,18 @@
# -*- coding: utf-8 -*-
import io
+from io import StringIO
import os
from click.testing import CliRunner
-try:
- # python 2.x
- from StringIO import StringIO
-except ImportError:
- # python 3.x
- from io import StringIO # pylint: disable=ungrouped-imports
-
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch
from gitlint.tests.base import BaseTestCase
from gitlint import cli
from gitlint import hooks
from gitlint import config
+from gitlint.shell import ErrorReturnCode
from gitlint.utils import DEFAULT_ENCODING
@@ -45,12 +35,12 @@ class CLIHookTests(BaseTestCase):
self.git_version_path.stop()
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
- @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
+ @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
def test_install_hook(self, _, install_hook):
""" Test for install-hook subcommand """
result = self.cli.invoke(cli.cli, ["install-hook"])
- expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
- expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(expected_path)
+ expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
+ expected = f"Successfully installed gitlint commit-msg hook in {expected_path}\n"
self.assertEqual(result.output, expected)
self.assertEqual(result.exit_code, 0)
expected_config = config.LintConfig()
@@ -58,12 +48,12 @@ class CLIHookTests(BaseTestCase):
install_hook.assert_called_once_with(expected_config)
@patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook')
- @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
+ @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
def test_install_hook_target(self, _, install_hook):
""" Test for install-hook subcommand with a specific --target option specified """
# Specified target
result = self.cli.invoke(cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"])
- expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
+ expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path
self.assertEqual(result.exit_code, 0)
self.assertEqual(result.output, expected)
@@ -72,40 +62,40 @@ class CLIHookTests(BaseTestCase):
expected_config.target = self.SAMPLES_DIR
install_hook.assert_called_once_with(expected_config)
- @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst"))
+ @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst"))
def test_install_hook_negative(self, install_hook):
""" Negative test for install-hook subcommand """
result = self.cli.invoke(cli.cli, ["install-hook"])
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
- self.assertEqual(result.output, u"tëst\n")
+ self.assertEqual(result.output, "tëst\n")
expected_config = config.LintConfig()
expected_config.target = os.path.realpath(os.getcwd())
install_hook.assert_called_once_with(expected_config)
@patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook')
- @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur"))
+ @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur"))
def test_uninstall_hook(self, _, uninstall_hook):
""" Test for uninstall-hook subcommand """
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
- expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
- expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_path)
+ expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH)
+ expected = f"Successfully uninstalled gitlint commit-msg hook from {expected_path}\n"
self.assertEqual(result.exit_code, 0)
self.assertEqual(result.output, expected)
expected_config = config.LintConfig()
expected_config.target = os.path.realpath(os.getcwd())
uninstall_hook.assert_called_once_with(expected_config)
- @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst"))
+ @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst"))
def test_uninstall_hook_negative(self, uninstall_hook):
""" Negative test for uninstall-hook subcommand """
result = self.cli.invoke(cli.cli, ["uninstall-hook"])
self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
- self.assertEqual(result.output, u"tëst\n")
+ self.assertEqual(result.output, "tëst\n")
expected_config = config.LintConfig()
expected_config.target = os.path.realpath(os.getcwd())
uninstall_hook.assert_called_once_with(expected_config)
- def test_hook_no_tty(self):
+ def test_run_hook_no_tty(self):
""" Test for run-hook subcommand.
When no TTY is available (like is the case for this test), the hook will abort after the first check.
"""
@@ -119,9 +109,9 @@ class CLIHookTests(BaseTestCase):
# check the output which indirectly proves the same thing.
with self.tempdir() as tmpdir:
- msg_filename = os.path.join(tmpdir, u"hür")
+ msg_filename = os.path.join(tmpdir, "hür")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
- f.write(u"WIP: tïtle\n")
+ f.write("WIP: tïtle\n")
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
@@ -132,12 +122,12 @@ class CLIHookTests(BaseTestCase):
self.assertEqual(result.exit_code, 1)
@patch('gitlint.cli.shell')
- def test_hook_edit(self, shell):
+ def test_run_hook_edit(self, shell):
""" Test for run-hook subcommand, answering 'e(dit)' after commit-hook """
- set_editors = [None, u"myeditor"]
- expected_editors = [u"vim -n", u"myeditor"]
- commit_messages = [u"WIP: höok edit 1", u"WIP: höok edit 2"]
+ set_editors = [None, "myeditor"]
+ expected_editors = ["vim -n", "myeditor"]
+ commit_messages = ["WIP: höok edit 1", "WIP: höok edit 2"]
for i in range(0, len(set_editors)):
if set_editors[i]:
@@ -145,7 +135,7 @@ class CLIHookTests(BaseTestCase):
with self.patch_input(['e', 'e', 'n']):
with self.tempdir() as tmpdir:
- msg_filename = os.path.realpath(os.path.join(tmpdir, u"hür"))
+ msg_filename = os.path.realpath(os.path.join(tmpdir, "hür"))
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
f.write(commit_messages[i] + "\n")
@@ -161,18 +151,17 @@ class CLIHookTests(BaseTestCase):
self.assertEqual(result.exit_code, 2)
shell.assert_called_with(expected_editors[i] + " " + msg_filename)
- self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: editing commit message")
- self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: {0} {1}".format(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_hook_no(self):
+ 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, u"hür")
+ msg_filename = os.path.join(tmpdir, "hür")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
- f.write(u"WIP: höok no\n")
+ 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"])
@@ -184,13 +173,13 @@ class CLIHookTests(BaseTestCase):
self.assertEqual(result.exit_code, 2)
self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined")
- def test_hook_yes(self):
+ 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, u"hür")
+ msg_filename = os.path.join(tmpdir, "hür")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
- f.write(u"WIP: höok yes\n")
+ 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"])
@@ -202,8 +191,32 @@ class CLIHookTests(BaseTestCase):
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=u"WIP: Test hook stdin tïtle\n")
- def test_hook_stdin_violations(self, _):
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
+ @patch('gitlint.git.sh')
+ def test_run_hook_negative(self, sh, _):
+ """ Negative test for the run-hook subcommand: testing whether exceptions are correctly handled when
+ running `gitlint run-hook`.
+ """
+ # GIT_CONTEXT_ERROR_CODE: git error
+ 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()})
+ self.assertEqual(result.output, expected)
+ self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
+
+ # USAGE_ERROR_CODE: incorrect use of gitlint
+ result = self.cli.invoke(cli.cli, ["--staged", "run-hook"])
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_run_hook_negative_2'))
+ self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE)
+
+ # CONFIG_ERROR_CODE: incorrect config. Note that this is handled before the hook even runs
+ result = self.cli.invoke(cli.cli, ["-c", "föo.bár=1", "run-hook"])
+ self.assertEqual(result.output, "Config Error: No such rule 'föo'\n")
+ self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE)
+
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook stdin tïtle\n")
+ def test_run_hook_stdin_violations(self, _):
""" Test for passing stdin data to run-hook, expecting some violations. Equivalent of:
$ echo "WIP: Test hook stdin tïtle" | gitlint run-hook
"""
@@ -216,8 +229,8 @@ class CLIHookTests(BaseTestCase):
# Hook will auto-abort because we're using stdin. Abort = exit code 1
self.assertEqual(result.exit_code, 1)
- @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nTest bödy that is long enough")
- def test_hook_stdin_no_violations(self, _):
+ @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nTest bödy that is long enough")
+ def test_run_hook_stdin_no_violations(self, _):
""" Test for passing stdin data to run-hook, expecting *NO* violations, Equivalent of:
$ echo -e "Test tïtle\n\nTest bödy that is long enough" | gitlint run-hook
"""
@@ -229,8 +242,8 @@ class CLIHookTests(BaseTestCase):
self.assertEqual(result.output, expected_stdout)
self.assertEqual(result.exit_code, 0)
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook config tïtle\n")
- def test_hook_config(self, _):
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook config tïtle\n")
+ def test_run_hook_config(self, _):
""" Test that gitlint still respects config when running run-hook, equivalent of:
$ echo "WIP: Test hook config tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook
"""
@@ -244,18 +257,18 @@ class CLIHookTests(BaseTestCase):
@patch('gitlint.cli.get_stdin_data', return_value=False)
@patch('gitlint.git.sh')
- def test_hook_local_commit(self, sh, _):
+ def test_run_hook_local_commit(self, sh, _):
""" Test running the hook on the last commit-msg from the local repo, equivalent of:
$ gitlint run-hook
and then choosing 'e'
"""
sh.git.side_effect = [
"6f29bf81a8322a04071bb794666e48c443a90360",
- u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"WIP: commït-title\n\ncommït-body",
- u"#", # git config --get core.commentchar
- u"commit-1-branch-1\ncommit-1-branch-2\n",
- u"file1.txt\npåth/to/file2.txt\n"
+ "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ "WIP: commït-title\n\ncommït-body",
+ "#", # git config --get core.commentchar
+ "commit-1-branch-1\ncommit-1-branch-2\n",
+ "file1.txt\npåth/to/file2.txt\n"
]
with self.patch_input(['e']):
diff --git a/gitlint/tests/config/test_config.py b/gitlint/tests/config/test_config.py
index b981a86..93e35de 100644
--- a/gitlint/tests/config/test_config.py
+++ b/gitlint/tests/config/test_config.py
@@ -1,16 +1,11 @@
# -*- coding: utf-8 -*-
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+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.tests.base import BaseTestCase, ustr
+from gitlint.tests.base import BaseTestCase
class LintConfigTests(BaseTestCase):
@@ -29,20 +24,20 @@ class LintConfigTests(BaseTestCase):
config = LintConfig()
# non-existing rule
- expected_error_msg = u"No such rule 'föobar'"
+ expected_error_msg = "No such rule 'föobar'"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config.set_rule_option(u'föobar', u'lïne-length', 60)
# non-existing option
- expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
+ expected_error_msg = "Rule 'title-max-length' has no option 'föobar'"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config.set_rule_option('title-max-length', u'föobar', 60)
# invalid option value
- expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
- u"Option 'line-length' must be a positive integer (current value: 'föo')."
+ expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \
+ "Option 'line-length' must be a positive integer (current value: 'föo')."
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
- config.set_rule_option('title-max-length', 'line-length', u"föo")
+ config.set_rule_option('title-max-length', 'line-length', "föo")
def test_set_general_option(self):
config = LintConfig()
@@ -117,7 +112,7 @@ class LintConfigTests(BaseTestCase):
actual_rule = config.rules.find_rule("contrib-title-conventional-commits")
self.assertTrue(actual_rule.is_contrib)
- self.assertEqual(ustr(type(actual_rule)), "<class 'conventional_commit.ConventionalCommit'>")
+ self.assertEqual(str(type(actual_rule)), "<class 'conventional_commit.ConventionalCommit'>")
self.assertEqual(actual_rule.id, 'CT1')
self.assertEqual(actual_rule.name, u'contrib-title-conventional-commits')
self.assertEqual(actual_rule.target, rules.CommitMessageTitle)
@@ -135,7 +130,7 @@ class LintConfigTests(BaseTestCase):
actual_rule = config.rules.find_rule("contrib-body-requires-signed-off-by")
self.assertTrue(actual_rule.is_contrib)
- self.assertEqual(ustr(type(actual_rule)), "<class 'signedoff_by.SignedOffBy'>")
+ self.assertEqual(str(type(actual_rule)), "<class 'signedoff_by.SignedOffBy'>")
self.assertEqual(actual_rule.id, 'CC1')
self.assertEqual(actual_rule.name, u'contrib-body-requires-signed-off-by')
@@ -151,15 +146,15 @@ class LintConfigTests(BaseTestCase):
def test_contrib_negative(self):
config = LintConfig()
# non-existent contrib rule
- with self.assertRaisesMessage(LintConfigError, u"No contrib rule with id or name 'föo' found."):
- config.contrib = u"contrib-title-conventional-commits,föo"
+ with self.assertRaisesMessage(LintConfigError, "No contrib rule with id or name 'föo' found."):
+ config.contrib = "contrib-title-conventional-commits,föo"
# UserRuleError, RuleOptionError should be re-raised as LintConfigErrors
- side_effects = [rules.UserRuleError(u"üser-rule"), options.RuleOptionError(u"rüle-option")]
+ 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 self.assertRaisesMessage(LintConfigError, ustr(side_effect)):
- config.contrib = u"contrib-title-conventional-commits"
+ with self.assertRaisesMessage(LintConfigError, str(side_effect)):
+ config.contrib = "contrib-title-conventional-commits"
def test_extra_path(self):
config = LintConfig()
@@ -168,11 +163,11 @@ class LintConfigTests(BaseTestCase):
self.assertEqual(config.extra_path, self.get_user_rules_path())
actual_rule = config.rules.find_rule('UC1')
self.assertTrue(actual_rule.is_user_defined)
- self.assertEqual(ustr(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>")
+ self.assertEqual(str(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>")
self.assertEqual(actual_rule.id, 'UC1')
self.assertEqual(actual_rule.name, u'my-üser-commit-rule')
self.assertEqual(actual_rule.target, None)
- expected_rule_option = options.IntOption('violation-count', 1, u"Number of violåtions to return")
+ expected_rule_option = options.IntOption('violation-count', 1, "Number of violåtions to return")
self.assertListEqual(actual_rule.options_spec, [expected_rule_option])
self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option})
@@ -183,10 +178,10 @@ class LintConfigTests(BaseTestCase):
def test_extra_path_negative(self):
config = LintConfig()
- regex = u"Option extra-path must be either an existing directory or file (current value: 'föo/bar')"
+ regex = "Option extra-path must be either an existing directory or file (current value: 'föo/bar')"
# incorrect extra_path
with self.assertRaisesMessage(LintConfigError, regex):
- config.extra_path = u"föo/bar"
+ config.extra_path = "föo/bar"
# extra path contains classes with errors
with self.assertRaisesMessage(LintConfigError,
@@ -198,17 +193,17 @@ class LintConfigTests(BaseTestCase):
# Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes
with self.assertRaisesMessage(LintConfigError, "'foo' is not a valid gitlint option"):
- config.set_general_option("foo", u"bår")
+ config.set_general_option("foo", "bår")
# try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from
# being set
with self.assertRaisesMessage(LintConfigError, "'_config_path' is not a valid gitlint option"):
- config.set_general_option("_config_path", u"bår")
+ config.set_general_option("_config_path", "bår")
# invalid verbosity
- incorrect_values = [-1, u"föo"]
+ incorrect_values = [-1, "föo"]
for value in incorrect_values:
- expected_msg = u"Option 'verbosity' must be a positive integer (current value: '{0}')".format(value)
+ expected_msg = f"Option 'verbosity' must be a positive integer (current value: '{value}')"
with self.assertRaisesMessage(LintConfigError, expected_msg):
config.verbosity = value
@@ -220,12 +215,12 @@ class LintConfigTests(BaseTestCase):
# invalid ignore_xxx_commits
ignore_attributes = ["ignore_merge_commits", "ignore_fixup_commits", "ignore_squash_commits",
"ignore_revert_commits"]
- incorrect_values = [-1, 4, u"föo"]
+ incorrect_values = [-1, 4, "föo"]
for attribute in ignore_attributes:
for value in incorrect_values:
option_name = attribute.replace("_", "-")
with self.assertRaisesMessage(LintConfigError,
- "Option '{0}' must be either 'true' or 'false'".format(option_name)):
+ f"Option '{option_name}' must be either 'true' or 'false'"):
setattr(config, attribute, value)
# invalid ignore -> not here because ignore is a ListOption which converts everything to a string before
@@ -235,15 +230,15 @@ class LintConfigTests(BaseTestCase):
for attribute in ['debug', 'staged', 'ignore_stdin']:
option_name = attribute.replace("_", "-")
with self.assertRaisesMessage(LintConfigError,
- "Option '{0}' must be either 'true' or 'false'".format(option_name)):
- setattr(config, attribute, u"föobar")
+ f"Option '{option_name}' must be either 'true' or 'false'"):
+ setattr(config, attribute, "föobar")
# extra-path has its own negative test
# invalid target
with self.assertRaisesMessage(LintConfigError,
- u"Option target must be an existing directory (current value: 'föo/bar')"):
- config.target = u"föo/bar"
+ "Option target must be an existing directory (current value: 'föo/bar')"):
+ config.target = "föo/bar"
def test_ignore_independent_from_rules(self):
# Test that the lintconfig rules are not modified when setting config.ignore
@@ -273,9 +268,9 @@ class LintConfigTests(BaseTestCase):
# Other attributes don't matter
config1 = LintConfig()
config2 = LintConfig()
- config1.foo = u"bår"
+ config1.foo = "bår"
self.assertEqual(config1, config2)
- config2.foo = u"dūr"
+ config2.foo = "dūr"
self.assertEqual(config1, config2)
@@ -283,5 +278,5 @@ class LintConfigGeneratorTests(BaseTestCase):
@staticmethod
@patch('gitlint.config.shutil.copyfile')
def test_install_commit_msg_hook_negative(copy):
- LintConfigGenerator.generate_config(u"föo/bar/test")
- copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, u"föo/bar/test")
+ LintConfigGenerator.generate_config("föo/bar/test")
+ copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, "föo/bar/test")
diff --git a/gitlint/tests/config/test_config_builder.py b/gitlint/tests/config/test_config_builder.py
index 5a28c9f..e0d7f9b 100644
--- a/gitlint/tests/config/test_config_builder.py
+++ b/gitlint/tests/config/test_config_builder.py
@@ -42,30 +42,30 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
# nothing gitlint
- config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint\nfoo"))
+ config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint\nfoo"))
config = config_builder.build()
self.assertSequenceEqual(config.rules, original_rules)
self.assertListEqual(config.ignore, [])
# ignore all rules
- config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: all\nfoo"))
+ config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: all\nfoo"))
config = config_builder.build()
self.assertEqual(config.ignore, original_rule_ids)
# ignore all rules, no space
- config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore:all\nfoo"))
+ config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore:all\nfoo"))
config = config_builder.build()
self.assertEqual(config.ignore, original_rule_ids)
# ignore all rules, more spacing
- config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: \t all\nfoo"))
+ config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: \t all\nfoo"))
config = config_builder.build()
self.assertEqual(config.ignore, original_rule_ids)
def test_set_from_commit_ignore_specific(self):
# ignore specific rules
config_builder = LintConfigBuilder()
- config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: T1, body-hard-tab"))
+ config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: T1, body-hard-tab"))
config = config_builder.build()
self.assertEqual(config.ignore, ["T1", "body-hard-tab"])
@@ -89,14 +89,14 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
# bad config file load
- foo_path = self.get_sample_path(u"föo")
- expected_error_msg = u"Invalid file path: {0}".format(foo_path)
+ foo_path = self.get_sample_path("föo")
+ expected_error_msg = f"Invalid file path: {foo_path}"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.set_from_config_file(foo_path)
# error during file parsing
path = self.get_sample_path("config/no-sections")
- expected_error_msg = u"File contains no section headers."
+ expected_error_msg = "File contains no section headers."
# We only match the start of the message here, since the exact message can vary depending on platform
with self.assertRaisesRegex(LintConfigError, expected_error_msg):
config_builder.set_from_config_file(path)
@@ -105,7 +105,7 @@ class LintConfigBuilderTests(BaseTestCase):
path = self.get_sample_path("config/nonexisting-rule")
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
- expected_error_msg = u"No such rule 'föobar'"
+ expected_error_msg = "No such rule 'föobar'"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
@@ -113,7 +113,7 @@ class LintConfigBuilderTests(BaseTestCase):
path = self.get_sample_path("config/nonexisting-general-option")
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
- expected_error_msg = u"'foo' is not a valid gitlint option"
+ expected_error_msg = "'foo' is not a valid gitlint option"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
@@ -121,7 +121,7 @@ class LintConfigBuilderTests(BaseTestCase):
path = self.get_sample_path("config/nonexisting-option")
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
- expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
+ expected_error_msg = "Rule 'title-max-length' has no option 'föobar'"
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
@@ -129,8 +129,8 @@ class LintConfigBuilderTests(BaseTestCase):
path = self.get_sample_path("config/invalid-option-value")
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
- expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
- u"Option 'line-length' must be a positive integer (current value: 'föo')."
+ expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \
+ "Option 'line-length' must be a positive integer (current value: 'föo')."
with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
@@ -141,39 +141,39 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
config_builder.set_config_from_string_list(['general.verbosity=1', 'title-max-length.line-length=60',
'body-max-line-length.line-length=120',
- u"title-must-not-contain-word.words=håha"])
+ "title-must-not-contain-word.words=håha"])
config = config_builder.build()
self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60)
self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120)
- self.assertListEqual(config.get_rule_option('title-must-not-contain-word', 'words'), [u"håha"])
+ self.assertListEqual(config.get_rule_option('title-must-not-contain-word', 'words'), ["håha"])
self.assertEqual(config.verbosity, 1)
def test_set_config_from_string_list_negative(self):
config_builder = LintConfigBuilder()
# assert error on incorrect rule - this happens at build time
- config_builder.set_config_from_string_list([u"föo.bar=1"])
- with self.assertRaisesMessage(LintConfigError, u"No such rule 'föo'"):
+ config_builder.set_config_from_string_list(["föo.bar=1"])
+ with self.assertRaisesMessage(LintConfigError, "No such rule 'föo'"):
config_builder.build()
# no equal sign
- expected_msg = u"'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'"
+ expected_msg = "'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'"
with self.assertRaisesMessage(LintConfigError, expected_msg):
- config_builder.set_config_from_string_list([u"föo.bar"])
+ config_builder.set_config_from_string_list(["föo.bar"])
# missing value
- expected_msg = u"'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'"
+ expected_msg = "'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'"
with self.assertRaisesMessage(LintConfigError, expected_msg):
- config_builder.set_config_from_string_list([u"föo.bar="])
+ config_builder.set_config_from_string_list(["föo.bar="])
# space instead of equal sign
- expected_msg = u"'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
+ expected_msg = "'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
with self.assertRaisesMessage(LintConfigError, expected_msg):
- config_builder.set_config_from_string_list([u"föo.bar 1"])
+ config_builder.set_config_from_string_list(["föo.bar 1"])
# no period between rule and option names
- expected_msg = u"'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
+ expected_msg = "'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.set_config_from_string_list([u'föobar=1'])
@@ -216,15 +216,15 @@ class LintConfigBuilderTests(BaseTestCase):
# Add a named rule by setting an option in the config builder that follows the named rule pattern
# Assert that whitespace in the rule name is stripped
rule_qualifiers = [u'T7:my-extra-rüle', u' T7 : my-extra-rüle ', u'\tT7:\tmy-extra-rüle\t',
- u'T7:\t\n \tmy-extra-rüle\t\n\n', u"title-match-regex:my-extra-rüle"]
+ u'T7:\t\n \tmy-extra-rüle\t\n\n', "title-match-regex:my-extra-rüle"]
for rule_qualifier in rule_qualifiers:
config_builder = LintConfigBuilder()
- config_builder.set_option(rule_qualifier, 'regex', u"föo")
+ config_builder.set_option(rule_qualifier, 'regex', "föo")
expected_rules = copy.deepcopy(default_rules)
- my_rule = rules.TitleRegexMatches({'regex': u"föo"})
- my_rule.id = rules.TitleRegexMatches.id + u":my-extra-rüle"
- my_rule.name = rules.TitleRegexMatches.name + u":my-extra-rüle"
+ my_rule = rules.TitleRegexMatches({'regex': "föo"})
+ my_rule.id = rules.TitleRegexMatches.id + ":my-extra-rüle"
+ my_rule.name = rules.TitleRegexMatches.name + ":my-extra-rüle"
expected_rules._rules[u'T7:my-extra-rüle'] = my_rule
self.assertEqual(config_builder.build().rules, expected_rules)
@@ -233,32 +233,32 @@ class LintConfigBuilderTests(BaseTestCase):
# to the same rule
for other_rule_qualifier in rule_qualifiers:
cb = config_builder.clone()
- cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + u"bōr")
+ cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + "bōr")
# before setting the expected rule option value correctly, the RuleCollection should be different
self.assertNotEqual(cb.build().rules, expected_rules)
# after setting the option on the expected rule, it should be equal
- my_rule.options['regex'].set(other_rule_qualifier + u"bōr")
+ my_rule.options['regex'].set(other_rule_qualifier + "bōr")
self.assertEqual(cb.build().rules, expected_rules)
- my_rule.options['regex'].set(u"wrong")
+ my_rule.options['regex'].set("wrong")
def test_named_rules_negative(self):
# T7 = title-match-regex
# Invalid rule name
- for invalid_name in ["", " ", " ", "\t", "\n", u"å b", u"å:b", u"åb:", u":åb"]:
+ for invalid_name in ["", " ", " ", "\t", "\n", "å b", "å:b", "åb:", ":åb"]:
config_builder = LintConfigBuilder()
- config_builder.set_option(u"T7:{0}".format(invalid_name), 'regex', u"tëst")
- expected_msg = u"The rule-name part in 'T7:{0}' cannot contain whitespace, colons or be empty"
- with self.assertRaisesMessage(LintConfigError, expected_msg.format(invalid_name)):
+ config_builder.set_option(f"T7:{invalid_name}", 'regex', "tëst")
+ expected_msg = f"The rule-name part in 'T7:{invalid_name}' cannot contain whitespace, colons or be empty"
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.build()
# Invalid parent rule name
config_builder = LintConfigBuilder()
- config_builder.set_option(u"Ž123:foöbar", u"fåke-option", u"fåke-value")
- with self.assertRaisesMessage(LintConfigError, u"No such rule 'Ž123' (named rule: 'Ž123:foöbar')"):
+ config_builder.set_option("Ž123:foöbar", "fåke-option", "fåke-value")
+ with self.assertRaisesMessage(LintConfigError, "No such rule 'Ž123' (named rule: 'Ž123:foöbar')"):
config_builder.build()
# Invalid option name (this is the same as with regular rules)
config_builder = LintConfigBuilder()
- config_builder.set_option(u"T7:foöbar", u"blå", u"my-rëgex")
- with self.assertRaisesMessage(LintConfigError, u"Rule 'T7:foöbar' has no option 'blå'"):
+ config_builder.set_option("T7:foöbar", "blå", "my-rëgex")
+ with self.assertRaisesMessage(LintConfigError, "Rule 'T7:foöbar' has no option 'blå'"):
config_builder.build()
diff --git a/gitlint/tests/config/test_config_precedence.py b/gitlint/tests/config/test_config_precedence.py
index a0eeccd..aa4de88 100644
--- a/gitlint/tests/config/test_config_precedence.py
+++ b/gitlint/tests/config/test_config_precedence.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
-try:
- # python 2.x
- from StringIO import StringIO
-except ImportError:
- # python 3.x
- from io import StringIO
+from io import StringIO
from click.testing import CliRunner
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch
from gitlint.tests.base import BaseTestCase
from gitlint import cli
@@ -25,7 +15,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
def setUp(self):
self.cli = CliRunner()
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP:fö\n\nThis is å test message\n")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP:fö\n\nThis is å test message\n")
def test_config_precedence(self, _):
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
# to more easily test everything
@@ -41,14 +31,14 @@ class LintConfigPrecedenceTests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
- self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
+ self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
# 2. environment variables
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path],
env={"GITLINT_VERBOSITY": "3"})
self.assertEqual(result.output, "")
- self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
+ self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
# 3. commandline -c flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
@@ -66,9 +56,9 @@ class LintConfigPrecedenceTests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli)
self.assertEqual(result.output, "")
- self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
+ self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test")
+ @patch('gitlint.cli.get_stdin_data', return_value="WIP: This is å test")
def test_ignore_precedence(self, get_stdin_data):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
# --ignore takes precedence over -c general.ignore
@@ -77,11 +67,11 @@ class LintConfigPrecedenceTests(BaseTestCase):
self.assertEqual(result.exit_code, 1)
# We still expect the T5 violation, but no B6 violation as --ignore overwrites -c general.ignore
self.assertEqual(stderr.getvalue(),
- u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n")
+ "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n")
# test that we can also still configure a rule that is first ignored but then not
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- get_stdin_data.return_value = u"This is å test"
+ get_stdin_data.return_value = "This is å test"
# --ignore takes precedence over -c general.ignore
result = self.cli.invoke(cli.cli, ["-c", "general.ignore=title-max-length",
"-c", "title-max-length.line-length=5",
@@ -91,7 +81,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
# We still expect the T1 violation with custom config,
# but no B6 violation as --ignore overwrites -c general.ignore
- self.assertEqual(stderr.getvalue(), u"1: T1 Title exceeds max length (14>5): \"This is å test\"\n")
+ self.assertEqual(stderr.getvalue(), "1: T1 Title exceeds max length (14>5): \"This is å test\"\n")
def test_general_option_after_rule_option(self):
# We used to have a bug where we didn't process general options before setting specific options, this would
diff --git a/gitlint/tests/config/test_rule_collection.py b/gitlint/tests/config/test_rule_collection.py
index 089992c..5a50be0 100644
--- a/gitlint/tests/config/test_rule_collection.py
+++ b/gitlint/tests/config/test_rule_collection.py
@@ -10,34 +10,34 @@ class RuleCollectionTests(BaseTestCase):
def test_add_rule(self):
collection = RuleCollection()
- collection.add_rule(rules.TitleMaxLength, u"my-rüle", {"my_attr": u"föo", "my_attr2": 123})
+ collection.add_rule(rules.TitleMaxLength, "my-rüle", {"my_attr": "föo", "my_attr2": 123})
expected = rules.TitleMaxLength()
- expected.id = u"my-rüle"
- expected.my_attr = u"föo"
+ expected.id = "my-rüle"
+ expected.my_attr = "föo"
expected.my_attr2 = 123
self.assertEqual(len(collection), 1)
- self.assertDictEqual(collection._rules, OrderedDict({u"my-rüle": expected}))
+ self.assertDictEqual(collection._rules, OrderedDict({"my-rüle": expected}))
# Need to explicitely compare expected attributes as the rule.__eq__ method does not compare these attributes
self.assertEqual(collection._rules[expected.id].my_attr, expected.my_attr)
self.assertEqual(collection._rules[expected.id].my_attr2, expected.my_attr2)
def test_add_find_rule(self):
collection = RuleCollection()
- collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": u"föo"})
+ collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": "föo"})
# find by id
expected = rules.TitleMaxLength()
rule = collection.find_rule('T1')
self.assertEqual(rule, expected)
- self.assertEqual(rule.my_attr, u"föo")
+ self.assertEqual(rule.my_attr, "föo")
# find by name
expected2 = rules.TitleTrailingWhitespace()
rule = collection.find_rule('title-trailing-whitespace')
self.assertEqual(rule, expected2)
- self.assertEqual(rule.my_attr, u"föo")
+ self.assertEqual(rule.my_attr, "föo")
# find non-existing
rule = collection.find_rule(u'föo')
@@ -45,8 +45,8 @@ class RuleCollectionTests(BaseTestCase):
def test_delete_rules_by_attr(self):
collection = RuleCollection()
- collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": u"bår"})
- collection.add_rules([rules.BodyHardTab], {"hur": u"dûr"})
+ collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": "bår"})
+ collection.add_rules([rules.BodyHardTab], {"hur": "dûr"})
# Assert all rules are there as expected
self.assertEqual(len(collection), 3)
@@ -54,11 +54,11 @@ class RuleCollectionTests(BaseTestCase):
self.assertEqual(collection.find_rule(expected_rule.id), expected_rule)
# Delete rules by attr, assert that we still have the right rules in the collection
- collection.delete_rules_by_attr("foo", u"bår")
+ collection.delete_rules_by_attr("foo", "bår")
self.assertEqual(len(collection), 1)
self.assertIsNone(collection.find_rule(rules.TitleMaxLength.id), None)
self.assertIsNone(collection.find_rule(rules.TitleTrailingWhitespace.id), None)
found = collection.find_rule(rules.BodyHardTab.id)
self.assertEqual(found, rules.BodyHardTab())
- self.assertEqual(found.hur, u"dûr")
+ self.assertEqual(found.hur, "dûr")
diff --git a/gitlint/tests/contrib/rules/test_conventional_commit.py b/gitlint/tests/contrib/rules/test_conventional_commit.py
index 001af32..fb492df 100644
--- a/gitlint/tests/contrib/rules/test_conventional_commit.py
+++ b/gitlint/tests/contrib/rules/test_conventional_commit.py
@@ -20,28 +20,28 @@ class ContribConventionalCommitTests(BaseTestCase):
# No violations when using a correct type and format
for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]:
- violations = rule.validate(type + u": föo", None)
+ violations = rule.validate(type + ": föo", None)
self.assertListEqual([], violations)
# assert violation on wrong type
expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
- " style, refactor, perf, test, revert, ci, build", u"bår: foo")
- violations = rule.validate(u"bår: foo", None)
+ " style, refactor, perf, test, revert, ci, build", "bår: foo")
+ violations = rule.validate("bår: foo", None)
self.assertListEqual([expected_violation], violations)
# assert violation on wrong format
expected_violation = RuleViolation("CT1", "Title does not follow ConventionalCommits.org format "
- "'type(optional-scope): description'", u"fix föo")
- violations = rule.validate(u"fix föo", None)
+ "'type(optional-scope): description'", "fix föo")
+ violations = rule.validate("fix föo", None)
self.assertListEqual([expected_violation], violations)
# assert no violation when adding new type
- rule = ConventionalCommit({'types': [u"föo", u"bär"]})
- for typ in [u"föo", u"bär"]:
- violations = rule.validate(typ + u": hür dur", None)
+ rule = ConventionalCommit({'types': ["föo", "bär"]})
+ for typ in ["föo", "bär"]:
+ violations = rule.validate(typ + ": hür dur", None)
self.assertListEqual([], violations)
# assert violation when using incorrect type when types have been reconfigured
- violations = rule.validate(u"fix: hür dur", None)
- expected_violation = RuleViolation("CT1", u"Title does not start with one of föo, bär", u"fix: hür dur")
+ violations = rule.validate("fix: hür dur", None)
+ expected_violation = RuleViolation("CT1", "Title does not start with one of föo, bär", "fix: hür dur")
self.assertListEqual([expected_violation], violations)
diff --git a/gitlint/tests/contrib/rules/test_signedoff_by.py b/gitlint/tests/contrib/rules/test_signedoff_by.py
index 934aec5..c92f1a6 100644
--- a/gitlint/tests/contrib/rules/test_signedoff_by.py
+++ b/gitlint/tests/contrib/rules/test_signedoff_by.py
@@ -19,14 +19,14 @@ class ContribSignedOffByTests(BaseTestCase):
def test_signedoff_by(self):
# No violations when 'Signed-Off-By' line is present
rule = SignedOffBy()
- violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body\nSigned-Off-By: John Smith"))
+ violations = rule.validate(self.gitcommit("Föobar\n\nMy Body\nSigned-Off-By: John Smith"))
self.assertListEqual([], violations)
# Assert violation when no 'Signed-Off-By' line is present
- violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body"))
+ violations = rule.validate(self.gitcommit("Föobar\n\nMy Body"))
expected_violation = RuleViolation("CC1", "Body does not contain a 'Signed-Off-By' line", line_nr=1)
self.assertListEqual(violations, [expected_violation])
# Assert violation when no 'Signed-Off-By' in title but not in body
- violations = rule.validate(self.gitcommit(u"Signed-Off-By\n\nFöobar"))
+ violations = rule.validate(self.gitcommit("Signed-Off-By\n\nFöobar"))
self.assertListEqual(violations, [expected_violation])
diff --git a/gitlint/tests/contrib/test_contrib_rules.py b/gitlint/tests/contrib/test_contrib_rules.py
index 84db2d5..8ab6539 100644
--- a/gitlint/tests/contrib/test_contrib_rules.py
+++ b/gitlint/tests/contrib/test_contrib_rules.py
@@ -6,8 +6,6 @@ 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.utils import ustr
-
class ContribRuleTests(BaseTestCase):
@@ -24,10 +22,9 @@ class ContribRuleTests(BaseTestCase):
# Find all python files in the contrib dir and assert there's a corresponding test file
for filename in os.listdir(self.CONTRIB_DIR):
if filename.endswith(".py") and filename not in ["__init__.py"]:
- expected_test_file = ustr(u"test_" + filename)
- error_msg = u"Every Contrib Rule must have associated tests. " + \
- "Expected test file {0} not found.".format(os.path.join(contrib_tests_dir,
- expected_test_file))
+ expected_test_file = "test_" + filename
+ error_msg = "Every Contrib Rule must have associated tests. " + \
+ f"Expected test file {os.path.join(contrib_tests_dir, expected_test_file)} not found."
self.assertIn(expected_test_file, contrib_test_files, error_msg)
def test_contrib_rule_naming_conventions(self):
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1
new file mode 100644
index 0000000..9082830
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1
@@ -0,0 +1,2 @@
+gitlint: checking commit message...
+{git_repo} is not a git repository.
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2
new file mode 100644
index 0000000..bafbf29
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2
@@ -0,0 +1,2 @@
+gitlint: checking commit message...
+Error: The 'staged' option (--staged) can only be used when using '--msg-filename' or when piping data to gitlint via stdin.
diff --git a/gitlint/tests/git/test_git.py b/gitlint/tests/git/test_git.py
index 1830119..7b9b7c6 100644
--- a/gitlint/tests/git/test_git.py
+++ b/gitlint/tests/git/test_git.py
@@ -1,12 +1,7 @@
# -*- coding: utf-8 -*-
import os
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch
from gitlint.shell import ErrorReturnCode, CommandNotFound
@@ -19,7 +14,7 @@ class GitTests(BaseTestCase):
# Expected special_args passed to 'sh'
expected_sh_special_args = {
'_tty_out': False,
- '_cwd': u"fåke/path"
+ '_cwd': "fåke/path"
}
@patch('gitlint.git.sh')
@@ -28,7 +23,7 @@ class GitTests(BaseTestCase):
expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \
"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git."
with self.assertRaisesMessage(GitNotInstalledError, expected_msg):
- GitContext.from_local_repository(u"fåke/path")
+ GitContext.from_local_repository("fåke/path")
# assert that commit message was read using git command
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
@@ -39,8 +34,8 @@ class GitTests(BaseTestCase):
err = b"fatal: Not a git repository (or any of the parent directories): .git"
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
- with self.assertRaisesMessage(GitContextError, u"fåke/path is not a git repository."):
- GitContext.from_local_repository(u"fåke/path")
+ with self.assertRaisesMessage(GitContextError, "fåke/path is not a git repository."):
+ GitContext.from_local_repository("fåke/path")
# assert that commit message was read using git command
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
@@ -49,9 +44,9 @@ class GitTests(BaseTestCase):
err = b"fatal: Random git error"
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
- expected_msg = u"An error occurred while executing 'git log -1 --pretty=%H': {0}".format(err)
+ expected_msg = f"An error occurred while executing 'git log -1 --pretty=%H': {err}"
with self.assertRaisesMessage(GitContextError, expected_msg):
- GitContext.from_local_repository(u"fåke/path")
+ GitContext.from_local_repository("fåke/path")
# assert that commit message was read using git command
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
@@ -63,9 +58,9 @@ class GitTests(BaseTestCase):
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
- expected_msg = u"Current branch has no commits. Gitlint requires at least one commit to function."
+ expected_msg = "Current branch has no commits. Gitlint requires at least one commit to function."
with self.assertRaisesMessage(GitContextError, expected_msg):
- GitContext.from_local_repository(u"fåke/path")
+ GitContext.from_local_repository("fåke/path")
# assert that commit message was read using git command
sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args)
@@ -78,12 +73,12 @@ class GitTests(BaseTestCase):
b"'git <command> [<revision>...] -- [<file>...]'")
sh.git.side_effect = [
- u"#\n", # git config --get core.commentchar
+ "#\n", # git config --get core.commentchar
ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err)
]
with self.assertRaisesMessage(GitContextError, expected_msg):
- context = GitContext.from_commit_msg(u"test")
+ context = GitContext.from_commit_msg("test")
context.current_branch
# assert that commit message was read using git command
@@ -95,21 +90,19 @@ class GitTests(BaseTestCase):
self.assertEqual(git_commentchar(), "#")
git.return_value.exit_code = 0
- git.return_value.__str__ = lambda _: u"ä"
- git.return_value.__unicode__ = lambda _: u"ä"
- self.assertEqual(git_commentchar(), u"ä")
+ git.return_value = "ä"
+ self.assertEqual(git_commentchar(), "ä")
git.return_value = ';\n'
- self.assertEqual(git_commentchar(os.path.join(u"/föo", u"bar")), ';')
+ self.assertEqual(git_commentchar(os.path.join("/föo", "bar")), ';')
git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1],
- _cwd=os.path.join(u"/föo", u"bar"))
+ _cwd=os.path.join("/föo", "bar"))
@patch("gitlint.git._git")
def test_git_hooks_dir(self, git):
- hooks_dir = os.path.join(u"föo", ".git", "hooks")
- git.return_value.__str__ = lambda _: hooks_dir + "\n"
- git.return_value.__unicode__ = lambda _: hooks_dir + "\n"
- self.assertEqual(git_hooks_dir(u"/blä"), os.path.abspath(os.path.join(u"/blä", hooks_dir)))
+ hooks_dir = os.path.join("föo", ".git", "hooks")
+ git.return_value = hooks_dir + "\n"
+ self.assertEqual(git_hooks_dir("/blä"), os.path.abspath(os.path.join("/blä", hooks_dir)))
- git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd=u"/blä")
+ git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd="/blä")
diff --git a/gitlint/tests/git/test_git_commit.py b/gitlint/tests/git/test_git_commit.py
index 5f87a8e..6bb545a 100644
--- a/gitlint/tests/git/test_git_commit.py
+++ b/gitlint/tests/git/test_git_commit.py
@@ -6,17 +6,11 @@ import dateutil
import arrow
-try:
- # python 2.x
- from mock import patch, call
-except ImportError:
- # python 3.x
- from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch, call
from gitlint.tests.base import BaseTestCase
from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage
from gitlint.shell import ErrorReturnCode
-from gitlint.utils import ustr
class GitCommitTests(BaseTestCase):
@@ -24,7 +18,7 @@ class GitCommitTests(BaseTestCase):
# Expected special_args passed to 'sh'
expected_sh_special_args = {
'_tty_out': False,
- '_cwd': u"fåke/path"
+ '_cwd': "fåke/path"
}
@patch('gitlint.git.sh')
@@ -33,14 +27,14 @@ class GitCommitTests(BaseTestCase):
sh.git.side_effect = [
sample_sha,
- u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"cömmit-title\n\ncömmit-body",
- u"#", # git config --get core.commentchar
- u"file1.txt\npåth/to/file2.txt\n",
- u"foöbar\n* hürdur\n"
+ "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ "cömmit-title\n\ncömmit-body",
+ "#", # git config --get core.commentchar
+ "file1.txt\npåth/to/file2.txt\n",
+ "foöbar\n* hürdur\n"
]
- context = GitContext.from_local_repository(u"fåke/path")
+ context = GitContext.from_local_repository("fåke/path")
# assert that commit info was read using git command
expected_calls = [
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
@@ -57,13 +51,13 @@ class GitCommitTests(BaseTestCase):
last_commit = context.commits[-1]
self.assertIsInstance(last_commit, LocalGitCommit)
self.assertEqual(last_commit.sha, sample_sha)
- self.assertEqual(last_commit.message.title, u"cömmit-title")
- self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
- self.assertEqual(last_commit.author_name, u"test åuthor")
- self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
+ self.assertEqual(last_commit.message.title, "cömmit-title")
+ self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
+ self.assertEqual(last_commit.author_name, "test åuthor")
+ self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
- self.assertListEqual(last_commit.parents, [u"åbc"])
+ self.assertListEqual(last_commit.parents, ["åbc"])
self.assertFalse(last_commit.is_merge_commit)
self.assertFalse(last_commit.is_fixup_commit)
self.assertFalse(last_commit.is_squash_commit)
@@ -72,11 +66,11 @@ class GitCommitTests(BaseTestCase):
# First 2 'git log' calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
# 'git diff-tree' should have happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
- self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
+ self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
# All expected calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls)
@@ -86,14 +80,14 @@ class GitCommitTests(BaseTestCase):
sh.git.side_effect = [
sample_sha,
- u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"cömmit-title\n\ncömmit-body",
- u"#", # git config --get core.commentchar
- u"file1.txt\npåth/to/file2.txt\n",
- u"foöbar\n* hürdur\n"
+ "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ "cömmit-title\n\ncömmit-body",
+ "#", # git config --get core.commentchar
+ "file1.txt\npåth/to/file2.txt\n",
+ "foöbar\n* hürdur\n"
]
- context = GitContext.from_local_repository(u"fåke/path", sample_sha)
+ context = GitContext.from_local_repository("fåke/path", sample_sha)
# assert that commit info was read using git command
expected_calls = [
call("rev-list", sample_sha, **self.expected_sh_special_args),
@@ -110,13 +104,13 @@ class GitCommitTests(BaseTestCase):
last_commit = context.commits[-1]
self.assertIsInstance(last_commit, LocalGitCommit)
self.assertEqual(last_commit.sha, sample_sha)
- self.assertEqual(last_commit.message.title, u"cömmit-title")
- self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
- self.assertEqual(last_commit.author_name, u"test åuthor")
- self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
+ self.assertEqual(last_commit.message.title, "cömmit-title")
+ self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
+ self.assertEqual(last_commit.author_name, "test åuthor")
+ self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
- self.assertListEqual(last_commit.parents, [u"åbc"])
+ self.assertListEqual(last_commit.parents, ["åbc"])
self.assertFalse(last_commit.is_merge_commit)
self.assertFalse(last_commit.is_fixup_commit)
self.assertFalse(last_commit.is_squash_commit)
@@ -125,11 +119,11 @@ class GitCommitTests(BaseTestCase):
# First 2 'git log' calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
# 'git diff-tree' should have happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
- self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
+ self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
# All expected calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls)
@@ -139,14 +133,14 @@ class GitCommitTests(BaseTestCase):
sh.git.side_effect = [
sample_sha,
- u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n"
- u"Merge \"foo bår commit\"",
- u"#", # git config --get core.commentchar
- u"file1.txt\npåth/to/file2.txt\n",
- u"foöbar\n* hürdur\n"
+ "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n"
+ "Merge \"foo bår commit\"",
+ "#", # git config --get core.commentchar
+ "file1.txt\npåth/to/file2.txt\n",
+ "foöbar\n* hürdur\n"
]
- context = GitContext.from_local_repository(u"fåke/path")
+ context = GitContext.from_local_repository("fåke/path")
# assert that commit info was read using git command
expected_calls = [
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
@@ -163,13 +157,13 @@ class GitCommitTests(BaseTestCase):
last_commit = context.commits[-1]
self.assertIsInstance(last_commit, LocalGitCommit)
self.assertEqual(last_commit.sha, sample_sha)
- self.assertEqual(last_commit.message.title, u"Merge \"foo bår commit\"")
+ self.assertEqual(last_commit.message.title, "Merge \"foo bår commit\"")
self.assertEqual(last_commit.message.body, [])
- self.assertEqual(last_commit.author_name, u"test åuthor")
- self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
+ self.assertEqual(last_commit.author_name, "test åuthor")
+ self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
- self.assertListEqual(last_commit.parents, [u"åbc", "def"])
+ self.assertListEqual(last_commit.parents, ["åbc", "def"])
self.assertTrue(last_commit.is_merge_commit)
self.assertFalse(last_commit.is_fixup_commit)
self.assertFalse(last_commit.is_squash_commit)
@@ -178,11 +172,11 @@ class GitCommitTests(BaseTestCase):
# First 2 'git log' calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:3])
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
# 'git diff-tree' should have happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
- self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
+ self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
# All expected calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls)
@@ -194,14 +188,14 @@ class GitCommitTests(BaseTestCase):
sh.git.side_effect = [
sample_sha,
- u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
- u"{0}! \"foo bår commit\"".format(commit_type),
- u"#", # git config --get core.commentchar
- u"file1.txt\npåth/to/file2.txt\n",
- u"foöbar\n* hürdur\n"
+ "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ f"{commit_type}! \"foo bår commit\"",
+ "#", # git config --get core.commentchar
+ "file1.txt\npåth/to/file2.txt\n",
+ "foöbar\n* hürdur\n"
]
- context = GitContext.from_local_repository(u"fåke/path")
+ context = GitContext.from_local_repository("fåke/path")
# assert that commit info was read using git command
expected_calls = [
call("log", "-1", "--pretty=%H", **self.expected_sh_special_args),
@@ -218,13 +212,13 @@ class GitCommitTests(BaseTestCase):
last_commit = context.commits[-1]
self.assertIsInstance(last_commit, LocalGitCommit)
self.assertEqual(last_commit.sha, sample_sha)
- self.assertEqual(last_commit.message.title, u"{0}! \"foo bår commit\"".format(commit_type))
+ self.assertEqual(last_commit.message.title, f"{commit_type}! \"foo bår commit\"")
self.assertEqual(last_commit.message.body, [])
- self.assertEqual(last_commit.author_name, u"test åuthor")
- self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
+ self.assertEqual(last_commit.author_name, "test åuthor")
+ self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15,
tzinfo=dateutil.tz.tzoffset("+0100", 3600)))
- self.assertListEqual(last_commit.parents, [u"åbc"])
+ self.assertListEqual(last_commit.parents, ["åbc"])
# First 2 'git log' calls should've happened at this point
self.assertEqual(sh.git.mock_calls, expected_calls[:3])
@@ -236,13 +230,13 @@ class GitCommitTests(BaseTestCase):
self.assertFalse(last_commit.is_merge_commit)
self.assertFalse(last_commit.is_revert_commit)
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
# 'git diff-tree' should have happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls[:4])
- self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"])
+ self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"])
# All expected calls should've happened at this point
self.assertListEqual(sh.git.mock_calls, expected_calls)
@@ -250,27 +244,27 @@ class GitCommitTests(BaseTestCase):
@patch("gitlint.git.git_commentchar")
def test_from_commit_msg_full(self, commentchar):
- commentchar.return_value = u"#"
+ commentchar.return_value = "#"
gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1"))
- expected_title = u"Commit title contåining 'WIP', as well as trailing punctuation."
+ expected_title = "Commit title contåining 'WIP', as well as trailing punctuation."
expected_body = ["This line should be empty",
"This is the first line of the commit message body and it is meant to test a " +
"line that exceeds the maximum line length of 80 characters.",
- u"This line has a tråiling space. ",
+ "This line has a tråiling space. ",
"This line has a trailing tab.\t"]
expected_full = expected_title + "\n" + "\n".join(expected_body)
expected_original = expected_full + (
- u"\n# This is a cömmented line\n"
- u"# ------------------------ >8 ------------------------\n"
- u"# Anything after this line should be cleaned up\n"
- u"# this line appears on `git commit -v` command\n"
- u"diff --git a/gitlint/tests/samples/commit_message/sample1 "
- u"b/gitlint/tests/samples/commit_message/sample1\n"
- u"index 82dbe7f..ae71a14 100644\n"
- u"--- a/gitlint/tests/samples/commit_message/sample1\n"
- u"+++ b/gitlint/tests/samples/commit_message/sample1\n"
- u"@@ -1 +1 @@\n"
+ "\n# This is a cömmented line\n"
+ "# ------------------------ >8 ------------------------\n"
+ "# Anything after this line should be cleaned up\n"
+ "# this line appears on `git commit -v` command\n"
+ "diff --git a/gitlint/tests/samples/commit_message/sample1 "
+ "b/gitlint/tests/samples/commit_message/sample1\n"
+ "index 82dbe7f..ae71a14 100644\n"
+ "--- a/gitlint/tests/samples/commit_message/sample1\n"
+ "+++ b/gitlint/tests/samples/commit_message/sample1\n"
+ "@@ -1 +1 @@\n"
)
commit = gitcontext.commits[-1]
@@ -297,10 +291,10 @@ class GitCommitTests(BaseTestCase):
self.assertIsInstance(commit, GitCommit)
self.assertFalse(isinstance(commit, LocalGitCommit))
- self.assertEqual(commit.message.title, u"Just a title contåining WIP")
+ self.assertEqual(commit.message.title, "Just a title contåining WIP")
self.assertEqual(commit.message.body, [])
- self.assertEqual(commit.message.full, u"Just a title contåining WIP")
- self.assertEqual(commit.message.original, u"Just a title contåining WIP")
+ self.assertEqual(commit.message.full, "Just a title contåining WIP")
+ self.assertEqual(commit.message.original, "Just a title contåining WIP")
self.assertEqual(commit.author_name, None)
self.assertEqual(commit.author_email, None)
self.assertListEqual(commit.parents, [])
@@ -334,16 +328,16 @@ class GitCommitTests(BaseTestCase):
@patch("gitlint.git.git_commentchar")
def test_from_commit_msg_comment(self, commentchar):
- commentchar.return_value = u"#"
- gitcontext = GitContext.from_commit_msg(u"Tïtle\n\nBödy 1\n#Cömment\nBody 2")
+ commentchar.return_value = "#"
+ gitcontext = GitContext.from_commit_msg("Tïtle\n\nBödy 1\n#Cömment\nBody 2")
commit = gitcontext.commits[-1]
self.assertIsInstance(commit, GitCommit)
self.assertFalse(isinstance(commit, LocalGitCommit))
- self.assertEqual(commit.message.title, u"Tïtle")
- self.assertEqual(commit.message.body, ["", u"Bödy 1", "Body 2"])
- self.assertEqual(commit.message.full, u"Tïtle\n\nBödy 1\nBody 2")
- self.assertEqual(commit.message.original, u"Tïtle\n\nBödy 1\n#Cömment\nBody 2")
+ self.assertEqual(commit.message.title, "Tïtle")
+ self.assertEqual(commit.message.body, ["", "Bödy 1", "Body 2"])
+ self.assertEqual(commit.message.full, "Tïtle\n\nBödy 1\nBody 2")
+ self.assertEqual(commit.message.original, "Tïtle\n\nBödy 1\n#Cömment\nBody 2")
self.assertEqual(commit.author_name, None)
self.assertEqual(commit.author_email, None)
self.assertEqual(commit.date, None)
@@ -402,7 +396,7 @@ class GitCommitTests(BaseTestCase):
def test_from_commit_msg_fixup_squash_commit(self):
commit_types = ["fixup", "squash"]
for commit_type in commit_types:
- commit_msg = "{0}! Test message".format(commit_type)
+ commit_msg = f"{commit_type}! Test message"
gitcontext = GitContext.from_commit_msg(commit_msg)
commit = gitcontext.commits[-1]
@@ -431,16 +425,16 @@ class GitCommitTests(BaseTestCase):
# StagedLocalGitCommit()
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
- u"test åuthor\n", # git config --get user.name
- u"test-emåil@foo.com\n", # git config --get user.email
- u"my-brånch\n", # git rev-parse --abbrev-ref HEAD
- u"file1.txt\npåth/to/file2.txt\n",
+ "#", # git config --get core.commentchar
+ "test åuthor\n", # git config --get user.name
+ "test-emåil@foo.com\n", # git config --get user.email
+ "my-brånch\n", # git rev-parse --abbrev-ref HEAD
+ "file1.txt\npåth/to/file2.txt\n",
]
now.side_effect = [arrow.get("2020-02-19T12:18:46.675182+01:00")]
# We use a fixup commit, just to test a non-default path
- context = GitContext.from_staged_commit(u"fixup! Foōbar 123\n\ncömmit-body\n", u"fåke/path")
+ context = GitContext.from_staged_commit("fixup! Foōbar 123\n\ncömmit-body\n", "fåke/path")
# git calls we're expexting
expected_calls = [
@@ -454,15 +448,15 @@ class GitCommitTests(BaseTestCase):
last_commit = context.commits[-1]
self.assertIsInstance(last_commit, StagedLocalGitCommit)
self.assertIsNone(last_commit.sha, None)
- self.assertEqual(last_commit.message.title, u"fixup! Foōbar 123")
- self.assertEqual(last_commit.message.body, ["", u"cömmit-body"])
+ self.assertEqual(last_commit.message.title, "fixup! Foōbar 123")
+ self.assertEqual(last_commit.message.body, ["", "cömmit-body"])
# Only `git config --get core.commentchar` should've happened up until this point
self.assertListEqual(sh.git.mock_calls, expected_calls[0:1])
- self.assertEqual(last_commit.author_name, u"test åuthor")
+ self.assertEqual(last_commit.author_name, "test åuthor")
self.assertListEqual(sh.git.mock_calls, expected_calls[0:2])
- self.assertEqual(last_commit.author_email, u"test-emåil@foo.com")
+ self.assertEqual(last_commit.author_email, "test-emåil@foo.com")
self.assertListEqual(sh.git.mock_calls, expected_calls[0:3])
self.assertEqual(last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46,
@@ -475,10 +469,10 @@ class GitCommitTests(BaseTestCase):
self.assertFalse(last_commit.is_squash_commit)
self.assertFalse(last_commit.is_revert_commit)
- self.assertListEqual(last_commit.branches, [u"my-brånch"])
+ self.assertListEqual(last_commit.branches, ["my-brånch"])
self.assertListEqual(sh.git.mock_calls, expected_calls[0:4])
- self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
+ self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"])
self.assertListEqual(sh.git.mock_calls, expected_calls[0:5])
@patch('gitlint.git.sh')
@@ -486,32 +480,32 @@ class GitCommitTests(BaseTestCase):
# StagedLocalGitCommit()
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
+ "#", # git config --get core.commentchar
ErrorReturnCode('git config --get user.name', b"", b""),
]
expected_msg = "Missing git configuration: please set user.name"
with self.assertRaisesMessage(GitContextError, expected_msg):
- ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
- [ustr(commit) for commit in ctx.commits]
+ ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path")
+ [str(commit) for commit in ctx.commits]
@patch('gitlint.git.sh')
def test_staged_commit_with_missing_email(self, sh):
# StagedLocalGitCommit()
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
- u"test åuthor\n", # git config --get user.name
+ "#", # git config --get core.commentchar
+ "test åuthor\n", # git config --get user.name
ErrorReturnCode('git config --get user.name', b"", b""),
]
expected_msg = "Missing git configuration: please set user.email"
with self.assertRaisesMessage(GitContextError, expected_msg):
- ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
- [ustr(commit) for commit in ctx.commits]
+ ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path")
+ [str(commit) for commit in ctx.commits]
def test_gitcommitmessage_equality(self):
- commit_message1 = GitCommitMessage(GitContext(), u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
+ commit_message1 = GitCommitMessage(GitContext(), "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
attrs = ['original', 'full', 'title', 'body']
self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context})
@@ -519,20 +513,20 @@ class GitCommitTests(BaseTestCase):
def test_gitcommit_equality(self, git):
# git will be called to setup the context (commentchar and current_branch), just return the same value
# This only matters to test gitcontext equality, not gitcommit equality
- git.return_value = u"foöbar"
+ git.return_value = "foöbar"
# Test simple equality case
now = datetime.datetime.utcnow()
context1 = GitContext()
- commit_message1 = GitCommitMessage(context1, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
- commit1 = GitCommit(context1, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None,
- [u"föo/bar"], [u"brånch1", u"brånch2"])
+ commit_message1 = GitCommitMessage(context1, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
+ commit1 = GitCommit(context1, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None,
+ ["föo/bar"], ["brånch1", "brånch2"])
context1.commits = [commit1]
context2 = GitContext()
- commit_message2 = GitCommitMessage(context2, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
- commit2 = GitCommit(context2, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None,
- [u"föo/bar"], [u"brånch1", u"brånch2"])
+ commit_message2 = GitCommitMessage(context2, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"])
+ commit2 = GitCommit(context2, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None,
+ ["föo/bar"], ["brånch1", "brånch2"])
context2.commits = [commit2]
self.assertEqual(context1, context2)
@@ -547,8 +541,8 @@ class GitCommitTests(BaseTestCase):
self.object_equality_test(commit1, kwargs.keys(), {"context": commit1.context})
# Check that the is_* attributes that are affected by the commit message affect equality
- special_messages = {'is_merge_commit': u"Merge: foöbar", 'is_fixup_commit': u"fixup! foöbar",
- 'is_squash_commit': u"squash! foöbar", 'is_revert_commit': u"Revert: foöbar"}
+ special_messages = {'is_merge_commit': "Merge: foöbar", 'is_fixup_commit': "fixup! foöbar",
+ 'is_squash_commit': "squash! foöbar", 'is_revert_commit': "Revert: foöbar"}
for key in special_messages:
kwargs_copy = copy.deepcopy(kwargs)
clone1 = GitCommit(context=commit1.context, **kwargs_copy)
@@ -556,16 +550,16 @@ class GitCommitTests(BaseTestCase):
self.assertTrue(getattr(clone1, key))
clone2 = GitCommit(context=commit1.context, **kwargs_copy)
- clone2.message = GitCommitMessage.from_full_message(context1, u"foöbar")
+ clone2.message = GitCommitMessage.from_full_message(context1, "foöbar")
self.assertNotEqual(clone1, clone2)
@patch("gitlint.git.git_commentchar")
def test_commit_msg_custom_commentchar(self, patched):
- patched.return_value = u"ä"
+ patched.return_value = "ä"
context = GitContext()
- message = GitCommitMessage.from_full_message(context, u"Tïtle\n\nBödy 1\näCömment\nBody 2")
+ message = GitCommitMessage.from_full_message(context, "Tïtle\n\nBödy 1\näCömment\nBody 2")
- self.assertEqual(message.title, u"Tïtle")
- self.assertEqual(message.body, ["", u"Bödy 1", "Body 2"])
- self.assertEqual(message.full, u"Tïtle\n\nBödy 1\nBody 2")
- self.assertEqual(message.original, u"Tïtle\n\nBödy 1\näCömment\nBody 2")
+ self.assertEqual(message.title, "Tïtle")
+ self.assertEqual(message.body, ["", "Bödy 1", "Body 2"])
+ self.assertEqual(message.full, "Tïtle\n\nBödy 1\nBody 2")
+ self.assertEqual(message.original, "Tïtle\n\nBödy 1\näCömment\nBody 2")
diff --git a/gitlint/tests/git/test_git_context.py b/gitlint/tests/git/test_git_context.py
index b243d5e..bb05236 100644
--- a/gitlint/tests/git/test_git_context.py
+++ b/gitlint/tests/git/test_git_context.py
@@ -1,11 +1,6 @@
# -*- coding: utf-8 -*-
-try:
- # python 2.x
- from mock import patch, call
-except ImportError:
- # python 3.x
- from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch, call
from gitlint.tests.base import BaseTestCase
from gitlint.git import GitContext
@@ -16,15 +11,15 @@ class GitContextTests(BaseTestCase):
# Expected special_args passed to 'sh'
expected_sh_special_args = {
'_tty_out': False,
- '_cwd': u"fåke/path"
+ '_cwd': "fåke/path"
}
@patch('gitlint.git.sh')
def test_gitcontext(self, sh):
sh.git.side_effect = [
- u"#", # git config --get core.commentchar
- u"\nfoöbar\n"
+ "#", # git config --get core.commentchar
+ "\nfoöbar\n"
]
expected_calls = [
@@ -32,58 +27,58 @@ class GitContextTests(BaseTestCase):
call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args)
]
- context = GitContext(u"fåke/path")
+ context = GitContext("fåke/path")
self.assertEqual(sh.git.mock_calls, [])
# gitcontext.comment_branch
- self.assertEqual(context.commentchar, u"#")
+ self.assertEqual(context.commentchar, "#")
self.assertEqual(sh.git.mock_calls, expected_calls[0:1])
# gitcontext.current_branch
- self.assertEqual(context.current_branch, u"foöbar")
+ self.assertEqual(context.current_branch, "foöbar")
self.assertEqual(sh.git.mock_calls, expected_calls)
@patch('gitlint.git.sh')
def test_gitcontext_equality(self, sh):
sh.git.side_effect = [
- u"û\n", # context1: git config --get core.commentchar
- u"û\n", # context2: git config --get core.commentchar
- u"my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD
- u"my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD
+ "û\n", # context1: git config --get core.commentchar
+ "û\n", # context2: git config --get core.commentchar
+ "my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD
+ "my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD
]
- context1 = GitContext(u"fåke/path")
- context1.commits = [u"fōo", u"bår"] # we don't need real commits to check for equality
+ context1 = GitContext("fåke/path")
+ context1.commits = ["fōo", "bår"] # we don't need real commits to check for equality
- context2 = GitContext(u"fåke/path")
- context2.commits = [u"fōo", u"bår"]
+ context2 = GitContext("fåke/path")
+ context2.commits = ["fōo", "bår"]
self.assertEqual(context1, context2)
# INEQUALITY
# Different commits
- context2.commits = [u"hür", u"dür"]
+ context2.commits = ["hür", "dür"]
self.assertNotEqual(context1, context2)
# Different repository_path
context2.commits = context1.commits
- context2.repository_path = u"ōther/path"
+ context2.repository_path = "ōther/path"
self.assertNotEqual(context1, context2)
# Different comment_char
- context3 = GitContext(u"fåke/path")
- context3.commits = [u"fōo", u"bår"]
+ context3 = GitContext("fåke/path")
+ context3.commits = ["fōo", "bår"]
sh.git.side_effect = ([
- u"ç\n", # context3: git config --get core.commentchar
- u"my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD
+ "ç\n", # context3: git config --get core.commentchar
+ "my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD
])
self.assertNotEqual(context1, context3)
# Different current_branch
- context4 = GitContext(u"fåke/path")
- context4.commits = [u"fōo", u"bår"]
+ context4 = GitContext("fåke/path")
+ context4.commits = ["fōo", "bår"]
sh.git.side_effect = ([
- u"û\n", # context4: git config --get core.commentchar
- u"different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD
+ "û\n", # context4: git config --get core.commentchar
+ "different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD
])
self.assertNotEqual(context1, context4)
diff --git a/gitlint/tests/rules/test_body_rules.py b/gitlint/tests/rules/test_body_rules.py
index f46760b..96ae998 100644
--- a/gitlint/tests/rules/test_body_rules.py
+++ b/gitlint/tests/rules/test_body_rules.py
@@ -8,65 +8,65 @@ class BodyRuleTests(BaseTestCase):
rule = rules.BodyMaxLineLength()
# assert no error
- violation = rule.validate(u"å" * 80, None)
+ violation = rule.validate("å" * 80, None)
self.assertIsNone(violation)
# assert error on line length > 80
- expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", u"å" * 81)
- violations = rule.validate(u"å" * 81, None)
+ expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", "å" * 81)
+ violations = rule.validate("å" * 81, None)
self.assertListEqual(violations, [expected_violation])
# set line length to 120, and check no violation on length 73
rule = rules.BodyMaxLineLength({'line-length': 120})
- violations = rule.validate(u"å" * 73, None)
+ violations = rule.validate("å" * 73, None)
self.assertIsNone(violations)
# assert raise on 121
- expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", u"å" * 121)
- violations = rule.validate(u"å" * 121, None)
+ expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", "å" * 121)
+ violations = rule.validate("å" * 121, None)
self.assertListEqual(violations, [expected_violation])
def test_trailing_whitespace(self):
rule = rules.BodyTrailingWhitespace()
# assert no error
- violations = rule.validate(u"å", None)
+ violations = rule.validate("å", None)
self.assertIsNone(violations)
# trailing space
- expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å ")
- violations = rule.validate(u"å ", None)
+ expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å ")
+ violations = rule.validate("å ", None)
self.assertListEqual(violations, [expected_violation])
# trailing tab
- expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å\t")
- violations = rule.validate(u"å\t", None)
+ expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å\t")
+ violations = rule.validate("å\t", None)
self.assertListEqual(violations, [expected_violation])
def test_hard_tabs(self):
rule = rules.BodyHardTab()
# assert no error
- violations = rule.validate(u"This is ã test", None)
+ violations = rule.validate("This is ã test", None)
self.assertIsNone(violations)
# contains hard tab
- expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This is å\ttest")
- violations = rule.validate(u"This is å\ttest", None)
+ expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", "This is å\ttest")
+ violations = rule.validate("This is å\ttest", None)
self.assertListEqual(violations, [expected_violation])
def test_body_first_line_empty(self):
rule = rules.BodyFirstLineEmpty()
# assert no error
- commit = self.gitcommit(u"Tïtle\n\nThis is the secōnd body line")
+ commit = self.gitcommit("Tïtle\n\nThis is the secōnd body line")
violations = rule.validate(commit)
self.assertIsNone(violations)
# second line not empty
- expected_violation = rules.RuleViolation("B4", "Second line is not empty", u"nöt empty", 2)
+ expected_violation = rules.RuleViolation("B4", "Second line is not empty", "nöt empty", 2)
- commit = self.gitcommit(u"Tïtle\nnöt empty\nThis is the secönd body line")
+ commit = self.gitcommit("Tïtle\nnöt empty\nThis is the secönd body line")
violations = rule.validate(commit)
self.assertListEqual(violations, [expected_violation])
@@ -80,34 +80,34 @@ class BodyRuleTests(BaseTestCase):
self.assertIsNone(violations)
# assert no error - no body
- commit = self.gitcommit(u"Tïtle\n")
+ commit = self.gitcommit("Tïtle\n")
violations = rule.validate(commit)
self.assertIsNone(violations)
# body is too short
- expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", u"töoshort", 3)
+ expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", "töoshort", 3)
- commit = self.gitcommit(u"Tïtle\n\ntöoshort\n")
+ commit = self.gitcommit("Tïtle\n\ntöoshort\n")
violations = rule.validate(commit)
self.assertListEqual(violations, [expected_violation])
# assert error - short across multiple lines
- expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", u"secöndthïrd", 3)
- commit = self.gitcommit(u"Tïtle\n\nsecönd\nthïrd\n")
+ expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", "secöndthïrd", 3)
+ commit = self.gitcommit("Tïtle\n\nsecönd\nthïrd\n")
violations = rule.validate(commit)
self.assertListEqual(violations, [expected_violation])
# set line length to 120, and check violation on length 21
- expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", u"å" * 21, 3)
+ expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", "å" * 21, 3)
rule = rules.BodyMinLength({'min-length': 120})
- commit = self.gitcommit(u"Title\n\n%s\n" % (u"å" * 21))
+ commit = self.gitcommit("Title\n\n%s\n" % ("å" * 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(u"Tïtle\n\n%s\n" % (u"å" * 8))
+ commit = self.gitcommit("Tïtle\n\n%s\n" % ("å" * 8))
violations = rule.validate(commit)
self.assertIsNone(violations)
@@ -115,14 +115,14 @@ class BodyRuleTests(BaseTestCase):
rule = rules.BodyMissing()
# assert no error - body is present
- commit = self.gitcommit(u"Tïtle\n\nThis ïs the first body line\n")
+ commit = self.gitcommit("Tïtle\n\nThis ïs the first body line\n")
violations = rule.validate(commit)
self.assertIsNone(violations)
# body is too short
expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3)
- commit = self.gitcommit(u"Tïtle\n")
+ commit = self.gitcommit("Tïtle\n")
violations = rule.validate(commit)
self.assertListEqual(violations, [expected_violation])
@@ -130,7 +130,7 @@ class BodyRuleTests(BaseTestCase):
rule = rules.BodyMissing()
# assert no error - merge commit
- commit = self.gitcommit(u"Merge: Tïtle\n")
+ commit = self.gitcommit("Merge: Tïtle\n")
violations = rule.validate(commit)
self.assertIsNone(violations)
@@ -144,37 +144,37 @@ class BodyRuleTests(BaseTestCase):
rule = rules.BodyChangedFileMention()
# assert no error when no files have changed and no files need to be mentioned
- commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py")
+ commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py")
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert no error when no files have changed but certain files need to be mentioned on change
- rule = rules.BodyChangedFileMention({'files': u"bar.txt,föo/test.py"})
- commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py")
+ rule = rules.BodyChangedFileMention({'files': "bar.txt,föo/test.py"})
+ commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py")
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert no error if a file has changed and is mentioned
- commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py", [u"föo/test.py"])
+ commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py", ["föo/test.py"])
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert no error if multiple files have changed and are mentioned
- commit_msg = u"This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt"
- commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
+ commit_msg = "This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt"
+ commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert error if file has changed and is not mentioned
- commit_msg = u"This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt"
- commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
+ commit_msg = "This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt"
+ commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
violations = rule.validate(commit)
- expected_violation = rules.RuleViolation("B7", u"Body does not mention changed file 'föo/test.py'", None, 4)
+ expected_violation = rules.RuleViolation("B7", "Body does not mention changed file 'föo/test.py'", None, 4)
self.assertEqual([expected_violation], violations)
# assert multiple errors if multiple files habe changed and are not mentioned
- commit_msg = u"This is å test\n\nHere is a mention of\nAnd here is a mention of"
- commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"])
+ commit_msg = "This is å test\n\nHere is a mention of\nAnd here is a mention of"
+ commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"])
violations = rule.validate(commit)
expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4)
self.assertEqual([expected_violation_2, expected_violation], violations)
@@ -182,7 +182,7 @@ class BodyRuleTests(BaseTestCase):
def test_body_match_regex(self):
# We intentionally add 2 newlines at the end of our commit message as that's how git will pass the
# message. This way we also test that the rule strips off the last line.
- commit = self.gitcommit(u"US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n")
+ commit = self.gitcommit("US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n")
# assert no violation on default regex (=everything allowed)
rule = rules.BodyRegexMatches()
@@ -191,25 +191,25 @@ class BodyRuleTests(BaseTestCase):
# assert no violation on matching regex
# (also note that first body line - in between title and rest of body - is ignored)
- rule = rules.BodyRegexMatches({'regex': u"^Bödy(.*)"})
+ rule = rules.BodyRegexMatches({'regex': "^Bödy(.*)"})
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert we can do end matching (and last empty line is ignored)
# (also note that first body line - in between title and rest of body - is ignored)
- rule = rules.BodyRegexMatches({'regex': u"My-Commit-Tag: föo$"})
+ rule = rules.BodyRegexMatches({'regex': "My-Commit-Tag: föo$"})
violations = rule.validate(commit)
self.assertIsNone(violations)
# common use-case: matching that a given line is present
- rule = rules.BodyRegexMatches({'regex': u"(.*)Föo(.*)"})
+ rule = rules.BodyRegexMatches({'regex': "(.*)Föo(.*)"})
violations = rule.validate(commit)
self.assertIsNone(violations)
# assert violation on non-matching body
- rule = rules.BodyRegexMatches({'regex': u"^Tëst(.*)Foo"})
+ rule = rules.BodyRegexMatches({'regex': "^Tëst(.*)Foo"})
violations = rule.validate(commit)
- expected_violation = rules.RuleViolation("B8", u"Body does not match regex (^Tëst(.*)Foo)", None, 6)
+ expected_violation = rules.RuleViolation("B8", "Body does not match regex (^Tëst(.*)Foo)", None, 6)
self.assertListEqual(violations, [expected_violation])
# assert no violation on None regex
@@ -218,7 +218,7 @@ class BodyRuleTests(BaseTestCase):
self.assertIsNone(violations)
# Assert no issues when there's no body or a weird body variation
- bodies = [u"åbc", u"åbc\n", u"åbc\nföo\n", u"åbc\n\n", u"åbc\nföo\nblå", u"åbc\nföo\nblå\n"]
+ bodies = ["åbc", "åbc\n", "åbc\nföo\n", "åbc\n\n", "åbc\nföo\nblå", "åbc\nföo\nblå\n"]
for body in bodies:
commit = self.gitcommit(body)
rule = rules.BodyRegexMatches({'regex': ".*"})
diff --git a/gitlint/tests/rules/test_configuration_rules.py b/gitlint/tests/rules/test_configuration_rules.py
index 121cb3a..479d9c2 100644
--- a/gitlint/tests/rules/test_configuration_rules.py
+++ b/gitlint/tests/rules/test_configuration_rules.py
@@ -6,7 +6,7 @@ from gitlint.config import LintConfig
class ConfigurationRuleTests(BaseTestCase):
def test_ignore_by_title(self):
- commit = self.gitcommit(u"Releäse\n\nThis is the secōnd body line")
+ commit = self.gitcommit("Releäse\n\nThis is the secōnd body line")
# No regex specified -> Config shouldn't be changed
rule = rules.IgnoreByTitle()
@@ -16,29 +16,29 @@ class ConfigurationRuleTests(BaseTestCase):
self.assert_logged([]) # nothing logged -> nothing ignored
# Matching regex -> expect config to ignore all rules
- rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)"})
+ rule = rules.IgnoreByTitle({"regex": "^Releäse(.*)"})
expected_config = LintConfig()
expected_config.ignore = "all"
rule.apply(config, commit)
self.assertEqual(config, expected_config)
- expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
- u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all"
+ expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
+ "Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all"
self.assert_log_contains(expected_log_message)
# Matching regex with specific ignore
- rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)",
+ rule = rules.IgnoreByTitle({"regex": "^Releäse(.*)",
"ignore": "T1,B2"})
expected_config = LintConfig()
expected_config.ignore = "T1,B2"
rule.apply(config, commit)
self.assertEqual(config, expected_config)
- expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
- u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2"
+ expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
+ "Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2"
def test_ignore_by_body(self):
- commit = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
+ commit = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
# No regex specified -> Config shouldn't be changed
rule = rules.IgnoreByBody()
@@ -48,32 +48,32 @@ class ConfigurationRuleTests(BaseTestCase):
self.assert_logged([]) # nothing logged -> nothing ignored
# Matching regex -> expect config to ignore all rules
- rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)"})
+ rule = rules.IgnoreByBody({"regex": "(.*)relëase(.*)"})
expected_config = LintConfig()
expected_config.ignore = "all"
rule.apply(config, commit)
self.assertEqual(config, expected_config)
- expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
- u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \
- u" ignoring rules: all"
+ expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
+ "Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \
+ " ignoring rules: all"
self.assert_log_contains(expected_log_message)
# Matching regex with specific ignore
- rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)",
+ rule = rules.IgnoreByBody({"regex": "(.*)relëase(.*)",
"ignore": "T1,B2"})
expected_config = LintConfig()
expected_config.ignore = "T1,B2"
rule.apply(config, commit)
self.assertEqual(config, expected_config)
- expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
- u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2"
+ expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
+ "Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2"
self.assert_log_contains(expected_log_message)
def test_ignore_body_lines(self):
- commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
- commit2 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
+ commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
+ commit2 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
# no regex specified, nothing should have happened:
# commit and config should remain identical, log should be empty
@@ -85,22 +85,22 @@ class ConfigurationRuleTests(BaseTestCase):
self.assert_logged([])
# Matching regex
- rule = rules.IgnoreBodyLines({"regex": u"(.*)relëase(.*)"})
+ rule = rules.IgnoreBodyLines({"regex": "(.*)relëase(.*)"})
config = LintConfig()
rule.apply(config, commit1)
# Our modified commit should be identical to a commit that doesn't contain the specific line
- expected_commit = self.gitcommit(u"Tïtle\n\nThis is\n line")
+ expected_commit = self.gitcommit("Tïtle\n\nThis is\n line")
# The original message isn't touched by this rule, this way we always have a way to reference back to it,
# so assert it's not modified by setting it to the same as commit1
expected_commit.message.original = commit1.message.original
self.assertEqual(commit1, expected_commit)
self.assertEqual(config, LintConfig()) # config shouldn't have been modified
- self.assert_log_contains(u"DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " +
- u"matches '(.*)relëase(.*)'")
+ self.assert_log_contains("DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " +
+ "matches '(.*)relëase(.*)'")
# Non-Matching regex: no changes expected
- commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
- rule = rules.IgnoreBodyLines({"regex": u"(.*)föobar(.*)"})
+ commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line")
+ rule = rules.IgnoreBodyLines({"regex": "(.*)föobar(.*)"})
config = LintConfig()
rule.apply(config, commit1)
self.assertEqual(commit1, commit2)
diff --git a/gitlint/tests/rules/test_meta_rules.py b/gitlint/tests/rules/test_meta_rules.py
index 987aa88..568ca3f 100644
--- a/gitlint/tests/rules/test_meta_rules.py
+++ b/gitlint/tests/rules/test_meta_rules.py
@@ -8,25 +8,25 @@ class MetaRuleTests(BaseTestCase):
rule = AuthorValidEmail()
# valid email addresses
- valid_email_addresses = [u"föo@bar.com", u"Jöhn.Doe@bar.com", u"jöhn+doe@bar.com", u"jöhn/doe@bar.com",
- u"jöhn.doe@subdomain.bar.com"]
+ valid_email_addresses = ["föo@bar.com", "Jöhn.Doe@bar.com", "jöhn+doe@bar.com", "jöhn/doe@bar.com",
+ "jöhn.doe@subdomain.bar.com"]
for email in valid_email_addresses:
- commit = self.gitcommit(u"", author_email=email)
+ commit = self.gitcommit("", author_email=email)
violations = rule.validate(commit)
self.assertIsNone(violations)
# No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an
# email address)
- commit = self.gitcommit(u"")
+ commit = self.gitcommit("")
violations = rule.validate(commit)
self.assertIsNone(violations)
# Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint)
- invalid_email_addresses = [u"föo@bar", u"JöhnDoe", u"Jöhn Doe", u"Jöhn Doe@foo.com", u" JöhnDoe@foo.com",
- u"JöhnDoe@ foo.com", u"JöhnDoe@foo. com", u"JöhnDoe@foo. com", u"@bår.com",
- u"föo@.com"]
+ invalid_email_addresses = ["föo@bar", "JöhnDoe", "Jöhn Doe", "Jöhn Doe@foo.com", " JöhnDoe@foo.com",
+ "JöhnDoe@ foo.com", "JöhnDoe@foo. com", "JöhnDoe@foo. com", "@bår.com",
+ "föo@.com"]
for email in invalid_email_addresses:
- commit = self.gitcommit(u"", author_email=email)
+ commit = self.gitcommit("", author_email=email)
violations = rule.validate(commit)
self.assertListEqual(violations,
[RuleViolation("M1", "Author email for commit is invalid", email)])
@@ -35,25 +35,25 @@ class MetaRuleTests(BaseTestCase):
# regex=None -> the rule isn't applied
rule = AuthorValidEmail()
rule.options['regex'].set(None)
- emailadresses = [u"föo", None, u"hür dür"]
+ emailadresses = ["föo", None, "hür dür"]
for email in emailadresses:
- commit = self.gitcommit(u"", author_email=email)
+ commit = self.gitcommit("", author_email=email)
violations = rule.validate(commit)
self.assertIsNone(violations)
# Custom domain
- rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"})
+ rule = AuthorValidEmail({'regex': "[^@]+@bår.com"})
valid_email_addresses = [
- u"föo@bår.com", u"Jöhn.Doe@bår.com", u"jöhn+doe@bår.com", u"jöhn/doe@bår.com"]
+ "föo@bår.com", "Jöhn.Doe@bår.com", "jöhn+doe@bår.com", "jöhn/doe@bår.com"]
for email in valid_email_addresses:
- commit = self.gitcommit(u"", author_email=email)
+ commit = self.gitcommit("", author_email=email)
violations = rule.validate(commit)
self.assertIsNone(violations)
# Invalid email addresses
- invalid_email_addresses = [u"föo@hur.com"]
+ invalid_email_addresses = ["föo@hur.com"]
for email in invalid_email_addresses:
- commit = self.gitcommit(u"", author_email=email)
+ commit = self.gitcommit("", author_email=email)
violations = rule.validate(commit)
self.assertListEqual(violations,
[RuleViolation("M1", "Author email for commit is invalid", email)])
diff --git a/gitlint/tests/rules/test_rules.py b/gitlint/tests/rules/test_rules.py
index 58ee1c3..6fcf9bc 100644
--- a/gitlint/tests/rules/test_rules.py
+++ b/gitlint/tests/rules/test_rules.py
@@ -10,14 +10,14 @@ class RuleTests(BaseTestCase):
# Ensure rules are not equal if they differ on their attributes
for attr in ["id", "name", "target", "options"]:
rule = Rule()
- setattr(rule, attr, u"åbc")
+ setattr(rule, attr, "åbc")
self.assertNotEqual(Rule(), rule)
def test_rule_log(self):
rule = Rule()
- rule.log.debug(u"Tēst message")
- self.assert_log_contains(u"DEBUG: gitlint.rules Tēst message")
+ rule.log.debug("Tēst message")
+ self.assert_log_contains("DEBUG: gitlint.rules Tēst message")
def test_rule_violation_equality(self):
- violation1 = RuleViolation(u"ïd1", u"My messåge", u"My cöntent", 1)
+ violation1 = RuleViolation("ïd1", "My messåge", "My cöntent", 1)
self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"])
diff --git a/gitlint/tests/rules/test_title_rules.py b/gitlint/tests/rules/test_title_rules.py
index 049735e..e1be857 100644
--- a/gitlint/tests/rules/test_title_rules.py
+++ b/gitlint/tests/rules/test_title_rules.py
@@ -9,66 +9,66 @@ class TitleRuleTests(BaseTestCase):
rule = TitleMaxLength()
# assert no error
- violation = rule.validate(u"å" * 72, None)
+ violation = rule.validate("å" * 72, None)
self.assertIsNone(violation)
# assert error on line length > 72
- expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", u"å" * 73)
- violations = rule.validate(u"å" * 73, None)
+ expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", "å" * 73)
+ violations = rule.validate("å" * 73, None)
self.assertListEqual(violations, [expected_violation])
# set line length to 120, and check no violation on length 73
rule = TitleMaxLength({'line-length': 120})
- violations = rule.validate(u"å" * 73, None)
+ violations = rule.validate("å" * 73, None)
self.assertIsNone(violations)
# assert raise on 121
- expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", u"å" * 121)
- violations = rule.validate(u"å" * 121, None)
+ expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", "å" * 121)
+ violations = rule.validate("å" * 121, None)
self.assertListEqual(violations, [expected_violation])
def test_trailing_whitespace(self):
rule = TitleTrailingWhitespace()
# assert no error
- violations = rule.validate(u"å", None)
+ violations = rule.validate("å", None)
self.assertIsNone(violations)
# trailing space
- expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å ")
- violations = rule.validate(u"å ", None)
+ expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å ")
+ violations = rule.validate("å ", None)
self.assertListEqual(violations, [expected_violation])
# trailing tab
- expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å\t")
- violations = rule.validate(u"å\t", None)
+ expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å\t")
+ violations = rule.validate("å\t", None)
self.assertListEqual(violations, [expected_violation])
def test_hard_tabs(self):
rule = TitleHardTab()
# assert no error
- violations = rule.validate(u"This is å test", None)
+ violations = rule.validate("This is å test", None)
self.assertIsNone(violations)
# contains hard tab
- expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", u"This is å\ttest")
- violations = rule.validate(u"This is å\ttest", None)
+ expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", "This is å\ttest")
+ violations = rule.validate("This is å\ttest", None)
self.assertListEqual(violations, [expected_violation])
def test_trailing_punctuation(self):
rule = TitleTrailingPunctuation()
# assert no error
- violations = rule.validate(u"This is å test", None)
+ violations = rule.validate("This is å test", None)
self.assertIsNone(violations)
# assert errors for different punctuations
- punctuation = u"?:!.,;"
+ punctuation = "?:!.,;"
for char in punctuation:
- line = u"This is å test" + char # note that make sure to include some unicode!
+ line = "This is å test" + char # note that make sure to include some unicode!
gitcontext = self.gitcontext(line)
- expected_violation = RuleViolation("T3", u"Title has trailing punctuation ({0})".format(char), line)
+ expected_violation = RuleViolation("T3", f"Title has trailing punctuation ({char})", line)
violations = rule.validate(line, gitcontext)
self.assertListEqual(violations, [expected_violation])
@@ -76,40 +76,40 @@ class TitleRuleTests(BaseTestCase):
rule = TitleMustNotContainWord()
# no violations
- violations = rule.validate(u"This is å test", None)
+ violations = rule.validate("This is å test", None)
self.assertIsNone(violations)
# no violation if WIP occurs inside a wor
- violations = rule.validate(u"This is å wiping test", None)
+ violations = rule.validate("This is å wiping test", None)
self.assertIsNone(violations)
# match literally
- violations = rule.validate(u"WIP This is å test", None)
+ violations = rule.validate("WIP This is å test", None)
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"WIP This is å test")
+ "WIP This is å test")
self.assertListEqual(violations, [expected_violation])
# match case insensitive
- violations = rule.validate(u"wip This is å test", None)
+ violations = rule.validate("wip This is å test", None)
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"wip This is å test")
+ "wip This is å test")
self.assertListEqual(violations, [expected_violation])
# match if there is a colon after the word
- violations = rule.validate(u"WIP:This is å test", None)
+ violations = rule.validate("WIP:This is å test", None)
expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"WIP:This is å test")
+ "WIP:This is å test")
self.assertListEqual(violations, [expected_violation])
# match multiple words
- rule = TitleMustNotContainWord({'words': u"wip,test,å"})
- violations = rule.validate(u"WIP:This is å test", None)
+ rule = TitleMustNotContainWord({'words': "wip,test,å"})
+ violations = rule.validate("WIP:This is å test", None)
expected_violation = RuleViolation("T5", "Title contains the word 'wip' (case-insensitive)",
- u"WIP:This is å test")
+ "WIP:This is å test")
expected_violation2 = RuleViolation("T5", "Title contains the word 'test' (case-insensitive)",
- u"WIP:This is å test")
- expected_violation3 = RuleViolation("T5", u"Title contains the word 'å' (case-insensitive)",
- u"WIP:This is å test")
+ "WIP:This is å test")
+ expected_violation3 = RuleViolation("T5", "Title contains the word 'å' (case-insensitive)",
+ "WIP:This is å test")
self.assertListEqual(violations, [expected_violation, expected_violation2, expected_violation3])
def test_leading_whitespace(self):
@@ -130,12 +130,12 @@ class TitleRuleTests(BaseTestCase):
self.assertListEqual(violations, [expected_violation])
# unicode test
- expected_violation = RuleViolation("T6", "Title has leading whitespace", u" ☺")
- violations = rule.validate(u" ☺", None)
+ expected_violation = RuleViolation("T6", "Title has leading whitespace", " ☺")
+ violations = rule.validate(" ☺", None)
self.assertListEqual(violations, [expected_violation])
def test_regex_matches(self):
- commit = self.gitcommit(u"US1234: åbc\n")
+ commit = self.gitcommit("US1234: åbc\n")
# assert no violation on default regex (=everything allowed)
rule = TitleRegexMatches()
@@ -143,41 +143,41 @@ class TitleRuleTests(BaseTestCase):
self.assertIsNone(violations)
# assert no violation on matching regex
- rule = TitleRegexMatches({'regex': u"^US[0-9]*: å"})
+ rule = TitleRegexMatches({'regex': "^US[0-9]*: å"})
violations = rule.validate(commit.message.title, commit)
self.assertIsNone(violations)
# assert violation when no matching regex
- rule = TitleRegexMatches({'regex': u"^UÅ[0-9]*"})
+ rule = TitleRegexMatches({'regex': "^UÅ[0-9]*"})
violations = rule.validate(commit.message.title, commit)
- expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc")
+ expected_violation = RuleViolation("T7", "Title does not match regex (^UÅ[0-9]*)", "US1234: åbc")
self.assertListEqual(violations, [expected_violation])
def test_min_line_length(self):
rule = TitleMinLength()
# assert no error
- violation = rule.validate(u"å" * 72, None)
+ violation = rule.validate("å" * 72, None)
self.assertIsNone(violation)
# assert error on line length < 5
- expected_violation = RuleViolation("T8", "Title is too short (4<5)", u"å" * 4, 1)
- violations = rule.validate(u"å" * 4, None)
+ expected_violation = RuleViolation("T8", "Title is too short (4<5)", "å" * 4, 1)
+ violations = rule.validate("å" * 4, None)
self.assertListEqual(violations, [expected_violation])
# set line length to 3, and check no violation on length 4
rule = TitleMinLength({'min-length': 3})
- violations = rule.validate(u"å" * 4, None)
+ violations = rule.validate("å" * 4, None)
self.assertIsNone(violations)
# assert no violations on length 3 (this asserts we've implemented a *strict* less than)
rule = TitleMinLength({'min-length': 3})
- violations = rule.validate(u"å" * 3, None)
+ violations = rule.validate("å" * 3, None)
self.assertIsNone(violations)
# assert raise on 2
- expected_violation = RuleViolation("T8", "Title is too short (2<3)", u"å" * 2, 1)
- violations = rule.validate(u"å" * 2, None)
+ expected_violation = RuleViolation("T8", "Title is too short (2<3)", "å" * 2, 1)
+ violations = rule.validate("å" * 2, None)
self.assertListEqual(violations, [expected_violation])
# assert raise on empty title
diff --git a/gitlint/tests/rules/test_user_rules.py b/gitlint/tests/rules/test_user_rules.py
index 52d0283..510a829 100644
--- a/gitlint/tests/rules/test_user_rules.py
+++ b/gitlint/tests/rules/test_user_rules.py
@@ -6,7 +6,6 @@ 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.utils import ustr
from gitlint import options, rules
@@ -25,7 +24,7 @@ class UserRuleTests(BaseTestCase):
# - Other members of the my_commit_rules module are ignored
# (such as func_should_be_ignored, global_variable_should_be_ignored)
# - Rules are loaded non-recursively (user_rules/import_exception directory is ignored)
- self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", ustr(classes))
+ self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", str(classes))
# Assert that we added the new user_rules directory to the system path and modules
self.assertIn(user_rule_path, sys.path)
@@ -33,8 +32,8 @@ class UserRuleTests(BaseTestCase):
# Do some basic asserts on our user rule
self.assertEqual(classes[0].id, "UC1")
- self.assertEqual(classes[0].name, u"my-üser-commit-rule")
- expected_option = options.IntOption('violation-count', 1, u"Number of violåtions to return")
+ self.assertEqual(classes[0].name, "my-üser-commit-rule")
+ expected_option = options.IntOption('violation-count', 1, "Number of violåtions to return")
self.assertListEqual(classes[0].options_spec, [expected_option])
self.assertTrue(hasattr(classes[0], "validate"))
@@ -42,13 +41,13 @@ class UserRuleTests(BaseTestCase):
# expected result
rule_class = classes[0]()
violations = rule_class.validate("false-commit-object (ignored)")
- self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)])
+ self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1)])
# Have it return more violations
rule_class.options['violation-count'].value = 2
violations = rule_class.validate("false-commit-object (ignored)")
- self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1),
- rules.RuleViolation("UC1", u"Commit violåtion 2", u"Contënt 2", 2)])
+ self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1),
+ rules.RuleViolation("UC1", "Commit violåtion 2", "Contënt 2", 2)])
def test_extra_path_specified_by_file(self):
# Test that find_rule_classes can handle an extra path given as a file name instead of a directory
@@ -58,7 +57,7 @@ class UserRuleTests(BaseTestCase):
rule_class = classes[0]()
violations = rule_class.validate("false-commit-object (ignored)")
- self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)])
+ self.assertListEqual(violations, [rules.RuleViolation("UC1", "Commit violåtion 1", "Contënt 1", 1)])
def test_rules_from_init_file(self):
# Test that we can import rules that are defined in __init__.py files
@@ -68,8 +67,8 @@ class UserRuleTests(BaseTestCase):
classes = find_rule_classes(user_rule_path)
# convert classes to strings and sort them so we can compare them
- class_strings = sorted([ustr(clazz) for clazz in classes])
- expected = [u"<class 'my_commit_rules.MyUserCommitRule'>", u"<class 'parent_package.InitFileRule'>"]
+ class_strings = sorted([str(clazz) for clazz in classes])
+ expected = ["<class 'my_commit_rules.MyUserCommitRule'>", "<class 'parent_package.InitFileRule'>"]
self.assertListEqual(class_strings, expected)
def test_empty_user_classes(self):
@@ -92,8 +91,8 @@ class UserRuleTests(BaseTestCase):
find_rule_classes(user_rule_path)
def test_find_rule_classes_nonexisting_path(self):
- with self.assertRaisesMessage(UserRuleError, u"Invalid extra-path: föo/bar"):
- find_rule_classes(u"föo/bar")
+ with self.assertRaisesMessage(UserRuleError, "Invalid extra-path: föo/bar"):
+ find_rule_classes("föo/bar")
def test_assert_valid_rule_class(self):
class MyLineRuleClass(rules.LineRule):
@@ -132,7 +131,7 @@ class UserRuleTests(BaseTestCase):
def test_assert_valid_rule_class_negative_parent(self):
# rule class must extend from LineRule or CommitRule
- class MyRuleClass(object):
+ class MyRuleClass:
pass
expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \
@@ -160,8 +159,9 @@ class UserRuleTests(BaseTestCase):
# Rule ids must not start with one of the reserved id letters
for letter in ["T", "R", "B", "M", "I"]:
MyRuleClass.id = letter + "1"
- expected_msg = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
- with self.assertRaisesMessage(UserRuleError, expected_msg.format(letter)):
+ expected_msg = f"The id '{letter}' of 'MyRuleClass' is invalid. " + \
+ "Gitlint reserves ids starting with R,T,B,M,I"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_name(self):
@@ -186,17 +186,17 @@ class UserRuleTests(BaseTestCase):
class MyRuleClass(parent_class):
id = "UC1"
- name = u"my-rüle-class"
+ name = "my-rüle-class"
# if set, option_spec must be a list of gitlint options
- MyRuleClass.options_spec = u"föo"
+ MyRuleClass.options_spec = "föo"
expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
"of gitlint.options.RuleOption"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# option_spec is a list, but not of gitlint options
- MyRuleClass.options_spec = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
+ MyRuleClass.options_spec = ["föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
@@ -206,14 +206,14 @@ class UserRuleTests(BaseTestCase):
for clazz in baseclasses:
class MyRuleClass(clazz):
id = "UC1"
- name = u"my-rüle-class"
+ name = "my-rüle-class"
with self.assertRaisesMessage(UserRuleError,
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
assert_valid_rule_class(MyRuleClass)
# validate attribute - not a method
- MyRuleClass.validate = u"föo"
+ MyRuleClass.validate = "föo"
with self.assertRaisesMessage(UserRuleError,
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
assert_valid_rule_class(MyRuleClass)
@@ -221,21 +221,21 @@ class UserRuleTests(BaseTestCase):
def test_assert_valid_rule_class_negative_apply(self):
class MyRuleClass(rules.ConfigurationRule):
id = "UCR1"
- name = u"my-rüle-class"
+ name = "my-rüle-class"
expected_msg = "User-defined Configuration rule class 'MyRuleClass' must have an 'apply' method"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# validate attribute - not a method
- MyRuleClass.validate = u"föo"
+ MyRuleClass.validate = "föo"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_target(self):
class MyRuleClass(rules.LineRule):
id = "UC1"
- name = u"my-rüle-class"
+ name = "my-rüle-class"
def validate(self):
pass
@@ -247,7 +247,7 @@ class UserRuleTests(BaseTestCase):
assert_valid_rule_class(MyRuleClass)
# invalid target
- MyRuleClass.target = u"föo"
+ MyRuleClass.target = "föo"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
diff --git a/gitlint/tests/samples/user_rules/my_commit_rules.py b/gitlint/tests/samples/user_rules/my_commit_rules.py
index 5456487..8b0907e 100644
--- a/gitlint/tests/samples/user_rules/my_commit_rules.py
+++ b/gitlint/tests/samples/user_rules/my_commit_rules.py
@@ -5,14 +5,14 @@ from gitlint.options import IntOption
class MyUserCommitRule(CommitRule):
- name = u"my-üser-commit-rule"
+ name = "my-üser-commit-rule"
id = "UC1"
- options_spec = [IntOption('violation-count', 1, u"Number of violåtions to return")]
+ options_spec = [IntOption('violation-count', 1, "Number of violåtions to return")]
def validate(self, _commit):
violations = []
for i in range(1, self.options['violation-count'].value + 1):
- violations.append(RuleViolation(self.id, u"Commit violåtion %d" % i, u"Contënt %d" % i, i))
+ violations.append(RuleViolation(self.id, "Commit violåtion %d" % i, "Contënt %d" % i, i))
return violations
diff --git a/gitlint/tests/samples/user_rules/parent_package/__init__.py b/gitlint/tests/samples/user_rules/parent_package/__init__.py
index 32c05fc..9ea5371 100644
--- a/gitlint/tests/samples/user_rules/parent_package/__init__.py
+++ b/gitlint/tests/samples/user_rules/parent_package/__init__.py
@@ -5,7 +5,7 @@ from gitlint.rules import CommitRule
class InitFileRule(CommitRule):
- name = u"my-init-cömmit-rule"
+ name = "my-init-cömmit-rule"
id = "UC1"
options_spec = []
diff --git a/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py b/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py
index b73a305..b143e62 100644
--- a/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py
+++ b/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py
@@ -4,7 +4,7 @@ from gitlint.rules import CommitRule
class MyUserCommitRule(CommitRule):
- name = u"my-user-cömmit-rule"
+ name = "my-user-cömmit-rule"
id = "UC2"
options_spec = []
diff --git a/gitlint/tests/test_cache.py b/gitlint/tests/test_cache.py
index 5d78953..4b1d47a 100644
--- a/gitlint/tests/test_cache.py
+++ b/gitlint/tests/test_cache.py
@@ -16,13 +16,13 @@ class CacheTests(BaseTestCase):
@cache
def foo(self):
self.counter += 1
- return u"bår"
+ return "bår"
@property
- @cache(cachekey=u"hür")
+ @cache(cachekey="hür")
def bar(self):
self.counter += 1
- return u"fōo"
+ return "fōo"
def test_cache(self):
# Init new class with cached properties
@@ -31,14 +31,14 @@ class CacheTests(BaseTestCase):
self.assertDictEqual(myclass._cache, {})
# Assert that function is called on first access, cache is set
- self.assertEqual(myclass.foo, u"bår")
+ self.assertEqual(myclass.foo, "bår")
self.assertEqual(myclass.counter, 1)
- self.assertDictEqual(myclass._cache, {"foo": u"bår"})
+ self.assertDictEqual(myclass._cache, {"foo": "bår"})
# After function is not called on subsequent access, cache is still set
- self.assertEqual(myclass.foo, u"bår")
+ self.assertEqual(myclass.foo, "bår")
self.assertEqual(myclass.counter, 1)
- self.assertDictEqual(myclass._cache, {"foo": u"bår"})
+ self.assertDictEqual(myclass._cache, {"foo": "bår"})
def test_cache_custom_key(self):
# Init new class with cached properties
@@ -47,11 +47,11 @@ class CacheTests(BaseTestCase):
self.assertDictEqual(myclass._cache, {})
# Assert that function is called on first access, cache is set with custom key
- self.assertEqual(myclass.bar, u"fōo")
+ self.assertEqual(myclass.bar, "fōo")
self.assertEqual(myclass.counter, 1)
- self.assertDictEqual(myclass._cache, {u"hür": u"fōo"})
+ self.assertDictEqual(myclass._cache, {"hür": "fōo"})
# After function is not called on subsequent access, cache is still set
- self.assertEqual(myclass.bar, u"fōo")
+ self.assertEqual(myclass.bar, "fōo")
self.assertEqual(myclass.counter, 1)
- self.assertDictEqual(myclass._cache, {u"hür": u"fōo"})
+ self.assertDictEqual(myclass._cache, {"hür": "fōo"})
diff --git a/gitlint/tests/test_display.py b/gitlint/tests/test_display.py
index 1c64b34..167ef96 100644
--- a/gitlint/tests/test_display.py
+++ b/gitlint/tests/test_display.py
@@ -1,19 +1,8 @@
# -*- coding: utf-8 -*-
-try:
- # python 2.x
- from StringIO import StringIO
-except ImportError:
- # python 3.x
- from io import StringIO
+from io import StringIO
-
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
from gitlint.display import Display
from gitlint.config import LintConfig
@@ -28,21 +17,21 @@ class DisplayTests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
# Non exact outputting, should output both v and vv output
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
- display.v(u"tëst")
- display.vv(u"tëst2")
+ display.v("tëst")
+ display.vv("tëst2")
# vvvv should be ignored regardless
- display.vvv(u"tëst3.1")
- display.vvv(u"tëst3.2", exact=True)
- self.assertEqual(u"tëst\ntëst2\n", stdout.getvalue())
+ display.vvv("tëst3.1")
+ display.vvv("tëst3.2", exact=True)
+ self.assertEqual("tëst\ntëst2\n", stdout.getvalue())
# exact outputting, should only output v
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
- display.v(u"tëst", exact=True)
- display.vv(u"tëst2", exact=True)
+ display.v("tëst", exact=True)
+ display.vv("tëst2", exact=True)
# vvvv should be ignored regardless
- display.vvv(u"tëst3.1")
- display.vvv(u"tëst3.2", exact=True)
- self.assertEqual(u"tëst2\n", stdout.getvalue())
+ display.vvv("tëst3.1")
+ display.vvv("tëst3.2", exact=True)
+ self.assertEqual("tëst2\n", stdout.getvalue())
# standard error should be empty throughtout all of this
self.assertEqual('', stderr.getvalue())
@@ -54,21 +43,21 @@ class DisplayTests(BaseTestCase):
with patch('gitlint.display.stdout', new=StringIO()) as stdout:
# Non exact outputting, should output both v and vv output
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- display.e(u"tëst")
- display.ee(u"tëst2")
+ display.e("tëst")
+ display.ee("tëst2")
# vvvv should be ignored regardless
- display.eee(u"tëst3.1")
- display.eee(u"tëst3.2", exact=True)
- self.assertEqual(u"tëst\ntëst2\n", stderr.getvalue())
+ display.eee("tëst3.1")
+ display.eee("tëst3.2", exact=True)
+ self.assertEqual("tëst\ntëst2\n", stderr.getvalue())
# exact outputting, should only output v
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
- display.e(u"tëst", exact=True)
- display.ee(u"tëst2", exact=True)
+ display.e("tëst", exact=True)
+ display.ee("tëst2", exact=True)
# vvvv should be ignored regardless
- display.eee(u"tëst3.1")
- display.eee(u"tëst3.2", exact=True)
- self.assertEqual(u"tëst2\n", stderr.getvalue())
+ display.eee("tëst3.1")
+ display.eee("tëst3.2", exact=True)
+ self.assertEqual("tëst2\n", stderr.getvalue())
# standard output should be empty throughtout all of this
self.assertEqual('', stdout.getvalue())
diff --git a/gitlint/tests/test_hooks.py b/gitlint/tests/test_hooks.py
index 62f55e5..0ce5040 100644
--- a/gitlint/tests/test_hooks.py
+++ b/gitlint/tests/test_hooks.py
@@ -2,12 +2,7 @@
import os
-try:
- # python 2.x
- from mock import patch, ANY, mock_open
-except ImportError:
- # python 3.x
- from unittest.mock import patch, ANY, mock_open # pylint: disable=no-name-in-module, import-error
+from unittest.mock import patch, ANY, mock_open
from gitlint.tests.base import BaseTestCase
from gitlint.config import LintConfig
@@ -19,7 +14,7 @@ class HookTests(BaseTestCase):
@patch('gitlint.hooks.git_hooks_dir')
def test_commit_msg_hook_path(self, git_hooks_dir):
- git_hooks_dir.return_value = os.path.join(u"/föo", u"bar")
+ git_hooks_dir.return_value = os.path.join("/föo", "bar")
lint_config = LintConfig()
lint_config.target = self.SAMPLES_DIR
expected_path = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
@@ -37,8 +32,8 @@ class HookTests(BaseTestCase):
@patch('gitlint.hooks.git_hooks_dir')
def test_install_commit_msg_hook(git_hooks_dir, isdir, path_exists, copy, stat, chmod):
lint_config = LintConfig()
- lint_config.target = os.path.join(u"/hür", u"dur")
- git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
+ lint_config.target = os.path.join("/hür", "dur")
+ git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
GitHookInstaller.install_commit_msg_hook(lint_config)
isdir.assert_called_with(git_hooks_dir.return_value)
@@ -54,11 +49,11 @@ class HookTests(BaseTestCase):
@patch('gitlint.hooks.git_hooks_dir')
def test_install_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, copy):
lint_config = LintConfig()
- lint_config.target = os.path.join(u"/hür", u"dur")
- git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
+ lint_config.target = os.path.join("/hür", "dur")
+ git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
# mock that current dir is not a git repo
isdir.return_value = False
- expected_msg = u"{0} is not a git repository.".format(lint_config.target)
+ 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)
@@ -69,7 +64,7 @@ class HookTests(BaseTestCase):
isdir.return_value = True
path_exists.return_value = True
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
- expected_msg = u"There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \
+ expected_msg = f"There is already a commit-msg hook file present in {expected_dst}.\n" + \
"gitlint currently does not support appending to an existing commit-msg file."
with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.install_commit_msg_hook(lint_config)
@@ -81,8 +76,8 @@ class HookTests(BaseTestCase):
@patch('gitlint.hooks.git_hooks_dir')
def test_uninstall_commit_msg_hook(git_hooks_dir, isdir, path_exists, remove):
lint_config = LintConfig()
- git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
- lint_config.target = os.path.join(u"/hür", u"dur")
+ git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
+ lint_config.target = os.path.join("/hür", "dur")
read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
@@ -99,12 +94,12 @@ class HookTests(BaseTestCase):
@patch('gitlint.hooks.git_hooks_dir')
def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, remove):
lint_config = LintConfig()
- lint_config.target = os.path.join(u"/hür", u"dur")
- git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
+ lint_config.target = os.path.join("/hür", "dur")
+ git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks")
# mock that the current directory is not a git repo
isdir.return_value = False
- expected_msg = u"{0} is not a git repository.".format(lint_config.target)
+ 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)
@@ -115,7 +110,7 @@ class HookTests(BaseTestCase):
isdir.return_value = True
path_exists.return_value = False
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
- expected_msg = u"There is no commit-msg hook present in {0}.".format(expected_dst)
+ 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)
@@ -127,7 +122,7 @@ class HookTests(BaseTestCase):
path_exists.return_value = True
read_data = "#!/bin/sh\nfoo"
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
- expected_msg = u"The commit-msg hook in {0} was not installed by gitlint ".format(expected_dst) + \
+ expected_msg = f"The commit-msg hook in {expected_dst} was not installed by gitlint " + \
"(or it was modified).\nUninstallation of 3th party or modified gitlint hooks " + \
"is not supported."
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
diff --git a/gitlint/tests/test_lint.py b/gitlint/tests/test_lint.py
index 3bf9a94..b743389 100644
--- a/gitlint/tests/test_lint.py
+++ b/gitlint/tests/test_lint.py
@@ -1,18 +1,8 @@
# -*- coding: utf-8 -*-
-try:
- # python 2.x
- from StringIO import StringIO
-except ImportError:
- # python 3.x
- from io import StringIO
-
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
+from io import StringIO
+
+from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
from gitlint.tests.base import BaseTestCase
from gitlint.lint import GitLinter
@@ -27,14 +17,14 @@ class LintTests(BaseTestCase):
gitcontext = self.gitcontext(self.get_sample("commit_message/sample1"))
violations = linter.lint(gitcontext.commits[-1])
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
- u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ "Commit title contåining 'WIP', as well as trailing punctuation.", 1),
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ "Commit title contåining 'WIP', as well as trailing punctuation.", 1),
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
RuleViolation("B1", "Line exceeds max length (135>80)",
"This is the first line of the commit message body and it is meant to test " +
"a line that exceeds the maximum line length of 80 characters.", 3),
- RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4),
+ RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling space. ", 4),
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
RuleViolation("B3", "Line contains hard tab characters (\\t)",
"This line has a trailing tab.\t", 5)]
@@ -46,7 +36,7 @@ class LintTests(BaseTestCase):
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
violations = linter.lint(gitcontext.commits[-1])
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"Just a title contåining WIP", 1),
+ "Just a title contåining WIP", 1),
RuleViolation("B6", "Body message is missing", None, 3)]
self.assertListEqual(violations, expected)
@@ -56,7 +46,7 @@ class LintTests(BaseTestCase):
gitcontext = self.gitcontext(self.get_sample("commit_message/sample3"))
violations = linter.lint(gitcontext.commits[-1])
- title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
+ title = " Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
RuleViolation("T3", "Title has trailing punctuation (.)", title, 1),
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
@@ -64,12 +54,12 @@ class LintTests(BaseTestCase):
RuleViolation("T6", "Title has leading whitespace", title, 1),
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
RuleViolation("B1", "Line exceeds max length (101>80)",
- u"This is the first line is meånt to test a line that exceeds the maximum line " +
+ "This is the first line is meånt to test a line that exceeds the maximum line " +
"length of 80 characters.", 3),
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing space. ", 4),
- RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling tab.\t", 5),
+ RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling tab.\t", 5),
RuleViolation("B3", "Line contains hard tab characters (\\t)",
- u"This line has a tråiling tab.\t", 5)]
+ "This line has a tråiling tab.\t", 5)]
self.assertListEqual(violations, expected)
@@ -90,13 +80,13 @@ class LintTests(BaseTestCase):
linter = GitLinter(config_builder.build())
violations = linter.lint(commit)
- title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
+ title = " Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters."
# expect only certain violations because sample5 has a 'gitlint-ignore: T3, T6, body-max-line-length'
expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1),
RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1),
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1),
- RuleViolation("B4", "Second line is not empty", u"This line should be ëmpty", 2),
- RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4),
+ RuleViolation("B4", "Second line is not empty", "This line should be ëmpty", 2),
+ RuleViolation("B2", "Line has trailing whitespace", "This line has a tråiling space. ", 4),
RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5),
RuleViolation("B3", "Line contains hard tab characters (\\t)",
"This line has a trailing tab.\t", 5)]
@@ -106,11 +96,11 @@ class LintTests(BaseTestCase):
""" Lint sample2 but also add some metadata to the commit so we that gets linted as well """
linter = GitLinter(LintConfig())
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
- gitcontext.commits[0].author_email = u"foo bår"
+ gitcontext.commits[0].author_email = "foo bår"
violations = linter.lint(gitcontext.commits[-1])
- expected = [RuleViolation("M1", "Author email for commit is invalid", u"foo bår", None),
+ expected = [RuleViolation("M1", "Author email for commit is invalid", "foo bår", None),
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"Just a title contåining WIP", 1),
+ "Just a title contåining WIP", 1),
RuleViolation("B6", "Body message is missing", None, 3)]
self.assertListEqual(violations, expected)
@@ -123,7 +113,7 @@ class LintTests(BaseTestCase):
expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
RuleViolation("B3", "Line contains hard tab characters (\\t)",
- u"This line has a tråiling tab.\t", 5)]
+ "This line has a tråiling tab.\t", 5)]
self.assertListEqual(violations, expected)
@@ -146,19 +136,19 @@ class LintTests(BaseTestCase):
# Normally we'd expect a B6 violation, but that one is skipped because of the specific ignore set above
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"Just a title contåining WIP", 1)]
+ "Just a title contåining WIP", 1)]
self.assertListEqual(violations, expected)
# Test ignoring body lines
lint_config = LintConfig()
linter = GitLinter(lint_config)
- lint_config.set_rule_option("I3", "regex", u"(.*)tråiling(.*)")
+ lint_config.set_rule_option("I3", "regex", "(.*)tråiling(.*)")
violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample1")))
expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
- u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ "Commit title contåining 'WIP', as well as trailing punctuation.", 1),
RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
- u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ "Commit title contåining 'WIP', as well as trailing punctuation.", 1),
RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
RuleViolation("B1", "Line exceeds max length (135>80)",
"This is the first line of the commit message body and it is meant to test " +
@@ -171,7 +161,7 @@ class LintTests(BaseTestCase):
def test_lint_special_commit(self):
for commit_type in ["merge", "revert", "squash", "fixup"]:
- commit = self.gitcommit(self.get_sample("commit_message/{0}".format(commit_type)))
+ commit = self.gitcommit(self.get_sample(f"commit_message/{commit_type}"))
lintconfig = LintConfig()
linter = GitLinter(lintconfig)
violations = linter.lint(commit)
@@ -180,7 +170,7 @@ class LintTests(BaseTestCase):
self.assertListEqual(violations, [])
# Check that we do see violations if we disable 'ignore-merge-commits'
- setattr(lintconfig, "ignore_{0}_commits".format(commit_type), False)
+ setattr(lintconfig, f"ignore_{commit_type}_commits", False)
linter = GitLinter(lintconfig)
violations = linter.lint(commit)
self.assertTrue(len(violations) > 0)
@@ -195,7 +185,7 @@ class LintTests(BaseTestCase):
self.assertListEqual(violations, [])
# Matching regexes shouldn't be a problem
- rule_regexes = [("title-match-regex", u"Tïtle$"), ("body-match-regex", u"Sïgned-Off-By: (.*)$")]
+ rule_regexes = [("title-match-regex", "Tïtle$"), ("body-match-regex", "Sïgned-Off-By: (.*)$")]
for rule_regex in rule_regexes:
lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1])
violations = linter.lint(commit)
@@ -203,16 +193,16 @@ class LintTests(BaseTestCase):
# Non-matching regexes should return violations
rule_regexes = [("title-match-regex", ), ("body-match-regex",)]
- lintconfig.set_rule_option("title-match-regex", "regex", u"^Tïtle")
- lintconfig.set_rule_option("body-match-regex", "regex", u"Sügned-Off-By: (.*)$")
- expected_violations = [RuleViolation("T7", u"Title does not match regex (^Tïtle)", u"Normal Commit Tïtle", 1),
- RuleViolation("B8", u"Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)]
+ lintconfig.set_rule_option("title-match-regex", "regex", "^Tïtle")
+ lintconfig.set_rule_option("body-match-regex", "regex", "Sügned-Off-By: (.*)$")
+ expected_violations = [RuleViolation("T7", "Title does not match regex (^Tïtle)", "Normal Commit Tïtle", 1),
+ RuleViolation("B8", "Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)]
violations = linter.lint(commit)
self.assertListEqual(violations, expected_violations)
def test_print_violations(self):
- violations = [RuleViolation("RULE_ID_1", u"Error Messåge 1", "Violating Content 1", None),
- RuleViolation("RULE_ID_2", "Error Message 2", u"Violåting Content 2", 2)]
+ violations = [RuleViolation("RULE_ID_1", "Error Messåge 1", "Violating Content 1", None),
+ RuleViolation("RULE_ID_2", "Error Message 2", "Violåting Content 2", 2)]
linter = GitLinter(LintConfig())
# test output with increasing verbosity
@@ -224,54 +214,54 @@ class LintTests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
linter.config.verbosity = 1
linter.print_violations(violations)
- expected = u"-: RULE_ID_1\n2: RULE_ID_2\n"
+ expected = "-: RULE_ID_1\n2: RULE_ID_2\n"
self.assertEqual(expected, stderr.getvalue())
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
linter.config.verbosity = 2
linter.print_violations(violations)
- expected = u"-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
+ expected = "-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
self.assertEqual(expected, stderr.getvalue())
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
linter.config.verbosity = 3
linter.print_violations(violations)
- expected = u"-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
- u"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
+ expected = "-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
+ "2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
self.assertEqual(expected, stderr.getvalue())
def test_named_rules(self):
""" Test that when named rules are present, both them and the original (non-named) rules executed """
lint_config = LintConfig()
- for rule_name in [u"my-ïd", u"another-rule-ïd"]:
+ for rule_name in ["my-ïd", "another-rule-ïd"]:
rule_id = TitleMustNotContainWord.id + ":" + rule_name
lint_config.rules.add_rule(TitleMustNotContainWord, rule_id)
- lint_config.set_rule_option(rule_id, "words", [u"Föo"])
+ lint_config.set_rule_option(rule_id, "words", ["Föo"])
linter = GitLinter(lint_config)
- violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
- RuleViolation(u"T5:another-rule-ïd", u"Title contains the word 'Föo' (case-insensitive)",
- u"WIP: Föo bar", 1),
- RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
- u"WIP: Föo bar", 1)]
- self.assertListEqual(violations, linter.lint(self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")))
+ violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1),
+ RuleViolation("T5:another-rule-ïd", "Title contains the word 'Föo' (case-insensitive)",
+ "WIP: Föo bar", 1),
+ RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)",
+ "WIP: Föo bar", 1)]
+ self.assertListEqual(violations, linter.lint(self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla")))
def test_ignore_named_rules(self):
""" Test that named rules can be ignored """
# Add named rule to lint config
config_builder = LintConfigBuilder()
- rule_id = TitleMustNotContainWord.id + u":my-ïd"
- config_builder.set_option(rule_id, "words", [u"Föo"])
+ rule_id = TitleMustNotContainWord.id + ":my-ïd"
+ config_builder.set_option(rule_id, "words", ["Föo"])
lint_config = config_builder.build()
linter = GitLinter(lint_config)
- commit = self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")
+ commit = self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla")
# By default, we expect both the violations of the regular rule as well as the named rule to show up
- violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
- RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
- u"WIP: Föo bar", 1)]
+ violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1),
+ RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)",
+ "WIP: Föo bar", 1)]
self.assertListEqual(violations, linter.lint(commit))
# ignore regular rule: only named rule violations show up
@@ -283,5 +273,5 @@ class LintTests(BaseTestCase):
self.assertListEqual(violations[:-1], linter.lint(commit))
# ignore named rule by name: only regular rule violations show up
- lint_config.ignore = [TitleMustNotContainWord.name + u":my-ïd"]
+ lint_config.ignore = [TitleMustNotContainWord.name + ":my-ïd"]
self.assertListEqual(violations[:-1], linter.lint(commit))
diff --git a/gitlint/tests/test_options.py b/gitlint/tests/test_options.py
index 68f0f8c..fc3ccc1 100644
--- a/gitlint/tests/test_options.py
+++ b/gitlint/tests/test_options.py
@@ -9,25 +9,25 @@ from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOp
class RuleOptionTests(BaseTestCase):
def test_option_equality(self):
- options = {IntOption: 123, StrOption: u"foöbar", BoolOption: False, ListOption: ["a", "b"],
- PathOption: ".", RegexOption: u"^foöbar(.*)"}
+ options = {IntOption: 123, StrOption: "foöbar", BoolOption: False, ListOption: ["a", "b"],
+ PathOption: ".", RegexOption: "^foöbar(.*)"}
for clazz, val in options.items():
# 2 options are equal if their name, value and description match
- option1 = clazz(u"test-öption", val, u"Test Dëscription")
- option2 = clazz(u"test-öption", val, u"Test Dëscription")
+ option1 = clazz("test-öption", val, "Test Dëscription")
+ option2 = clazz("test-öption", val, "Test Dëscription")
self.assertEqual(option1, option2)
# Not equal: class, name, description, value are different
- self.assertNotEqual(option1, IntOption(u"tëst-option1", 123, u"Test Dëscription"))
- self.assertNotEqual(option1, StrOption(u"tëst-option1", u"åbc", u"Test Dëscription"))
- self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbcd", u"Test Dëscription"))
- self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbc", u"Test Dëscription2"))
+ self.assertNotEqual(option1, IntOption("tëst-option1", 123, "Test Dëscription"))
+ self.assertNotEqual(option1, StrOption("tëst-option1", "åbc", "Test Dëscription"))
+ self.assertNotEqual(option1, StrOption("tëst-option", "åbcd", "Test Dëscription"))
+ self.assertNotEqual(option1, StrOption("tëst-option", "åbc", "Test Dëscription2"))
def test_int_option(self):
# normal behavior
- option = IntOption(u"tëst-name", 123, u"Tëst Description")
- self.assertEqual(option.name, u"tëst-name")
- self.assertEqual(option.description, u"Tëst Description")
+ option = IntOption("tëst-name", 123, "Tëst Description")
+ self.assertEqual(option.name, "tëst-name")
+ self.assertEqual(option.description, "Tëst Description")
self.assertEqual(option.value, 123)
# re-set value
@@ -39,12 +39,12 @@ class RuleOptionTests(BaseTestCase):
self.assertEqual(option.value, None)
# error on negative int when not allowed
- expected_error = u"Option 'tëst-name' must be a positive integer (current value: '-123')"
+ expected_error = "Option 'tëst-name' must be a positive integer (current value: '-123')"
with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set(-123)
# error on non-int value
- expected_error = u"Option 'tëst-name' must be a positive integer (current value: 'foo')"
+ expected_error = "Option 'tëst-name' must be a positive integer (current value: 'foo')"
with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set("foo")
@@ -54,20 +54,20 @@ class RuleOptionTests(BaseTestCase):
self.assertEqual(option.value, -456)
# error on non-int value when negative int is allowed
- expected_error = u"Option 'test-name' must be an integer (current value: 'foo')"
+ expected_error = "Option 'test-name' must be an integer (current value: 'foo')"
with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set("foo")
def test_str_option(self):
# normal behavior
- option = StrOption(u"tëst-name", u"föo", u"Tëst Description")
- self.assertEqual(option.name, u"tëst-name")
- self.assertEqual(option.description, u"Tëst Description")
- self.assertEqual(option.value, u"föo")
+ option = StrOption("tëst-name", "föo", "Tëst Description")
+ self.assertEqual(option.name, "tëst-name")
+ self.assertEqual(option.description, "Tëst Description")
+ self.assertEqual(option.value, "föo")
# re-set value
- option.set(u"bår")
- self.assertEqual(option.value, u"bår")
+ option.set("bår")
+ self.assertEqual(option.value, "bår")
# conversion to str
option.set(123)
@@ -83,9 +83,9 @@ class RuleOptionTests(BaseTestCase):
def test_boolean_option(self):
# normal behavior
- option = BoolOption(u"tëst-name", "true", u"Tëst Description")
- self.assertEqual(option.name, u"tëst-name")
- self.assertEqual(option.description, u"Tëst Description")
+ option = BoolOption("tëst-name", "true", "Tëst Description")
+ self.assertEqual(option.name, "tëst-name")
+ self.assertEqual(option.description, "Tëst Description")
self.assertEqual(option.value, True)
# re-set value
@@ -97,25 +97,25 @@ class RuleOptionTests(BaseTestCase):
self.assertEqual(option.value, True)
# error on incorrect value
- incorrect_values = [1, -1, "foo", u"bår", ["foo"], {'foo': "bar"}, None]
+ incorrect_values = [1, -1, "foo", "bår", ["foo"], {'foo': "bar"}, None]
for value in incorrect_values:
- with self.assertRaisesMessage(RuleOptionError, u"Option 'tëst-name' must be either 'true' or 'false'"):
+ with self.assertRaisesMessage(RuleOptionError, "Option 'tëst-name' must be either 'true' or 'false'"):
option.set(value)
def test_list_option(self):
# normal behavior
- option = ListOption(u"tëst-name", u"å,b,c,d", u"Tëst Description")
- self.assertEqual(option.name, u"tëst-name")
- self.assertEqual(option.description, u"Tëst Description")
- self.assertListEqual(option.value, [u"å", u"b", u"c", u"d"])
+ option = ListOption("tëst-name", "å,b,c,d", "Tëst Description")
+ self.assertEqual(option.name, "tëst-name")
+ self.assertEqual(option.description, "Tëst Description")
+ self.assertListEqual(option.value, ["å", "b", "c", "d"])
# re-set value
- option.set(u"1,2,3,4")
- self.assertListEqual(option.value, [u"1", u"2", u"3", u"4"])
+ option.set("1,2,3,4")
+ self.assertListEqual(option.value, ["1", "2", "3", "4"])
# set list
- option.set([u"foo", u"bår", u"test"])
- self.assertListEqual(option.value, [u"foo", u"bår", u"test"])
+ option.set(["foo", "bår", "test"])
+ self.assertListEqual(option.value, ["foo", "bår", "test"])
# None
option.set(None)
@@ -134,8 +134,8 @@ class RuleOptionTests(BaseTestCase):
self.assertListEqual(option.value, [])
# trailing comma
- option.set(u"ë,f,g,")
- self.assertListEqual(option.value, [u"ë", u"f", u"g"])
+ option.set("ë,f,g,")
+ self.assertListEqual(option.value, ["ë", "f", "g"])
# leading and trailing whitespace should be trimmed, but only deduped within text
option.set(" abc , def , ghi \t , jkl mno ")
@@ -150,11 +150,11 @@ class RuleOptionTests(BaseTestCase):
self.assertListEqual(option.value, ["123"])
def test_path_option(self):
- option = PathOption(u"tëst-directory", ".", u"Tëst Description", type=u"dir")
- self.assertEqual(option.name, u"tëst-directory")
- self.assertEqual(option.description, u"Tëst Description")
+ 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.type, u"dir")
+ self.assertEqual(option.type, "dir")
# re-set value
option.set(self.SAMPLES_DIR)
@@ -165,33 +165,32 @@ class RuleOptionTests(BaseTestCase):
self.assertIsNone(option.value)
# set to int
- expected = u"Option tëst-directory must be an existing directory (current value: '1234')"
+ expected = "Option tëst-directory must be an existing directory (current value: '1234')"
with self.assertRaisesMessage(RuleOptionError, expected):
option.set(1234)
# set to non-existing directory
- non_existing_path = os.path.join(u"/föo", u"bar")
- expected = u"Option tëst-directory must be an existing directory (current value: '{0}')"
- with self.assertRaisesMessage(RuleOptionError, expected.format(non_existing_path)):
+ non_existing_path = os.path.join("/föo", "bar")
+ expected = f"Option tëst-directory must be an existing directory (current value: '{non_existing_path}')"
+ with self.assertRaisesMessage(RuleOptionError, expected):
option.set(non_existing_path)
# set to a file, should raise exception since option.type = dir
sample_path = self.get_sample_path(os.path.join("commit_message", "sample1"))
- expected = u"Option tëst-directory must be an existing directory (current value: '{0}')".format(sample_path)
+ expected = f"Option tëst-directory must be an existing directory (current value: '{sample_path}')"
with self.assertRaisesMessage(RuleOptionError, expected):
option.set(sample_path)
# set option.type = file, file should now be accepted, directories not
- option.type = u"file"
+ option.type = "file"
option.set(sample_path)
self.assertEqual(option.value, sample_path)
- expected = u"Option tëst-directory must be an existing file (current value: '{0}')".format(
- self.get_sample_path())
+ expected = f"Option tëst-directory must be an existing file (current value: '{self.get_sample_path()}')"
with self.assertRaisesMessage(RuleOptionError, expected):
option.set(self.get_sample_path())
# set option.type = both, files and directories should now be accepted
- option.type = u"both"
+ option.type = "both"
option.set(sample_path)
self.assertEqual(option.value, sample_path)
option.set(self.get_sample_path())
@@ -199,27 +198,27 @@ class RuleOptionTests(BaseTestCase):
# Expect exception if path type is invalid
option.type = u'föo'
- expected = u"Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
+ expected = "Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
with self.assertRaisesMessage(RuleOptionError, expected):
option.set("haha")
def test_regex_option(self):
# normal behavior
- option = RegexOption(u"tëst-regex", u"^myrëgex(.*)foo$", u"Tëst Regex Description")
- self.assertEqual(option.name, u"tëst-regex")
- self.assertEqual(option.description, u"Tëst Regex Description")
- self.assertEqual(option.value, re.compile(u"^myrëgex(.*)foo$", re.UNICODE))
+ option = RegexOption("tëst-regex", "^myrëgex(.*)foo$", "Tëst Regex Description")
+ self.assertEqual(option.name, "tëst-regex")
+ self.assertEqual(option.description, "Tëst Regex Description")
+ self.assertEqual(option.value, re.compile("^myrëgex(.*)foo$", re.UNICODE))
# re-set value
- option.set(u"[0-9]föbar.*")
- self.assertEqual(option.value, re.compile(u"[0-9]föbar.*", re.UNICODE))
+ option.set("[0-9]föbar.*")
+ self.assertEqual(option.value, re.compile("[0-9]föbar.*", re.UNICODE))
# set None
option.set(None)
self.assertIsNone(option.value)
# error on invalid regex
- incorrect_values = [u"foo(", 123, -1]
+ incorrect_values = ["foo(", 123, -1]
for value in incorrect_values:
- with self.assertRaisesRegex(RuleOptionError, u"Invalid regular expression"):
+ with self.assertRaisesRegex(RuleOptionError, "Invalid regular expression"):
option.set(value)
diff --git a/gitlint/tests/test_utils.py b/gitlint/tests/test_utils.py
index 5841b63..4ec8bda 100644
--- a/gitlint/tests/test_utils.py
+++ b/gitlint/tests/test_utils.py
@@ -1,15 +1,10 @@
# -*- coding: utf-8 -*-
+from unittest.mock import patch
+
from gitlint import utils
from gitlint.tests.base import BaseTestCase
-try:
- # python 2.x
- from mock import patch
-except ImportError:
- # python 3.x
- from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
-
class UtilsTests(BaseTestCase):
@@ -24,7 +19,7 @@ class UtilsTests(BaseTestCase):
self.assertEqual(utils.use_sh_library(), True)
patched_env.get.assert_called_once_with("GITLINT_USE_SH_LIB", None)
- for invalid_val in ["0", u"foöbar"]:
+ for invalid_val in ["0", "foöbar"]:
patched_env.get.reset_mock() # reset mock call count
patched_env.get.return_value = invalid_val
self.assertEqual(utils.use_sh_library(), False, invalid_val)
@@ -41,12 +36,12 @@ class UtilsTests(BaseTestCase):
@patch('gitlint.utils.locale')
def test_default_encoding_non_windows(self, mocked_locale):
utils.PLATFORM_IS_WINDOWS = False
- mocked_locale.getpreferredencoding.return_value = u"foöbar"
- self.assertEqual(utils.getpreferredencoding(), u"foöbar")
+ mocked_locale.getpreferredencoding.return_value = "foöbar"
+ self.assertEqual(utils.getpreferredencoding(), "foöbar")
mocked_locale.getpreferredencoding.assert_called_once()
mocked_locale.getpreferredencoding.return_value = False
- self.assertEqual(utils.getpreferredencoding(), u"UTF-8")
+ self.assertEqual(utils.getpreferredencoding(), "UTF-8")
@patch('os.environ')
def test_default_encoding_windows(self, patched_env):
@@ -60,23 +55,23 @@ class UtilsTests(BaseTestCase):
patched_env.get.side_effect = mocked_get
# Assert getpreferredencoding reads env vars in order: LC_ALL, LC_CTYPE, LANG
- mock_env = {"LC_ALL": u"ASCII", "LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
- self.assertEqual(utils.getpreferredencoding(), u"ASCII")
- mock_env = {"LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
- self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
- mock_env = {"LANG": u"CP1251"}
- self.assertEqual(utils.getpreferredencoding(), u"CP1251")
+ mock_env = {"LC_ALL": "ASCII", "LC_CTYPE": "UTF-16", "LANG": "CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), "ASCII")
+ mock_env = {"LC_CTYPE": "UTF-16", "LANG": "CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), "UTF-16")
+ mock_env = {"LANG": "CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), "CP1251")
# Assert split on dot
- mock_env = {"LANG": u"foo.UTF-16"}
- self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
+ mock_env = {"LANG": "foo.UTF-16"}
+ self.assertEqual(utils.getpreferredencoding(), "UTF-16")
# assert default encoding is UTF-8
mock_env = {}
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
- mock_env = {"FOO": u"föo"}
+ mock_env = {"FOO": "föo"}
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
# assert fallback encoding is UTF-8 in case we set an unavailable encoding
- mock_env = {"LC_ALL": u"foo"}
- self.assertEqual(utils.getpreferredencoding(), u"UTF-8")
+ mock_env = {"LC_ALL": "foo"}
+ self.assertEqual(utils.getpreferredencoding(), "UTF-8")
diff --git a/gitlint/utils.py b/gitlint/utils.py
index 89015e7..6976aac 100644
--- a/gitlint/utils.py
+++ b/gitlint/utils.py
@@ -1,7 +1,6 @@
# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
import codecs
import platform
-import sys
import os
import locale
@@ -25,16 +24,6 @@ def platform_is_windows():
PLATFORM_IS_WINDOWS = platform_is_windows()
########################################################################################################################
-# IS_PY2
-
-
-def is_py2():
- return sys.version_info[0] == 2
-
-
-IS_PY2 = is_py2()
-
-########################################################################################################################
# USE_SH_LIB
# Determine whether to use the `sh` library
# On windows we won't want to use the sh library since it's not supported - instead we'll use our own shell module.
@@ -90,39 +79,3 @@ def getpreferredencoding():
DEFAULT_ENCODING = getpreferredencoding()
-
-########################################################################################################################
-# Unicode utility functions
-
-
-def ustr(obj):
- """ Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3"""
- if IS_PY2:
- # If we are getting a string, then do an explicit decode
- # else, just call the unicode method of the object
- if type(obj) in [str, basestring]: # pragma: no cover # noqa
- return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa
- else:
- return unicode(obj) # pragma: no cover # noqa
- else:
- if type(obj) in [bytes]:
- return obj.decode(DEFAULT_ENCODING)
- else:
- return str(obj)
-
-
-def sstr(obj):
- """ Python 2 and 3 utility method that converts an obj to a DEFAULT_ENCODING encoded string in python 2
- and to unicode in python 3.
- Especially useful for implementing __str__ methods in python 2: http://stackoverflow.com/a/1307210/381010"""
- if IS_PY2:
- # For lists and tuples in python2, remove unicode string representation characters.
- # i.e. ensure lists are printed as ['a', 'b'] and not [u'a', u'b']
- if type(obj) in [list]:
- return [sstr(item) for item in obj] # pragma: no cover # noqa
- elif type(obj) in [tuple]:
- return tuple(sstr(item) for item in obj) # pragma: no cover # noqa
-
- return unicode(obj).encode(DEFAULT_ENCODING) # pragma: no cover # noqa
- else:
- return obj # pragma: no cover