summaryrefslogtreecommitdiffstats
path: root/gitlint/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'gitlint/cli.py')
-rw-r--r--gitlint/cli.py62
1 files changed, 39 insertions, 23 deletions
diff --git a/gitlint/cli.py b/gitlint/cli.py
index 9b16d47..19676b3 100644
--- a/gitlint/cli.py
+++ b/gitlint/cli.py
@@ -18,6 +18,7 @@ from gitlint.utils import LOG_FORMAT
from gitlint.exception import GitlintError
# Error codes
+GITLINT_SUCCESS = 0
MAX_VIOLATION_ERROR_CODE = 252
USAGE_ERROR_CODE = 253
GIT_CONTEXT_ERROR_CODE = 254
@@ -61,7 +62,8 @@ def log_system_info():
def build_config( # pylint: disable=too-many-arguments
- target, config_path, c, extra_path, ignore, contrib, ignore_stdin, staged, verbose, silent, debug
+ target, config_path, c, extra_path, ignore, contrib, ignore_stdin, staged, fail_without_commits, verbose,
+ silent, debug
):
""" Creates a LintConfig object based on a set of commandline parameters. """
config_builder = LintConfigBuilder()
@@ -102,6 +104,9 @@ def build_config( # pylint: disable=too-many-arguments
if staged:
config_builder.set_option('general', 'staged', staged)
+ if fail_without_commits:
+ config_builder.set_option('general', 'fail-without-commits', fail_without_commits)
+
config = config_builder.build()
return config, config_builder
@@ -139,7 +144,7 @@ def get_stdin_data():
return False
-def build_git_context(lint_config, msg_filename, refspec):
+def build_git_context(lint_config, msg_filename, commit_hash, refspec):
""" Builds a git context based on passed parameters and order of precedence """
# Determine which GitContext method to use if a custom message is passed
@@ -168,7 +173,11 @@ def build_git_context(lint_config, msg_filename, refspec):
# 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)
+
+ if commit_hash and refspec:
+ raise GitLintUsageError("--commit and --commits are mutually exclusive, use one or the other.")
+
+ return GitContext.from_local_repository(lint_config.target, refspec=refspec, commit_hash=commit_hash)
def handle_gitlint_error(ctx, exc):
@@ -187,9 +196,10 @@ def handle_gitlint_error(ctx, exc):
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):
+ def __init__(self, config, config_builder, commit_hash, refspec, msg_filename, gitcontext=None):
self.config = config
self.config_builder = config_builder
+ self.commit_hash = commit_hash
self.refspec = refspec
self.msg_filename = msg_filename
self.gitcontext = gitcontext
@@ -205,6 +215,7 @@ class ContextObj:
@click.option('-c', multiple=True,
help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " +
"Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation
+@click.option('--commit', envvar='GITLINT_COMMIT', default=None, help="Hash (SHA) of specific commit to lint.")
@click.option('--commits', envvar='GITLINT_COMMITS', default=None, help="The range of commits to lint. [default: HEAD]")
@click.option('-e', '--extra-path', envvar='GITLINT_EXTRA_PATH',
help="Path to a directory or python module with extra user-defined rules",
@@ -217,6 +228,8 @@ class ContextObj:
help="Ignore any stdin data. Useful for running in CI server.")
@click.option('--staged', envvar='GITLINT_STAGED', is_flag=True,
help="Read staged commit meta-info from the local repository.")
+@click.option('--fail-without-commits', envvar='GITLINT_FAIL_WITHOUT_COMMITS', is_flag=True,
+ help="Hard fail when the target commit range is empty.")
@click.option('-v', '--verbose', envvar='GITLINT_VERBOSITY', count=True, default=0,
help="Verbosity, more v's for more verbose output (e.g.: -v, -vv, -vvv). [default: -vvv]", )
@click.option('-s', '--silent', envvar='GITLINT_SILENT', is_flag=True,
@@ -225,8 +238,9 @@ class ContextObj:
@click.version_option(version=gitlint.__version__)
@click.pass_context
def cli( # pylint: disable=too-many-arguments
- ctx, target, config, c, commits, extra_path, ignore, contrib,
- msg_filename, ignore_stdin, staged, verbose, silent, debug,
+ ctx, target, config, c, commit, commits, extra_path, ignore, contrib,
+ msg_filename, ignore_stdin, staged, fail_without_commits, verbose,
+ silent, debug,
):
""" Git lint tool, checks your git commit messages for styling issues
@@ -242,11 +256,11 @@ def cli( # pylint: disable=too-many-arguments
# Get the lint config from the commandline parameters and
# 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)
+ config, config_builder = build_config(target, config, c, extra_path, ignore, contrib, ignore_stdin, staged,
+ fail_without_commits, verbose, silent, debug)
LOG.debug("Configuration\n%s", config)
- ctx.obj = ContextObj(config, config_builder, commits, msg_filename)
+ ctx.obj = ContextObj(config, config_builder, commit, commits, msg_filename)
# If no subcommand is specified, then just lint
if ctx.invoked_subcommand is None:
@@ -262,9 +276,10 @@ def lint(ctx):
""" Lints a git repository [default command] """
lint_config = ctx.obj.config
refspec = ctx.obj.refspec
+ commit_hash = ctx.obj.commit_hash
msg_filename = ctx.obj.msg_filename
- gitcontext = build_git_context(lint_config, msg_filename, refspec)
+ gitcontext = build_git_context(lint_config, msg_filename, commit_hash, refspec)
# Set gitcontext in the click context, so we can use it in command that are ran after this
# in particular, this is used by run-hook
ctx.obj.gitcontext = gitcontext
@@ -273,17 +288,20 @@ def lint(ctx):
# Exit if we don't have commits in the specified range. Use a 0 exit code, since a popular use-case is one
# where users are using --commits in a check job to check the commit messages inside a CI job. By returning 0, we
# ensure that these jobs don't fail if for whatever reason the specified commit range is empty.
+ # This behavior can be overridden by using the --fail-without-commits flag.
if number_of_commits == 0:
- LOG.debug(u'No commits in range "%s"', refspec)
- ctx.exit(0)
+ LOG.debug('No commits in range "%s"', refspec)
+ if lint_config.fail_without_commits:
+ raise GitLintUsageError(f'No commits in range "{refspec}"')
+ ctx.exit(GITLINT_SUCCESS)
- LOG.debug(u'Linting %d commit(s)', number_of_commits)
+ LOG.debug('Linting %d commit(s)', number_of_commits)
general_config_builder = ctx.obj.config_builder
last_commit = gitcontext.commits[-1]
# Let's get linting!
first_violation = True
- exit_code = 0
+ exit_code = GITLINT_SUCCESS
for commit in gitcontext.commits:
# Build a config_builder taking into account the commit specific config (if any)
config_builder = general_config_builder.clone()
@@ -301,10 +319,8 @@ def lint(ctx):
if violations:
# Display the commit hash & new lines intelligently
if number_of_commits > 1 and commit.sha:
- linter.display.e("{0}Commit {1}:".format(
- "\n" if not first_violation or commit is last_commit else "",
- commit.sha[:10]
- ))
+ commit_separator = "\n" if not first_violation or commit is last_commit else ""
+ linter.display.e(f"{commit_separator}Commit {commit.sha[:10]}:")
linter.print_violations(violations)
first_violation = False
@@ -323,7 +339,7 @@ def install_hook(ctx):
hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
click.echo(f"Successfully installed gitlint commit-msg hook in {hook_path}")
- ctx.exit(0)
+ ctx.exit(GITLINT_SUCCESS)
except hooks.GitHookInstallerError as e:
click.echo(e, err=True)
ctx.exit(GIT_CONTEXT_ERROR_CODE)
@@ -337,7 +353,7 @@ def uninstall_hook(ctx):
hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
click.echo(f"Successfully uninstalled gitlint commit-msg hook from {hook_path}")
- ctx.exit(0)
+ ctx.exit(GITLINT_SUCCESS)
except hooks.GitHookInstallerError as e:
click.echo(e, err=True)
ctx.exit(GIT_CONTEXT_ERROR_CODE)
@@ -361,7 +377,7 @@ def run_hook(ctx):
sys.stdout.flush()
exit_code = e.exit_code
- if exit_code == 0:
+ if exit_code == GITLINT_SUCCESS:
click.echo("gitlint: " + click.style("OK", fg='green') + " (no violations in commit message)")
continue
@@ -387,7 +403,7 @@ def run_hook(ctx):
if value == "y":
LOG.debug("run-hook: commit message accepted")
- exit_code = 0
+ exit_code = GITLINT_SUCCESS
elif value == "e":
LOG.debug("run-hook: editing commit message")
msg_filename = ctx.obj.msg_filename
@@ -428,7 +444,7 @@ def generate_config(ctx):
LintConfigGenerator.generate_config(path)
click.echo(f"Successfully generated {path}")
- ctx.exit(0)
+ ctx.exit(GITLINT_SUCCESS)
# Let's Party!