diff options
author | Joris Roovers <jroovers@cisco.com> | 2018-04-02 10:48:41 +0200 |
---|---|---|
committer | Joris Roovers <jroovers@cisco.com> | 2018-04-02 10:48:41 +0200 |
commit | 7a964c94c02da7df501afb168696c1a60f0e5d5e (patch) | |
tree | 20b669e3f2c3509b6174cf9d7849a6c990f676f7 | |
parent | 52dc67e751dbbaa66c455063c6fe4f2f86d668d4 (diff) |
New ignore-by-body rule
New ignore-by-body (I2) that allows users to ignore commit messages based on
matching the commit body against a regex.
Also fixed an issue with unit test failures in Python 3.4 and 3.5 which had
a different default sort order for dictionaries.
This closes #54.
-rw-r--r-- | docs/contributing.md | 6 | ||||
-rw-r--r-- | docs/index.md | 5 | ||||
-rw-r--r-- | docs/rules.md | 16 | ||||
-rw-r--r-- | gitlint/config.py | 3 | ||||
-rw-r--r-- | gitlint/files/gitlint | 9 | ||||
-rw-r--r-- | gitlint/rules.py | 23 | ||||
-rw-r--r-- | gitlint/tests/expected/debug_configuration_output1 | 3 | ||||
-rw-r--r-- | gitlint/tests/test_cli.py | 15 | ||||
-rw-r--r-- | gitlint/tests/test_configuration_rules.py | 35 | ||||
-rw-r--r-- | qa/expected/debug_output1 | 3 | ||||
-rw-r--r-- | qa/samples/config/ignore-release-commits | 6 | ||||
-rw-r--r-- | qa/test_commits.py | 5 |
12 files changed, 115 insertions, 14 deletions
diff --git a/docs/contributing.md b/docs/contributing.md index 965c88d..973092a 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,6 +1,8 @@ +# Contributing + We'd love for you to contribute to gitlint. Thanks for your interest! -Sometimes it takes a while for [me](https://github.com/jorisroovers) to -get back to you (this is a hobby project), but rest assured that we read your message and appreciate your interest! +Sometimes it takes a while for [me](https://github.com/jorisroovers) to get back to you (sometimes up to a few months, +this is a hobby project), but rest assured that we read your message and appreciate your interest! We maintain a [wishlist on our wiki](https://github.com/jorisroovers/gitlint/wiki/Wishlist), but we're obviously open to any suggestions! diff --git a/docs/index.md b/docs/index.md index 25509f2..4adb8a1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -257,6 +257,11 @@ regex=^Release(.*) ignore=title-max-length,body-min-length # ignore all rules by setting ignore to 'all' # ignore=all + +[ignore-by-title] +# Match commits message bodies that have a line that contains 'release' +regex=(.*)release(.*) +ignore=all ``` !!! note diff --git a/docs/rules.md b/docs/rules.md index 47e9483..9142337 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -183,7 +183,6 @@ regex | >= 0.9.0 | ```[^@ ]+@[^@ ]+\.[^@ ]+``` | Rege An often recurring use-case is to only allow email addresses from a certain domain. The following regular expression achieves this: ```[^@]+@foo.com``` - ## I1: ignore-by-title ## ID | Name | gitlint version | Description @@ -197,3 +196,18 @@ Name | gitlint version | Default | Descr ----------------------|-------------------|------------------------------|---------------------------------- regex | >= 0.10.0 | None | Regex to match against commit title. On match, the commit will be ignored. ignore | >= 0.10.0 | all | Comma-seperated list of rule names or ids to ignore when this rule is matched. + + +## I2: ignore-by-body ## + +ID | Name | gitlint version | Description +------|-----------------------------|-----------------|------------------------------------------- +I2 | ignore-by-body | >= 0.10.0 | Ignore a commit based on matching its body. + + +### Options ### + +Name | gitlint version | Default | Description +----------------------|-------------------|------------------------------|---------------------------------- +regex | >= 0.10.0 | None | Regex to match against each line of the body. On match, the commit will be ignored. +ignore | >= 0.10.0 | all | Comma-seperated list of rule names or ids to ignore when this rule is matched.
\ No newline at end of file diff --git a/gitlint/config.py b/gitlint/config.py index 5637d06..97b20ca 100644 --- a/gitlint/config.py +++ b/gitlint/config.py @@ -47,6 +47,7 @@ class LintConfig(object): # Default tuple of rule classes (tuple because immutable). default_rule_classes = (rules.IgnoreByTitle, + rules.IgnoreByBody, rules.TitleMaxLength, rules.TitleTrailingWhitespace, rules.TitleLeadingWhitespace, @@ -255,7 +256,7 @@ class LintConfig(object): return_str += u"[RULES]\n" for rule in self.rules: return_str += u" {0}: {1}\n".format(rule.id, rule.name) - for option_name, option_value in rule.options.items(): + for option_name, option_value in sorted(rule.options.items()): if isinstance(option_value.value, list): option_val_repr = ",".join(option_value.value) else: diff --git a/gitlint/files/gitlint b/gitlint/files/gitlint index e678d04..69f1503 100644 --- a/gitlint/files/gitlint +++ b/gitlint/files/gitlint @@ -70,4 +70,13 @@ # # Ignore certain rules, you can reference them by their id or by their full name # Use 'all' to ignore all rules +# ignore=T1,body-min-length + +# [ignore-by-body] +# Ignore certain rules for commits of which the body has a line that matches a regex +# E.g. Match bodies that have a line that that contain "release" +# regex=(.*)release(.*) +# +# Ignore certain rules, you can reference them by their id or by their full name +# Use 'all' to ignore all rules # ignore=T1,body-min-length
\ No newline at end of file diff --git a/gitlint/rules.py b/gitlint/rules.py index aa341f5..a4a91e9 100644 --- a/gitlint/rules.py +++ b/gitlint/rules.py @@ -315,7 +315,7 @@ class AuthorValidEmail(CommitRule): class IgnoreByTitle(ConfigurationRule): name = "ignore-by-title" id = "I1" - options_spec = [StrOption('regex', None, "Regex that matches the titles of commits this rule should apply to"), + options_spec = [StrOption('regex', None, "Regex matching the titles of commits this rule should apply to"), StrOption('ignore', "all", "Comman-seperate list of rules to ignore")] def apply(self, config, commit): @@ -328,3 +328,24 @@ class IgnoreByTitle(ConfigurationRule): message = message.format(commit.message.title, self.options['regex'].value, self.options['ignore'].value) LOG.debug("Ignoring commit because of rule '%s': %s", self.id, message) + + +class IgnoreByBody(ConfigurationRule): + name = "ignore-by-body" + id = "I2" + options_spec = [StrOption('regex', None, "Regex matching lines of the body of commits this rule should apply to"), + StrOption('ignore', "all", "Comman-seperate list of rules to ignore")] + + def apply(self, config, commit): + body_line_regex = re.compile(self.options['regex'].value, re.UNICODE) + + for line in commit.message.body: + if body_line_regex.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, self.options['ignore'].value) + + LOG.debug("Ignoring commit because of rule '%s': %s", self.id, message) + # No need to check other lines if we found a match + return diff --git a/gitlint/tests/expected/debug_configuration_output1 b/gitlint/tests/expected/debug_configuration_output1 index 382181d..39af301 100644 --- a/gitlint/tests/expected/debug_configuration_output1 +++ b/gitlint/tests/expected/debug_configuration_output1 @@ -11,8 +11,11 @@ debug: True target: {target} [RULES] I1: ignore-by-title + ignore=all regex=None + I2: ignore-by-body ignore=all + regex=None T1: title-max-length line-length=20 T2: title-trailing-whitespace diff --git a/gitlint/tests/test_cli.py b/gitlint/tests/test_cli.py index f2d548d..2f2ffd6 100644 --- a/gitlint/tests/test_cli.py +++ b/gitlint/tests/test_cli.py @@ -141,9 +141,10 @@ class CLITests(BaseTestCase): @patch('gitlint.cli.stdin_has_data', return_value=False) @patch('gitlint.git.sh') def test_lint_multiple_commits_configuration_rules(self, sh, _): - """ Test for --commits option where some of the commits have gitlint config in the commit message """ + """ Test for --commits option where where we have configured gitlint to ignore certain rules for certain commits + """ - # Note that the second commit title has a trailing period that is being ignored by gitlint-ignore: T3 + # Note that the second commit sh.git.side_effect = ["6f29bf81a8322a04071bb794666e48c443a90360\n" + # git rev-list <SHA> "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", @@ -152,21 +153,25 @@ class CLITests(BaseTestCase): u"commït-title1\n\ncommït-body1", u"file1.txt\npåth/to/file2.txt\n", # git diff-tree <SHA> u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 01:00\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"file4.txt\npåth/to/file5.txt\n", u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 01:00\x00åbc\n" - u"commït-title3\n\ncommït-body3", + # 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"file6.txt\npåth/to/file7.txt\n"] with patch('gitlint.display.stderr', new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["--commits", "foo...bar", "-c", "I1.regex=^commït-title2(.*)"]) + result = self.cli.invoke(cli.cli, ["--commits", "foo...bar", "-c", "I1.regex=^commït-title2(.*)", + "-c", "I2.regex=^commït-body3(.*)", "-c", "I2.ignore=B5"]) # 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" u'3: B5 Body message is too short (12<20): "commït-body1"\n\n' u"Commit 4da2656b0d:\n" - u'3: B5 Body message is too short (12<20): "commït-body3"\n') + u'1: T3 Title has trailing punctuation (.): "commït-title3."\n') self.assertEqual(stderr.getvalue(), expected) self.assertEqual(result.exit_code, 2) diff --git a/gitlint/tests/test_configuration_rules.py b/gitlint/tests/test_configuration_rules.py index e1b6882..11bdec0 100644 --- a/gitlint/tests/test_configuration_rules.py +++ b/gitlint/tests/test_configuration_rules.py @@ -13,7 +13,7 @@ class ConfigurationRuleTests(BaseTestCase): config = LintConfig() rule.apply(config, commit) self.assertEqual(config, LintConfig()) - self.assert_logged([]) + self.assert_logged([]) # nothing logged -> nothing ignored # Matching regex -> expect config to ignore all rules rule = rules.IgnoreByTitle({"regex": "^Releäse(.*)"}) @@ -36,3 +36,36 @@ class ConfigurationRuleTests(BaseTestCase): 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" + + def test_ignore_by_body(self): + commit = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") + + # No regex specified -> Config shouldn't be changed + rule = rules.IgnoreByBody() + config = LintConfig() + rule.apply(config, commit) + self.assertEqual(config, LintConfig()) + self.assert_logged([]) # nothing logged -> nothing ignored + + # Matching regex -> expect config to ignore all rules + 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" + self.assert_log_contains(expected_log_message) + + # Matching regex with specific ignore + 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 'I1': " + \ + u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2" diff --git a/qa/expected/debug_output1 b/qa/expected/debug_output1 index 80bee74..93d7ff7 100644 --- a/qa/expected/debug_output1 +++ b/qa/expected/debug_output1 @@ -15,8 +15,11 @@ debug: True target: {target} [RULES] I1: ignore-by-title + ignore=all regex=None + I2: ignore-by-body ignore=all + regex=None T1: title-max-length line-length=20 T2: title-trailing-whitespace diff --git a/qa/samples/config/ignore-release-commits b/qa/samples/config/ignore-release-commits index 898e73f..5807c96 100644 --- a/qa/samples/config/ignore-release-commits +++ b/qa/samples/config/ignore-release-commits @@ -1,3 +1,7 @@ [ignore-by-title] regex=^Release(.*) -ignore=T5,T3
\ No newline at end of file +ignore=T5,T3 + +[ignore-by-body] +regex=(.*)relëase(.*) +ignore=T3,B3
\ No newline at end of file diff --git a/qa/test_commits.py b/qa/test_commits.py index 0d306e1..ddff660 100644 --- a/qa/test_commits.py +++ b/qa/test_commits.py @@ -83,7 +83,8 @@ class CommitsTests(BaseTestCase): # But in this case only B5 because T3 and T5 are being ignored because of config self._create_simple_commit(u"Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo) # In the following 2 commits, the T3 violations are as normal - self._create_simple_commit(u"Sïmple title3.\n\nSimple bödy describing the commit3", git_repo=tmp_git_repo) + self._create_simple_commit( + u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo) self._create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo) revlist = git("rev-list", "HEAD", _err_to_out=True, _cwd=tmp_git_repo).split() @@ -95,7 +96,7 @@ class CommitsTests(BaseTestCase): u"Commit {0}:\n".format(revlist[0][:10]) + u"1: T3 Title has trailing punctuation (.): \"Sïmple title4.\"\n\n" + u"Commit {0}:\n".format(revlist[1][:10]) + - u"1: T3 Title has trailing punctuation (.): \"Sïmple title3.\"\n\n" + + u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Sïmple WIP title3.\"\n\n" + u"Commit {0}:\n".format(revlist[2][:10]) + u"3: B5 Body message is too short (5<20): \"Short\"\n\n" + u"Commit {0}:\n".format(revlist[3][:10]) + |