def test_lint_sample3(self): linter = GitLinter(LintConfig()) gitcontext = GitContext() gitcontext.set_commit_msg(self.get_sample("commit_message/sample3")) violations = linter.lint(gitcontext) title = " Commit title containing 'WIP', \tleading and trailing 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), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1), 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)", "This is the first line is meant 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", "This line has a trailing tab.\t", 5), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5), ] self.assertListEqual(violations, expected)
def test_lint_regex_rules(self): """ Additional test for title-match-regex, body-match-regex """ commit = self.gitcommit( self.get_sample("commit_message/no-violations")) lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # No violations by default 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: (.*)$")] for rule_regex in rule_regexes: lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1]) violations = linter.lint(commit) self.assertListEqual(violations, []) # 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) ] violations = linter.lint(commit) self.assertListEqual(violations, expected_violations)
def test_lint_sample1(self): linter = GitLinter(LintConfig()) gitcontext = GitContext() gitcontext.set_commit_msg(self.get_sample("commit_message/sample1")) violations = linter.lint(gitcontext) expected_errors = [ RuleViolation( "T3", "Title has trailing punctuation (.)", "Commit title containing 'WIP', as well as trailing punctuation.", 1, ), RuleViolation( "T5", "Title contains the word 'WIP' (case-insensitive)", "Commit title containing '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", "This line has a trailing 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), ] self.assertListEqual(violations, expected_errors)
def test_lint_sample5(self): gitcontext = self.gitcontext(self.get_sample("commit_message/sample5")) lintconfig = LintConfig() lintconfig.apply_config_from_commit(gitcontext.commits[-1]) linter = GitLinter(lintconfig) violations = linter.lint(gitcontext.commits[-1], gitcontext) title = " Commit title containing 'WIP', \tleading and trailing whitespace and longer than 72 characters." # expect only certain violations because sample5 has a 'gitlint: T3,' 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", "This line should be empty", 2), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing 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) ] self.assertListEqual(violations, expected)
def test_lint_sample1(self): linter = GitLinter(LintConfig()) 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), RuleViolation( "T5", "Title contains the word 'WIP' (case-insensitive)", u"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 trailing tab.\t", 5), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5) ] self.assertListEqual(violations, expected_errors)
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"]: 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"]) 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")))
def test_lint_sample5(self): commit = self.gitcommit(self.get_sample("commit_message/sample5")) config_builder = LintConfigBuilder() config_builder.set_config_from_commit(commit) 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." # 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("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) ] self.assertListEqual(violations, expected)
def test_lint_sample3(self): linter = GitLinter(LintConfig()) 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." 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), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1), 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 " + "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("B3", "Line contains hard tab characters (\\t)", u"This line has a tråiling tab.\t", 5) ] self.assertListEqual(violations, expected)
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj[0] try: if sys.stdin.isatty(): # If target has not been set explicitly before, fallback to the current directory gitcontext = GitContext.from_local_repository(lint_config.target) else: gitcontext = GitContext.from_commit_msg(sys.stdin.read()) except GitContextError as e: click.echo(str(e)) ctx.exit(GIT_CONTEXT_ERROR_CODE) config_builder = ctx.obj[1] last_commit = gitcontext.commits[-1] # Apply an additional config that is specified in the last commit message config_builder.set_config_from_commit(last_commit) lint_config = config_builder.build(lint_config) # Let's get linting! linter = GitLinter(lint_config) violations = linter.lint(last_commit) linter.print_violations(violations) exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations)) ctx.exit(exit_code)
def cli(config, c, ignore, verbose, silent): """ Git lint tool, checks your git commit messages for styling issues """ try: # Config precedence: # First, load default config or config from configfile lint_config = get_lint_config(config) # Then process any commandline configuration flags try: lint_config.apply_config_options(c) except LintConfigError as e: click.echo("Config Error: {}".format(e.message)) exit(CONFIG_ERROR_CODE) # Finally, overwrite with any convenience commandline flags lint_config.apply_on_csv_string(ignore, lint_config.disable_rule) if silent: lint_config.verbosity = 0 elif verbose > 0: lint_config.verbosity = verbose except LintConfigError as e: click.echo("Config Error: {0}".format(e.message)) exit(CONFIG_ERROR_CODE) # return 10000 on config error if sys.stdin.isatty(): gitcontext = GitContext.from_environment() else: gitcontext = GitContext() gitcontext.set_commit_msg(sys.stdin.read()) linter = GitLinter(lint_config) violations = linter.lint(gitcontext) linter.print_violations(violations) exit(len(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) ] linter = GitLinter(LintConfig()) # test output with increasing verbosity with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 0 linter.print_violations(violations) self.assertEqual("", stderr.getvalue()) 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" 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" 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" self.assertEqual(expected, stderr.getvalue())
def test_lint_sample2(self): linter = GitLinter(LintConfig()) 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), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected)
def test_lint_sample2(self): linter = GitLinter(LintConfig()) 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)", "Just a title containing WIP", 1), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected)
def test_lint_sample4(self): gitcontext = self.gitcontext(self.get_sample("commit_message/sample4")) lintconfig = LintConfig() lintconfig.apply_config_from_commit(gitcontext.commits[-1]) linter = GitLinter(lintconfig) violations = linter.lint(gitcontext.commits[-1], gitcontext) # expect no violations because sample4 has a 'gitlint: disable line' expected = [] self.assertListEqual(violations, expected)
def test_lint_sample4(self): commit = self.gitcommit(self.get_sample("commit_message/sample4")) config_builder = LintConfigBuilder() config_builder.set_config_from_commit(commit) linter = GitLinter(config_builder.build()) violations = linter.lint(commit) # expect no violations because sample4 has a 'gitlint: disable line' expected = [] self.assertListEqual(violations, expected)
def test_print_violations(self): violations = [RuleViolation("RULE_ID_1", "Error Message 1", "Violating Content 1", 1), RuleViolation("RULE_ID_2", "Error Message 2", "Violating Content 2", 2)] linter = GitLinter(LintConfig()) # test output with increasing verbosity with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 0 linter.print_violations(violations) self.assertEqual("", stderr.getvalue()) with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 1 linter.print_violations(violations) expected = "1: 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 = "1: RULE_ID_1 Error Message 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 = "1: RULE_ID_1 Error Message 1: \"Violating Content 1\"\n" + \ "2: RULE_ID_2 Error Message 2: \"Violating Content 2\"\n" self.assertEqual(expected, stderr.getvalue())
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj.config refspec = ctx.obj.refspec msg_filename = ctx.obj.msg_filename gitcontext = build_git_context(lint_config, msg_filename, 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 number_of_commits = len(gitcontext.commits) # 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. if number_of_commits == 0: LOG.debug(u'No commits in range "%s"', refspec) ctx.exit(0) LOG.debug(u'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 for commit in gitcontext.commits: # Build a config_builder taking into account the commit specific config (if any) config_builder = general_config_builder.clone() config_builder.set_config_from_commit(commit) # Create a deepcopy from the original config, so we have a unique config object per commit # This is important for configuration rules to be able to modifying the config on a per commit basis commit_config = config_builder.build(copy.deepcopy(lint_config)) # Actually do the linting linter = GitLinter(commit_config) violations = linter.lint(commit) # exit code equals the total number of violations in all commits exit_code += len(violations) 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])) linter.print_violations(violations) first_violation = False # cap actual max exit code because bash doesn't like exit codes larger than 255: # http://tldp.org/LDP/abs/html/exitcodes.html exit_code = min(MAX_VIOLATION_ERROR_CODE, exit_code) LOG.debug("Exit Code = %s", exit_code) ctx.exit(exit_code)
def test_lint_ignore(self): lint_config = LintConfig() lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"] linter = GitLinter(lint_config) violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample3"))) 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)] self.assertListEqual(violations, expected)
def test_lint_ignore(self): lint_config = LintConfig() lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"] linter = GitLinter(lint_config) violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample3"))) expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5)] self.assertListEqual(violations, expected)
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj[0] try: if sys.stdin.isatty(): # If target has not been set explicitly before, fallback to the current directory gitcontext = GitContext.from_local_repository(lint_config.target, ctx.obj[2]) else: stdin_str = ustr(sys.stdin.read()) gitcontext = GitContext.from_commit_msg(stdin_str) except GitContextError as e: click.echo(ustr(e)) ctx.exit(GIT_CONTEXT_ERROR_CODE) number_of_commits = len(gitcontext.commits) # 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. if number_of_commits == 0: click.echo(u'No commits in range "{0}".'.format(ctx.obj[2])) ctx.exit(0) general_config_builder = ctx.obj[1] last_commit = gitcontext.commits[-1] # Let's get linting! first_violation = True exit_code = 0 for commit in gitcontext.commits: # Build a config_builder and linter taking into account the commit specific config (if any) config_builder = general_config_builder.clone() config_builder.set_config_from_commit(commit) lint_config = config_builder.build(lint_config) linter = GitLinter(lint_config) # Actually do the linting violations = linter.lint(commit) # exit code equals the total number of violations in all commits exit_code += len(violations) 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( "\n" if not first_violation or commit is last_commit else "", commit.sha[:10] )) linter.print_violations(violations) first_violation = False # cap actual max exit code because bash doesn't like exit codes larger than 255: # http://tldp.org/LDP/abs/html/exitcodes.html exit_code = min(MAX_VIOLATION_ERROR_CODE, exit_code) LOG.debug("Exit Code = %s", exit_code) ctx.exit(exit_code)
def test_lint_meta(self): """ Lint sample2 but also add some metadata to the commit so we that get's linted as well """ linter = GitLinter(LintConfig()) gitcontext = self.gitcontext(self.get_sample("commit_message/sample2")) gitcontext.commits[0].author_email = u"foo bår" violations = linter.lint(gitcontext.commits[-1]) expected = [RuleViolation("M1", "Author email for commit is invalid", u"foo bår", None), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", u"Just a title contåining WIP", 1), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected)
def test_lint_configuration_rule(self): # Test that all rules are ignored because of matching regex lint_config = LintConfig() lint_config.set_rule_option("I1", "regex", "^Just a title(.*)") linter = GitLinter(lint_config) violations = linter.lint( self.gitcommit(self.get_sample("commit_message/sample2"))) self.assertListEqual(violations, []) # Test ignoring only certain rules lint_config = LintConfig() lint_config.set_rule_option("I1", "regex", "^Just a title(.*)") lint_config.set_rule_option("I1", "ignore", "B6") linter = GitLinter(lint_config) violations = linter.lint( self.gitcommit(self.get_sample("commit_message/sample2"))) # 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) ] 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(.*)") 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), RuleViolation( "T5", "Title contains the word 'WIP' (case-insensitive)", u"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", "This line has a trailing tab.\t", 4), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 4) ] self.assertListEqual(violations, expected_errors)
def test_lint_merge_commit(self): commit = self.gitcommit(self.get_sample("commit_message/sample6")) # Sample 6 is a merge commit lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # Even though there are a number of violations in the commit message, they are ignored because # we are dealing with a merge commit self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' lintconfig.ignore_merge_commits = False linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0)
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj[0] try: if sys.stdin.isatty(): # If target has not been set explicitly before, fallback to the current directory gitcontext = GitContext.from_local_repository( lint_config.target, ctx.obj[2]) else: stdin_str = ustr(sys.stdin.read()) gitcontext = GitContext.from_commit_msg(stdin_str) except GitContextError as e: click.echo(ustr(e)) ctx.exit(GIT_CONTEXT_ERROR_CODE) number_of_commits = len(gitcontext.commits) # 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. if number_of_commits == 0: click.echo(u'No commits in range "{0}".'.format(ctx.obj[2])) ctx.exit(0) config_builder = ctx.obj[1] last_commit = gitcontext.commits[-1] # Apply an additional config that is specified in the last commit message config_builder.set_config_from_commit(last_commit) lint_config = config_builder.build(lint_config) # Let's get linting! linter = GitLinter(lint_config) first_violation = True for commit in gitcontext.commits: violations = linter.lint(commit) if violations: # Display the commit hash & new lines intelligently if number_of_commits > 1 and commit.sha: click.echo(u"{0}Commit {1}:".format( "\n" if not first_violation or commit is last_commit else "", commit.sha[:10])) linter.print_violations(violations) first_violation = False exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations)) ctx.exit(exit_code)
def test_lint_sample5(self): gitcontext = self.gitcontext(self.get_sample("commit_message/sample5")) lintconfig = LintConfig() lintconfig.apply_config_from_commit(gitcontext.commits[-1]) linter = GitLinter(lintconfig) violations = linter.lint(gitcontext.commits[-1], gitcontext) title = " Commit title containing 'WIP', \tleading and trailing whitespace and longer than 72 characters." # expect only certain violations because sample5 has a 'gitlint: T3,' 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", "This line should be empty", 2), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing 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)] self.assertListEqual(violations, expected)
def test_lint_configuration_rule(self): # Test that all rules are ignored because of matching regex lint_config = LintConfig() lint_config.set_rule_option("I1", "regex", "^Just a title(.*)") linter = GitLinter(lint_config) violations = linter.lint( self.gitcommit(self.get_sample("commit_message/sample2"))) self.assertListEqual(violations, []) # Test ignoring only certain rules lint_config = LintConfig() lint_config.set_rule_option("I1", "regex", "^Just a title(.*)") lint_config.set_rule_option("I1", "ignore", "B6") linter = GitLinter(lint_config) violations = linter.lint( self.gitcommit(self.get_sample("commit_message/sample2"))) # 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) ] self.assertListEqual(violations, expected)
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"]) 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") # 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) ] self.assertListEqual(violations, linter.lint(commit)) # ignore regular rule: only named rule violations show up lint_config.ignore = ["T5"] self.assertListEqual(violations[1:], linter.lint(commit)) # ignore named rule by id: only regular rule violations show up lint_config.ignore = [rule_id] 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"] self.assertListEqual(violations[:-1], linter.lint(commit))
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj try: if sys.stdin.isatty(): gitcontext = GitContext.from_local_repository(lint_config.target) else: gitcontext = GitContext.from_commit_msg(sys.stdin.read()) except GitContextError as e: click.echo(str(e)) ctx.exit(GIT_CONTEXT_ERROR_CODE) last_commit = gitcontext.commits[-1] # Apply an additional config that is specified in the last commit message lint_config.apply_config_from_commit(last_commit) # Let's get linting! linter = GitLinter(lint_config) violations = linter.lint(last_commit) linter.print_violations(violations) exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations)) ctx.exit(exit_code)
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj try: if sys.stdin.isatty(): gitcontext = GitContext.from_local_repository(lint_config.target) else: gitcontext = GitContext.from_commit_msg(sys.stdin.read()) except GitContextError as e: click.echo(str(e)) ctx.exit(GIT_CONTEXT_ERROR_CODE) last_commit = gitcontext.commits[-1] # Apply an additional config that is specified in the last commit message lint_config.apply_config_from_commit(last_commit) # Let's get linting! linter = GitLinter(lint_config) violations = linter.lint(last_commit, gitcontext) linter.print_violations(violations) exit_code = min(MAX_VIOLATION_ERROR_CODE, len(violations)) ctx.exit(exit_code)
def test_lint_special_commit(self): for commit_type in ["merge", "squash", "fixup"]: commit = self.gitcommit(self.get_sample("commit_message/{0}".format(commit_type))) lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # Even though there are a number of violations in the commit message, they are ignored because # we are dealing with a merge commit self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' setattr(lintconfig, "ignore_{0}_commits".format(commit_type), False) linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0)
def test_lint_merge_commit(self): commit = self.gitcommit(self.get_sample( "commit_message/sample6")) # Sample 6 is a merge commit lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # Even though there are a number of violations in the commit message, they are ignored because # we are dealing with a merge commit self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' lintconfig.ignore_merge_commits = False linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0)
def lint(ctx): """ Lints a git repository [default command] """ lint_config = ctx.obj[0] msg_filename = ctx.obj[3] # Let's determine where our input data is coming from: # Order of precedence: # 1. Any data specified via --msg-filename # 2. Any data sent to stdin # 3. Fallback to reading from local repository stdin_input = get_stdin_data() if msg_filename: LOG.debug("Attempting to read from --msg-filename.") gitcontext = GitContext.from_commit_msg(ustr(msg_filename.read())) elif stdin_input: LOG.debug("No --msg-filename flag. Attempting to read from stdin.") gitcontext = GitContext.from_commit_msg(stdin_input) else: LOG.debug( "No --msg-filename flag, no or empty data passed to stdin. Attempting to read from the local repo." ) gitcontext = GitContext.from_local_repository(lint_config.target, ctx.obj[2]) number_of_commits = len(gitcontext.commits) # 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. if number_of_commits == 0: LOG.debug(u'No commits in range "%s"', ctx.obj[2]) ctx.exit(0) general_config_builder = ctx.obj[1] last_commit = gitcontext.commits[-1] # Let's get linting! first_violation = True exit_code = 0 for commit in gitcontext.commits: # Build a config_builder taking into account the commit specific config (if any) config_builder = general_config_builder.clone() config_builder.set_config_from_commit(commit) # Create a deepcopy from the original config, so we have a unique config object per commit # This is important for configuration rules to be able to modifying the config on a per commit basis commit_config = config_builder.build(copy.deepcopy(lint_config)) # Actually do the linting linter = GitLinter(commit_config) violations = linter.lint(commit) # exit code equals the total number of violations in all commits exit_code += len(violations) 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( "\n" if not first_violation or commit is last_commit else "", commit.sha[:10])) linter.print_violations(violations) first_violation = False # cap actual max exit code because bash doesn't like exit codes larger than 255: # http://tldp.org/LDP/abs/html/exitcodes.html exit_code = min(MAX_VIOLATION_ERROR_CODE, exit_code) LOG.debug("Exit Code = %s", exit_code) ctx.exit(exit_code)