summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoris Roovers <jroovers@cisco.com>2018-04-02 10:48:41 +0200
committerJoris Roovers <jroovers@cisco.com>2018-04-02 10:48:41 +0200
commit7a964c94c02da7df501afb168696c1a60f0e5d5e (patch)
tree20b669e3f2c3509b6174cf9d7849a6c990f676f7
parent52dc67e751dbbaa66c455063c6fe4f2f86d668d4 (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.md6
-rw-r--r--docs/index.md5
-rw-r--r--docs/rules.md16
-rw-r--r--gitlint/config.py3
-rw-r--r--gitlint/files/gitlint9
-rw-r--r--gitlint/rules.py23
-rw-r--r--gitlint/tests/expected/debug_configuration_output13
-rw-r--r--gitlint/tests/test_cli.py15
-rw-r--r--gitlint/tests/test_configuration_rules.py35
-rw-r--r--qa/expected/debug_output13
-rw-r--r--qa/samples/config/ignore-release-commits6
-rw-r--r--qa/test_commits.py5
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]) +