From 23d0ac82f3d68663ddc74a0e1f9b963beb8d62b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 Dec 2021 04:31:49 +0100 Subject: Merging upstream version 0.17.0. Signed-off-by: Daniel Baumann --- .coveragerc | 2 +- .github/workflows/checks.yml | 10 +- .pre-commit-hooks.yaml | 1 + CHANGELOG.md | 6 + MANIFEST.in | 4 +- doc-requirements.txt | 2 +- docs/configuration.md | 2 +- docs/contributing.md | 6 +- docs/index.md | 2 +- docs/user_defined_rules.md | 4 +- examples/gitlint | 2 +- gitlint-core/LICENSE | 1 + gitlint-core/MANIFEST.in | 3 + gitlint-core/README.md | 1 + gitlint-core/gitlint/__init__.py | 1 + gitlint-core/gitlint/cache.py | 53 ++ gitlint-core/gitlint/cli.py | 454 +++++++++++++++ gitlint-core/gitlint/config.py | 528 ++++++++++++++++++ gitlint-core/gitlint/contrib/__init__.py | 0 gitlint-core/gitlint/contrib/rules/__init__.py | 0 .../gitlint/contrib/rules/conventional_commit.py | 37 ++ gitlint-core/gitlint/contrib/rules/signedoff_by.py | 18 + gitlint-core/gitlint/display.py | 36 ++ gitlint-core/gitlint/exception.py | 4 + gitlint-core/gitlint/files/commit-msg | 35 ++ gitlint-core/gitlint/files/gitlint | 134 +++++ gitlint-core/gitlint/git.py | 398 +++++++++++++ gitlint-core/gitlint/hooks.py | 63 +++ gitlint-core/gitlint/lint.py | 106 ++++ gitlint-core/gitlint/options.py | 149 +++++ gitlint-core/gitlint/rule_finder.py | 145 +++++ gitlint-core/gitlint/rules.py | 440 +++++++++++++++ gitlint-core/gitlint/shell.py | 77 +++ gitlint-core/gitlint/tests/__init__.py | 0 gitlint-core/gitlint/tests/base.py | 191 +++++++ gitlint-core/gitlint/tests/cli/test_cli.py | 593 ++++++++++++++++++++ gitlint-core/gitlint/tests/cli/test_cli_hooks.py | 281 ++++++++++ gitlint-core/gitlint/tests/config/test_config.py | 287 ++++++++++ .../gitlint/tests/config/test_config_builder.py | 264 +++++++++ .../gitlint/tests/config/test_config_precedence.py | 98 ++++ .../gitlint/tests/config/test_rule_collection.py | 64 +++ gitlint-core/gitlint/tests/contrib/__init__.py | 0 .../gitlint/tests/contrib/rules/__init__.py | 0 .../contrib/rules/test_conventional_commit.py | 75 +++ .../tests/contrib/rules/test_signedoff_by.py | 32 ++ .../gitlint/tests/contrib/test_contrib_rules.py | 69 +++ .../tests/expected/cli/test_cli/test_contrib_1 | 2 + .../tests/expected/cli/test_cli/test_debug_1 | 124 +++++ .../expected/cli/test_cli/test_input_stream_1 | 3 + .../cli/test_cli/test_input_stream_debug_1 | 3 + .../cli/test_cli/test_input_stream_debug_2 | 83 +++ .../tests/expected/cli/test_cli/test_lint_commit_1 | 2 + .../cli/test_cli/test_lint_multiple_commits_1 | 8 + .../test_cli/test_lint_multiple_commits_config_1 | 6 + .../cli/test_cli/test_lint_staged_msg_filename_1 | 2 + .../cli/test_cli/test_lint_staged_msg_filename_2 | 86 +++ .../expected/cli/test_cli/test_lint_staged_stdin_1 | 3 + .../expected/cli/test_cli/test_lint_staged_stdin_2 | 88 +++ .../tests/expected/cli/test_cli/test_named_rules_1 | 4 + .../tests/expected/cli/test_cli/test_named_rules_2 | 86 +++ .../cli/test_cli_hooks/test_hook_config_1_stderr | 2 + .../cli/test_cli_hooks/test_hook_config_1_stdout | 5 + .../cli/test_cli_hooks/test_hook_edit_1_stderr | 6 + .../cli/test_cli_hooks/test_hook_edit_1_stdout | 14 + .../test_cli_hooks/test_hook_local_commit_1_stderr | 2 + .../test_cli_hooks/test_hook_local_commit_1_stdout | 4 + .../cli/test_cli_hooks/test_hook_no_1_stderr | 2 + .../cli/test_cli_hooks/test_hook_no_1_stdout | 8 + .../cli/test_cli_hooks/test_hook_no_tty_1_stderr | 2 + .../cli/test_cli_hooks/test_hook_no_tty_1_stdout | 5 + .../test_hook_stdin_no_violations_1_stdout | 2 + .../test_hook_stdin_violations_1_stderr | 2 + .../test_hook_stdin_violations_1_stdout | 5 + .../cli/test_cli_hooks/test_hook_yes_1_stderr | 2 + .../cli/test_cli_hooks/test_hook_yes_1_stdout | 4 + .../cli/test_cli_hooks/test_run_hook_negative_1 | 2 + .../cli/test_cli_hooks/test_run_hook_negative_2 | 2 + gitlint-core/gitlint/tests/git/test_git.py | 108 ++++ gitlint-core/gitlint/tests/git/test_git_commit.py | 619 +++++++++++++++++++++ gitlint-core/gitlint/tests/git/test_git_context.py | 84 +++ gitlint-core/gitlint/tests/rules/__init__.py | 0 .../gitlint/tests/rules/test_body_rules.py | 236 ++++++++ .../tests/rules/test_configuration_rules.py | 140 +++++ .../gitlint/tests/rules/test_meta_rules.py | 59 ++ gitlint-core/gitlint/tests/rules/test_rules.py | 23 + .../gitlint/tests/rules/test_title_rules.py | 186 +++++++ .../gitlint/tests/rules/test_user_rules.py | 256 +++++++++ .../gitlint/tests/samples/commit_message/fixup | 1 + .../gitlint/tests/samples/commit_message/merge | 3 + .../tests/samples/commit_message/no-violations | 6 + .../gitlint/tests/samples/commit_message/revert | 3 + .../gitlint/tests/samples/commit_message/sample1 | 14 + .../gitlint/tests/samples/commit_message/sample2 | 1 + .../gitlint/tests/samples/commit_message/sample3 | 6 + .../gitlint/tests/samples/commit_message/sample4 | 7 + .../gitlint/tests/samples/commit_message/sample5 | 7 + .../gitlint/tests/samples/commit_message/squash | 3 + .../gitlint/tests/samples/config/gitlintconfig | 15 + .../tests/samples/config/invalid-option-value | 11 + .../gitlint/tests/samples/config/named-rules | 8 + .../gitlint/tests/samples/config/no-sections | 1 + .../samples/config/nonexisting-general-option | 13 + .../tests/samples/config/nonexisting-option | 11 + .../gitlint/tests/samples/config/nonexisting-rule | 11 + .../tests/samples/user_rules/bogus-file.txt | 2 + .../user_rules/import_exception/invalid_python.py | 3 + .../user_rules/incorrect_linerule/my_line_rule.py | 10 + .../tests/samples/user_rules/my_commit_rules.foo | 16 + .../tests/samples/user_rules/my_commit_rules.py | 26 + .../samples/user_rules/parent_package/__init__.py | 13 + .../user_rules/parent_package/my_commit_rules.py | 12 + gitlint-core/gitlint/tests/test_cache.py | 57 ++ gitlint-core/gitlint/tests/test_display.py | 63 +++ gitlint-core/gitlint/tests/test_hooks.py | 131 +++++ gitlint-core/gitlint/tests/test_lint.py | 277 +++++++++ gitlint-core/gitlint/tests/test_options.py | 224 ++++++++ gitlint-core/gitlint/tests/test_utils.py | 77 +++ gitlint-core/gitlint/utils.py | 81 +++ gitlint-core/setup.cfg | 2 + gitlint-core/setup.py | 107 ++++ gitlint/__init__.py | 1 - gitlint/cache.py | 53 -- gitlint/cli.py | 454 --------------- gitlint/config.py | 528 ------------------ gitlint/contrib/__init__.py | 0 gitlint/contrib/rules/__init__.py | 0 gitlint/contrib/rules/conventional_commit.py | 37 -- gitlint/contrib/rules/signedoff_by.py | 18 - gitlint/display.py | 36 -- gitlint/exception.py | 4 - gitlint/files/commit-msg | 35 -- gitlint/files/gitlint | 134 ----- gitlint/git.py | 398 ------------- gitlint/hooks.py | 63 --- gitlint/lint.py | 106 ---- gitlint/options.py | 149 ----- gitlint/rule_finder.py | 145 ----- gitlint/rules.py | 440 --------------- gitlint/shell.py | 77 --- gitlint/tests/__init__.py | 0 gitlint/tests/base.py | 191 ------- gitlint/tests/cli/test_cli.py | 593 -------------------- gitlint/tests/cli/test_cli_hooks.py | 281 ---------- gitlint/tests/config/test_config.py | 287 ---------- gitlint/tests/config/test_config_builder.py | 264 --------- gitlint/tests/config/test_config_precedence.py | 98 ---- gitlint/tests/config/test_rule_collection.py | 64 --- gitlint/tests/contrib/__init__.py | 0 gitlint/tests/contrib/rules/__init__.py | 0 .../contrib/rules/test_conventional_commit.py | 75 --- gitlint/tests/contrib/rules/test_signedoff_by.py | 32 -- gitlint/tests/contrib/test_contrib_rules.py | 69 --- gitlint/tests/expected/cli/test_cli/test_contrib_1 | 2 - gitlint/tests/expected/cli/test_cli/test_debug_1 | 124 ----- .../expected/cli/test_cli/test_input_stream_1 | 3 - .../cli/test_cli/test_input_stream_debug_1 | 3 - .../cli/test_cli/test_input_stream_debug_2 | 83 --- .../tests/expected/cli/test_cli/test_lint_commit_1 | 2 - .../cli/test_cli/test_lint_multiple_commits_1 | 8 - .../test_cli/test_lint_multiple_commits_config_1 | 6 - .../cli/test_cli/test_lint_staged_msg_filename_1 | 2 - .../cli/test_cli/test_lint_staged_msg_filename_2 | 86 --- .../expected/cli/test_cli/test_lint_staged_stdin_1 | 3 - .../expected/cli/test_cli/test_lint_staged_stdin_2 | 88 --- .../tests/expected/cli/test_cli/test_named_rules_1 | 4 - .../tests/expected/cli/test_cli/test_named_rules_2 | 86 --- .../cli/test_cli_hooks/test_hook_config_1_stderr | 2 - .../cli/test_cli_hooks/test_hook_config_1_stdout | 5 - .../cli/test_cli_hooks/test_hook_edit_1_stderr | 6 - .../cli/test_cli_hooks/test_hook_edit_1_stdout | 14 - .../test_cli_hooks/test_hook_local_commit_1_stderr | 2 - .../test_cli_hooks/test_hook_local_commit_1_stdout | 4 - .../cli/test_cli_hooks/test_hook_no_1_stderr | 2 - .../cli/test_cli_hooks/test_hook_no_1_stdout | 8 - .../cli/test_cli_hooks/test_hook_no_tty_1_stderr | 2 - .../cli/test_cli_hooks/test_hook_no_tty_1_stdout | 5 - .../test_hook_stdin_no_violations_1_stdout | 2 - .../test_hook_stdin_violations_1_stderr | 2 - .../test_hook_stdin_violations_1_stdout | 5 - .../cli/test_cli_hooks/test_hook_yes_1_stderr | 2 - .../cli/test_cli_hooks/test_hook_yes_1_stdout | 4 - .../cli/test_cli_hooks/test_run_hook_negative_1 | 2 - .../cli/test_cli_hooks/test_run_hook_negative_2 | 2 - gitlint/tests/git/test_git.py | 108 ---- gitlint/tests/git/test_git_commit.py | 619 --------------------- gitlint/tests/git/test_git_context.py | 84 --- gitlint/tests/rules/__init__.py | 0 gitlint/tests/rules/test_body_rules.py | 236 -------- gitlint/tests/rules/test_configuration_rules.py | 140 ----- gitlint/tests/rules/test_meta_rules.py | 59 -- gitlint/tests/rules/test_rules.py | 23 - gitlint/tests/rules/test_title_rules.py | 186 ------- gitlint/tests/rules/test_user_rules.py | 256 --------- gitlint/tests/samples/commit_message/fixup | 1 - gitlint/tests/samples/commit_message/merge | 3 - gitlint/tests/samples/commit_message/no-violations | 6 - gitlint/tests/samples/commit_message/revert | 3 - gitlint/tests/samples/commit_message/sample1 | 14 - gitlint/tests/samples/commit_message/sample2 | 1 - gitlint/tests/samples/commit_message/sample3 | 6 - gitlint/tests/samples/commit_message/sample4 | 7 - gitlint/tests/samples/commit_message/sample5 | 7 - gitlint/tests/samples/commit_message/squash | 3 - gitlint/tests/samples/config/gitlintconfig | 15 - gitlint/tests/samples/config/invalid-option-value | 11 - gitlint/tests/samples/config/named-rules | 8 - gitlint/tests/samples/config/no-sections | 1 - .../samples/config/nonexisting-general-option | 13 - gitlint/tests/samples/config/nonexisting-option | 11 - gitlint/tests/samples/config/nonexisting-rule | 11 - gitlint/tests/samples/user_rules/bogus-file.txt | 2 - .../user_rules/import_exception/invalid_python.py | 3 - .../user_rules/incorrect_linerule/my_line_rule.py | 10 - .../tests/samples/user_rules/my_commit_rules.foo | 16 - .../tests/samples/user_rules/my_commit_rules.py | 26 - .../samples/user_rules/parent_package/__init__.py | 13 - .../user_rules/parent_package/my_commit_rules.py | 12 - gitlint/tests/test_cache.py | 57 -- gitlint/tests/test_display.py | 63 --- gitlint/tests/test_hooks.py | 131 ----- gitlint/tests/test_lint.py | 277 --------- gitlint/tests/test_options.py | 224 -------- gitlint/tests/test_utils.py | 77 --- gitlint/utils.py | 81 --- qa/requirements.txt | 2 +- qa/shell.py | 2 +- qa/test_stdin.py | 2 +- requirements.txt | 5 +- run_tests.sh | 20 +- setup.py | 50 +- test-requirements.txt | 8 +- 231 files changed, 8185 insertions(+), 8109 deletions(-) create mode 120000 gitlint-core/LICENSE create mode 100644 gitlint-core/MANIFEST.in create mode 120000 gitlint-core/README.md create mode 100644 gitlint-core/gitlint/__init__.py create mode 100644 gitlint-core/gitlint/cache.py create mode 100644 gitlint-core/gitlint/cli.py create mode 100644 gitlint-core/gitlint/config.py create mode 100644 gitlint-core/gitlint/contrib/__init__.py create mode 100644 gitlint-core/gitlint/contrib/rules/__init__.py create mode 100644 gitlint-core/gitlint/contrib/rules/conventional_commit.py create mode 100644 gitlint-core/gitlint/contrib/rules/signedoff_by.py create mode 100644 gitlint-core/gitlint/display.py create mode 100644 gitlint-core/gitlint/exception.py create mode 100644 gitlint-core/gitlint/files/commit-msg create mode 100644 gitlint-core/gitlint/files/gitlint create mode 100644 gitlint-core/gitlint/git.py create mode 100644 gitlint-core/gitlint/hooks.py create mode 100644 gitlint-core/gitlint/lint.py create mode 100644 gitlint-core/gitlint/options.py create mode 100644 gitlint-core/gitlint/rule_finder.py create mode 100644 gitlint-core/gitlint/rules.py create mode 100644 gitlint-core/gitlint/shell.py create mode 100644 gitlint-core/gitlint/tests/__init__.py create mode 100644 gitlint-core/gitlint/tests/base.py create mode 100644 gitlint-core/gitlint/tests/cli/test_cli.py create mode 100644 gitlint-core/gitlint/tests/cli/test_cli_hooks.py create mode 100644 gitlint-core/gitlint/tests/config/test_config.py create mode 100644 gitlint-core/gitlint/tests/config/test_config_builder.py create mode 100644 gitlint-core/gitlint/tests/config/test_config_precedence.py create mode 100644 gitlint-core/gitlint/tests/config/test_rule_collection.py create mode 100644 gitlint-core/gitlint/tests/contrib/__init__.py create mode 100644 gitlint-core/gitlint/tests/contrib/rules/__init__.py create mode 100644 gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py create mode 100644 gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py create mode 100644 gitlint-core/gitlint/tests/contrib/test_contrib_rules.py create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_contrib_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_debug_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_commit_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_config_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli/test_named_rules_2 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 create mode 100644 gitlint-core/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 create mode 100644 gitlint-core/gitlint/tests/git/test_git.py create mode 100644 gitlint-core/gitlint/tests/git/test_git_commit.py create mode 100644 gitlint-core/gitlint/tests/git/test_git_context.py create mode 100644 gitlint-core/gitlint/tests/rules/__init__.py create mode 100644 gitlint-core/gitlint/tests/rules/test_body_rules.py create mode 100644 gitlint-core/gitlint/tests/rules/test_configuration_rules.py create mode 100644 gitlint-core/gitlint/tests/rules/test_meta_rules.py create mode 100644 gitlint-core/gitlint/tests/rules/test_rules.py create mode 100644 gitlint-core/gitlint/tests/rules/test_title_rules.py create mode 100644 gitlint-core/gitlint/tests/rules/test_user_rules.py create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/fixup create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/merge create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/no-violations create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/revert create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/sample1 create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/sample2 create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/sample3 create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/sample4 create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/sample5 create mode 100644 gitlint-core/gitlint/tests/samples/commit_message/squash create mode 100644 gitlint-core/gitlint/tests/samples/config/gitlintconfig create mode 100644 gitlint-core/gitlint/tests/samples/config/invalid-option-value create mode 100644 gitlint-core/gitlint/tests/samples/config/named-rules create mode 100644 gitlint-core/gitlint/tests/samples/config/no-sections create mode 100644 gitlint-core/gitlint/tests/samples/config/nonexisting-general-option create mode 100644 gitlint-core/gitlint/tests/samples/config/nonexisting-option create mode 100644 gitlint-core/gitlint/tests/samples/config/nonexisting-rule create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/bogus-file.txt create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/import_exception/invalid_python.py create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/incorrect_linerule/my_line_rule.py create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.foo create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py create mode 100644 gitlint-core/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py create mode 100644 gitlint-core/gitlint/tests/test_cache.py create mode 100644 gitlint-core/gitlint/tests/test_display.py create mode 100644 gitlint-core/gitlint/tests/test_hooks.py create mode 100644 gitlint-core/gitlint/tests/test_lint.py create mode 100644 gitlint-core/gitlint/tests/test_options.py create mode 100644 gitlint-core/gitlint/tests/test_utils.py create mode 100644 gitlint-core/gitlint/utils.py create mode 100644 gitlint-core/setup.cfg create mode 100644 gitlint-core/setup.py delete mode 100644 gitlint/__init__.py delete mode 100644 gitlint/cache.py delete mode 100644 gitlint/cli.py delete mode 100644 gitlint/config.py delete mode 100644 gitlint/contrib/__init__.py delete mode 100644 gitlint/contrib/rules/__init__.py delete mode 100644 gitlint/contrib/rules/conventional_commit.py delete mode 100644 gitlint/contrib/rules/signedoff_by.py delete mode 100644 gitlint/display.py delete mode 100644 gitlint/exception.py delete mode 100644 gitlint/files/commit-msg delete mode 100644 gitlint/files/gitlint delete mode 100644 gitlint/git.py delete mode 100644 gitlint/hooks.py delete mode 100644 gitlint/lint.py delete mode 100644 gitlint/options.py delete mode 100644 gitlint/rule_finder.py delete mode 100644 gitlint/rules.py delete mode 100644 gitlint/shell.py delete mode 100644 gitlint/tests/__init__.py delete mode 100644 gitlint/tests/base.py delete mode 100644 gitlint/tests/cli/test_cli.py delete mode 100644 gitlint/tests/cli/test_cli_hooks.py delete mode 100644 gitlint/tests/config/test_config.py delete mode 100644 gitlint/tests/config/test_config_builder.py delete mode 100644 gitlint/tests/config/test_config_precedence.py delete mode 100644 gitlint/tests/config/test_rule_collection.py delete mode 100644 gitlint/tests/contrib/__init__.py delete mode 100644 gitlint/tests/contrib/rules/__init__.py delete mode 100644 gitlint/tests/contrib/rules/test_conventional_commit.py delete mode 100644 gitlint/tests/contrib/rules/test_signedoff_by.py delete mode 100644 gitlint/tests/contrib/test_contrib_rules.py delete mode 100644 gitlint/tests/expected/cli/test_cli/test_contrib_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_debug_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_input_stream_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_input_stream_debug_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_commit_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_config_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_named_rules_1 delete mode 100644 gitlint/tests/expected/cli/test_cli/test_named_rules_2 delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 delete mode 100644 gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 delete mode 100644 gitlint/tests/git/test_git.py delete mode 100644 gitlint/tests/git/test_git_commit.py delete mode 100644 gitlint/tests/git/test_git_context.py delete mode 100644 gitlint/tests/rules/__init__.py delete mode 100644 gitlint/tests/rules/test_body_rules.py delete mode 100644 gitlint/tests/rules/test_configuration_rules.py delete mode 100644 gitlint/tests/rules/test_meta_rules.py delete mode 100644 gitlint/tests/rules/test_rules.py delete mode 100644 gitlint/tests/rules/test_title_rules.py delete mode 100644 gitlint/tests/rules/test_user_rules.py delete mode 100644 gitlint/tests/samples/commit_message/fixup delete mode 100644 gitlint/tests/samples/commit_message/merge delete mode 100644 gitlint/tests/samples/commit_message/no-violations delete mode 100644 gitlint/tests/samples/commit_message/revert delete mode 100644 gitlint/tests/samples/commit_message/sample1 delete mode 100644 gitlint/tests/samples/commit_message/sample2 delete mode 100644 gitlint/tests/samples/commit_message/sample3 delete mode 100644 gitlint/tests/samples/commit_message/sample4 delete mode 100644 gitlint/tests/samples/commit_message/sample5 delete mode 100644 gitlint/tests/samples/commit_message/squash delete mode 100644 gitlint/tests/samples/config/gitlintconfig delete mode 100644 gitlint/tests/samples/config/invalid-option-value delete mode 100644 gitlint/tests/samples/config/named-rules delete mode 100644 gitlint/tests/samples/config/no-sections delete mode 100644 gitlint/tests/samples/config/nonexisting-general-option delete mode 100644 gitlint/tests/samples/config/nonexisting-option delete mode 100644 gitlint/tests/samples/config/nonexisting-rule delete mode 100644 gitlint/tests/samples/user_rules/bogus-file.txt delete mode 100644 gitlint/tests/samples/user_rules/import_exception/invalid_python.py delete mode 100644 gitlint/tests/samples/user_rules/incorrect_linerule/my_line_rule.py delete mode 100644 gitlint/tests/samples/user_rules/my_commit_rules.foo delete mode 100644 gitlint/tests/samples/user_rules/my_commit_rules.py delete mode 100644 gitlint/tests/samples/user_rules/parent_package/__init__.py delete mode 100644 gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py delete mode 100644 gitlint/tests/test_cache.py delete mode 100644 gitlint/tests/test_display.py delete mode 100644 gitlint/tests/test_hooks.py delete mode 100644 gitlint/tests/test_lint.py delete mode 100644 gitlint/tests/test_options.py delete mode 100644 gitlint/tests/test_utils.py delete mode 100644 gitlint/utils.py diff --git a/.coveragerc b/.coveragerc index a120715..9da615c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,4 +3,4 @@ fail_under = 97 [run] branch = true -omit=*dist-packages*,*site-packages*,gitlint/tests/*,.venv/*,*virtualenv* +omit=*dist-packages*,*site-packages*,gitlint-core/gitlint/tests/*,.venv/*,*virtualenv* diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8fbda21..1ca2a9c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -109,13 +109,13 @@ jobs: run: gitlint --version - name: Tests (sanity) - run: tools\windows\run_tests.bat "gitlint\tests\cli\test_cli.py::CLITests::test_lint" + run: tools\windows\run_tests.bat "gitlint-core\gitlint\tests\cli\test_cli.py::CLITests::test_lint" - name: Tests (ignore cli\*) - run: pytest --ignore gitlint\tests\cli -rw -s gitlint + run: pytest --ignore gitlint-core\gitlint\tests\cli -rw -s gitlint-core - name: Tests (test_cli.py only - continue-on-error:true) - run: tools\windows\run_tests.bat "gitlint\tests\cli\test_cli.py" + run: tools\windows\run_tests.bat "gitlint-core\gitlint\tests\cli\test_cli.py" continue-on-error: true # Known to fail at this point - name: Tests (all - continue-on-error:true) @@ -127,10 +127,10 @@ jobs: continue-on-error: true # Known to fail at this point - name: PEP8 - run: flake8 gitlint qa examples + run: flake8 gitlint-core qa examples - name: PyLint - run: pylint gitlint qa --rcfile=".pylintrc" -r n + run: pylint gitlint-core\gitlint qa --rcfile=".pylintrc" -r n # Re-add git version control so we can run gitlint on itself. - name: Re-add git version control to code diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 4b15bfd..f2ccc7f 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,6 +1,7 @@ - id: gitlint name: gitlint language: python + additional_dependencies: ["./gitlint-core[trusted-deps]"] entry: gitlint args: [--staged, --msg-filename] stages: [commit-msg] diff --git a/CHANGELOG.md b/CHANGELOG.md index dd224e3..2546579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog # +## v0.17.0 (2021-11-28) ## +Contributors: +Special thanks to all contributors for this release, in particular [andersk](https://github.com/andersk) and [sigmavirus24](https://github.com/sigmavirus24). + +- Gitlint is now split in 2 packages: `gitlint` and `gitlint-core`. This allows users to install gitlint without pinned dependencies (which is the default) ([#162](https://github.com/jorisroovers/gitlint/issues/162)) +- Under-the-hood: dependencies updated ## v0.16.0 (2021-10-08) ## Contributors: diff --git a/MANIFEST.in b/MANIFEST.in index 51a5598..ad3da8a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,5 +3,5 @@ include LICENSE exclude Vagrantfile exclude *.yml *.sh *.txt recursive-exclude examples * -recursive-exclude gitlint/tests * -recursive-exclude qa * \ No newline at end of file +recursive-exclude gitlint-core * +recursive-exclude qa * diff --git a/doc-requirements.txt b/doc-requirements.txt index becd4f4..33ce51e 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -1 +1 @@ -mkdocs==1.2.2 \ No newline at end of file +mkdocs==1.2.3 \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index 226ba8a..addf0c0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -108,7 +108,7 @@ ignore-merge-commits=false # This is useful for when developers often erroneously edit certain files or git submodules. # By specifying this rule, developers can only change the file when they explicitly # reference it in the commit message. -files=gitlint/rules.py,README.md +files=gitlint-core/gitlint/rules.py,README.md [body-match-regex] # python-style regex that the commit-msg body must match. diff --git a/docs/contributing.md b/docs/contributing.md index d39f9e1..1002676 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -13,7 +13,7 @@ that's open to a lot of change and input. When contributing code, please consider all the parts that are typically required: -- [Unit tests](https://github.com/jorisroovers/gitlint/tree/main/gitlint/tests) (automatically +- [Unit tests](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/tests) (automatically [enforced by CI](https://github.com/jorisroovers/gitlint/actions)). Please consider writing new ones for your functionality, not only updating existing ones to make the build pass. - [Integration tests](https://github.com/jorisroovers/gitlint/tree/main/qa) (also automatically @@ -118,8 +118,8 @@ Then, we suggest taking the following approach to add a Contrib rule: 1. **Write your rule as a [user-defined rule](../user_defined_rules)**. In terms of code, Contrib rules are identical to user-defined rules, they just happen to have their code sit within the gitlint codebase itself. -2. **Add your user-defined rule to gitlint**. You should put your file(s) in the [gitlint/contrib/rules](https://github.com/jorisroovers/gitlint/tree/main/gitlint/contrib/rules) directory. -3. **Write unit tests**. The gitlint codebase contains [Contrib rule test files you can copy and modify](https://github.com/jorisroovers/gitlint/tree/main/gitlint/tests/contrib/rules). +2. **Add your user-defined rule to gitlint**. You should put your file(s) in the [gitlint/contrib/rules](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/contrib/rules) directory. +3. **Write unit tests**. The gitlint codebase contains [Contrib rule test files you can copy and modify](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/tests/contrib/rules). 4. **Write documentation**. In particular, you should update the [gitlint/docs/contrib_rules.md](https://github.com/jorisroovers/gitlint/blob/main/docs/contrib_rules.md) file with details on your Contrib rule. 5. **Create a Pull Request**: code review typically requires a bit of back and forth. Thanks for your contribution! diff --git a/docs/index.md b/docs/index.md index 98b72de..398b4e5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -315,7 +315,7 @@ general `ignore-merge-commits`, `ignore-revert-commits`, `ignore-fixup-commits` You can configure gitlint to ignore specific commits or parts of a commit. -One way to do this, is to by [adding a gitline-ignore line to your commit message](configuration.md#commit-specific-config). +One way to do this, is by [adding a gitlint-ignore line to your commit message](configuration.md#commit-specific-config). If you have a case where you want to ignore a certain type of commits all-together, you can use gitlint's *ignore* rules. diff --git a/docs/user_defined_rules.md b/docs/user_defined_rules.md index 3b9f5e7..f58dcc7 100644 --- a/docs/user_defined_rules.md +++ b/docs/user_defined_rules.md @@ -374,7 +374,7 @@ class ReleaseConfigurationRule(ConfigurationRule): ``` For all available properties and methods on the `config` object, have a look at the -[LintConfig class](https://github.com/jorisroovers/gitlint/blob/main/gitlint/config.py). Please do not use any +[LintConfig class](https://github.com/jorisroovers/gitlint/blob/main/gitlint-core/gitlint/config.py). Please do not use any properties or methods starting with an underscore, as those are subject to change. @@ -385,7 +385,7 @@ As long as you stick with simple rules that are similar to the sample user-defin should be able to discover and execute them. While clearly you can run any python code you want in your rules, you might run into some issues if you don't follow the conventions that gitlint requires. -While the [rule finding source-code](https://github.com/jorisroovers/gitlint/blob/main/gitlint/rule_finder.py) is the +While the [rule finding source-code](https://github.com/jorisroovers/gitlint/blob/main/gitlint-core/gitlint/rule_finder.py) is the ultimate source of truth, here are some of the requirements that gitlint enforces. ### Rule class requirements diff --git a/examples/gitlint b/examples/gitlint index b722023..0261752 100644 --- a/examples/gitlint +++ b/examples/gitlint @@ -55,4 +55,4 @@ ignore-merge-commits=false # This is useful for when developers often erroneously edit certain files or git submodules. # By specifying this rule, developers can only change the file when they explicitly reference # it in the commit message. -files=gitlint/rules.py,README.md +files=gitlint-core/gitlint/rules.py,README.md diff --git a/gitlint-core/LICENSE b/gitlint-core/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/gitlint-core/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/gitlint-core/MANIFEST.in b/gitlint-core/MANIFEST.in new file mode 100644 index 0000000..375cec1 --- /dev/null +++ b/gitlint-core/MANIFEST.in @@ -0,0 +1,3 @@ +include README.md +include LICENSE +recursive-exclude gitlint/tests * diff --git a/gitlint-core/README.md b/gitlint-core/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/gitlint-core/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/gitlint-core/gitlint/__init__.py b/gitlint-core/gitlint/__init__.py new file mode 100644 index 0000000..fd86b3e --- /dev/null +++ b/gitlint-core/gitlint/__init__.py @@ -0,0 +1 @@ +__version__ = "0.17.0" diff --git a/gitlint-core/gitlint/cache.py b/gitlint-core/gitlint/cache.py new file mode 100644 index 0000000..1b6558f --- /dev/null +++ b/gitlint-core/gitlint/cache.py @@ -0,0 +1,53 @@ +class PropertyCache: + """ Mixin class providing a simple cache. """ + + def __init__(self): + self._cache = {} + + def _try_cache(self, cache_key, cache_populate_func): + """ Tries to get a value from the cache identified by `cache_key`. + If no value is found in the cache, do a function call to `cache_populate_func` to populate the cache + and then return the value from the cache. """ + if cache_key not in self._cache: + cache_populate_func() + return self._cache[cache_key] + + +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: + # Use function name as cache key + @cache + def myfunc(args): + ... + + # Specify cache key + @cache(cachekey="foobar") + def myfunc(args): + ... + """ + + # Decorators with optional arguments are a bit convoluted in python, see some of the links below for details. + + def cache_decorator(func): + # 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[cachekey] = func(*args) + return args[0]._try_cache(cachekey, cache_func_result) + + return wrapped + + # 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: + return cache_decorator(original_func) + + return cache_decorator diff --git a/gitlint-core/gitlint/cli.py b/gitlint-core/gitlint/cli.py new file mode 100644 index 0000000..19676b3 --- /dev/null +++ b/gitlint-core/gitlint/cli.py @@ -0,0 +1,454 @@ +# pylint: disable=bad-option-value,wrong-import-position +# We need to disable the import position checks because of the windows check that we need to do below +import copy +import logging +import os +import platform +import stat +import sys +import click + +import gitlint +from gitlint.lint import GitLinter +from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator +from gitlint.git import GitContext, GitContextError, git_version +from gitlint import hooks +from gitlint.shell import shell +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 +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 ) +DEFAULT_COMMIT_MSG_EDITOR = "vim -n" + +# Since we use the return code to denote the amount of errors, we need to change the default click usage error code +click.UsageError.exit_code = USAGE_ERROR_CODE + +# We don't use logging.getLogger(__main__) here because that will cause DEBUG output to be lost +# when invoking gitlint as a python module (python -m gitlint.cli) +LOG = logging.getLogger("gitlint.cli") + + +class GitLintUsageError(GitlintError): + """ Exception indicating there is an issue with how gitlint is used. """ + pass + + +def setup_logging(): + """ Setup gitlint logging """ + root_log = logging.getLogger("gitlint") + root_log.propagate = False # Don't propagate to child loggers, the gitlint root logger handles everything + handler = logging.StreamHandler() + formatter = logging.Formatter(LOG_FORMAT) + handler.setFormatter(formatter) + root_log.addHandler(handler) + root_log.setLevel(logging.ERROR) + + +def log_system_info(): + LOG.debug("Platform: %s", platform.platform()) + LOG.debug("Python version: %s", sys.version) + LOG.debug("Git version: %s", git_version()) + LOG.debug("Gitlint version: %s", gitlint.__version__) + LOG.debug("GITLINT_USE_SH_LIB: %s", os.environ.get("GITLINT_USE_SH_LIB", "[NOT SET]")) + LOG.debug("DEFAULT_ENCODING: %s", gitlint.utils.DEFAULT_ENCODING) + + +def build_config( # pylint: disable=too-many-arguments + 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() + # Config precedence: + # First, load default config or config from configfile + if config_path: + config_builder.set_from_config_file(config_path) + elif os.path.exists(DEFAULT_CONFIG_FILE): + config_builder.set_from_config_file(DEFAULT_CONFIG_FILE) + + # Then process any commandline configuration flags + config_builder.set_config_from_string_list(c) + + # Finally, overwrite with any convenience commandline flags + if ignore: + config_builder.set_option('general', 'ignore', ignore) + + if contrib: + config_builder.set_option('general', 'contrib', contrib) + + if ignore_stdin: + config_builder.set_option('general', 'ignore-stdin', ignore_stdin) + + if silent: + config_builder.set_option('general', 'verbosity', 0) + elif verbose > 0: + config_builder.set_option('general', 'verbosity', verbose) + + if extra_path: + config_builder.set_option('general', 'extra-path', extra_path) + + if target: + config_builder.set_option('general', 'target', target) + + if debug: + config_builder.set_option('general', 'debug', debug) + + 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 + + +def get_stdin_data(): + """ Helper function that returns data send to stdin or False if nothing is send """ + # STDIN can only be 3 different types of things ("modes") + # 1. An interactive terminal device (i.e. a TTY -> sys.stdin.isatty() or stat.S_ISCHR) + # 2. A (named) pipe (stat.S_ISFIFO) + # 3. A regular file (stat.S_ISREG) + # Technically, STDIN can also be other device type like a named unix socket (stat.S_ISSOCK), but we don't + # support that in gitlint (at least not today). + # + # Now, the behavior that we want is the following: + # If someone sends something directly to gitlint via a pipe or a regular file, read it. If not, read from the + # local repository. + # Note that we don't care about whether STDIN is a TTY or not, we only care whether data is via a pipe or regular + # file. + # However, in case STDIN is not a TTY, it HAS to be one of the 2 other things (pipe or regular file), even if + # no-one is actually sending anything to gitlint over them. In this case, we still want to read from the local + # repository. + # To support this use-case (which is common in CI runners such as Jenkins and Gitlab), we need to actually attempt + # to read from STDIN in case it's a pipe or regular file. In case that fails, then we'll fall back to reading + # from the local repo. + + mode = os.fstat(sys.stdin.fileno()).st_mode + stdin_is_pipe_or_file = stat.S_ISFIFO(mode) or stat.S_ISREG(mode) + if stdin_is_pipe_or_file: + input_data = sys.stdin.read() + # Only return the input data if there's actually something passed + # i.e. don't consider empty piped data + if input_data: + return str(input_data) + return False + + +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 + from_commit_msg = GitContext.from_commit_msg + if lint_config.staged: + LOG.debug("Fetching additional meta-data from staged commit") + from_commit_msg = lambda message: GitContext.from_staged_commit(message, lint_config.target) # noqa + + # Order of precedence: + # 1. Any data specified via --msg-filename + if msg_filename: + LOG.debug("Using --msg-filename.") + 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: + stdin_input = get_stdin_data() + if stdin_input: + LOG.debug("Stdin data: '%s'", stdin_input) + LOG.debug("Stdin detected and not ignored. Using as input.") + return from_commit_msg(stdin_input) + + if lint_config.staged: + 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.") + + 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): + """ 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, 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 + + +@click.group(invoke_without_command=True, context_settings={'max_content_width': 120}, + epilog="When no COMMAND is specified, gitlint defaults to 'gitlint lint'.") +@click.option('--target', envvar='GITLINT_TARGET', + 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=f"Config file location [default: {DEFAULT_CONFIG_FILE}]") +@click.option('-c', multiple=True, + help="Config flags in format .