summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoris Roovers <jroovers@cisco.com>2017-05-13 20:35:17 -0400
committerJoris Roovers <jroovers@cisco.com>2017-05-13 20:35:17 -0400
commit0c66f6dca43eac634d37752dd41d37b7fdbec608 (patch)
treea9987964cb33cbb888afe2fdb46eeb42b91103e9
parent3c1b3c3284534d44f1a720e96497030bf5c9abd6 (diff)
New rule: author-valid-email
This commits adds a new rule (author-valid-email) that validates that the commit author email address is a valid email address. The rule has a regex option that allows users to configure specify which email addresses they want to be considered valid. Also changed the 'unittest' import back to 'unittest2' to fix a regression with the integration tests in python 2.6.
-rw-r--r--CHANGELOG.md6
-rw-r--r--docs/configuration.md6
-rw-r--r--docs/rules.md26
-rw-r--r--gitlint/config.py3
-rw-r--r--gitlint/files/gitlint6
-rw-r--r--gitlint/lint.py15
-rw-r--r--gitlint/rules.py20
-rw-r--r--gitlint/tests/expected/debug_configuration_output12
-rw-r--r--gitlint/tests/test_lint.py21
-rw-r--r--gitlint/tests/test_meta_rules.py51
-rw-r--r--qa/base.py2
-rw-r--r--qa/expected/debug_output12
12 files changed, 148 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50632af..fba098f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog #
+## v0.8.3 (In Progress) ##
+
+- New Rule: ```author-valid-email``` enforces a valid author email address. Details can be found in the
+ [Rules section of the documentation](http://jorisroovers.github.io/gitlint/rules/).
+- Debug output improvements
+
## v0.8.2 (2017-04-25) ##
The 0.8.2 release brings minor improvements, bugfixes and some under-the-hood changes. Special thanks to
diff --git a/docs/configuration.md b/docs/configuration.md
index 814840e..866808e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -62,6 +62,12 @@ ignore-merge-commits=false
# 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
+
+# [author-valid-email]
+# python like regex (https://docs.python.org/2/library/re.html) that the
+# commit author email address should be matched to
+# For example, use the following regex if you only want to allow email addresses from foo.com
+# regex = "[^@]+@foo.com"
```
# Commandline config #
diff --git a/docs/rules.md b/docs/rules.md
index 343984c..a75c324 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -21,6 +21,8 @@ B4 | body-first-line-empty | >= 0.1 | First line of the body (
B5 | body-min-length | >= 0.4 | Body length must be at least 20 characters
B6 | body-is-missing | >= 0.4 | Body message must be specified
B7 | body-changed-file-mention | >= 0.4 | Body must contain references to certain files if those files are changed in the last commit
+M1 | author-valid-email | >= 0.8.3 | Author email address must be a valid email address
+
## T1: title-max-length ##
@@ -155,3 +157,27 @@ B7 | body-changed-file-mention | >= 0.4 | Body must contain refere
Name | gitlint version | Default | Description
----------------------|-----------------|--------------|----------------------------------
files | >= 0.4 | (empty) | Comma-separated list of files that need to an explicit mention in the commit message in case they are changed.
+
+
+
+## M1: author-valid-email ##
+
+ID | Name | gitlint version | Description
+------|-----------------------------|-----------------|-------------------------------------------
+M1 | author-valid-email | >= 0.8.3 | Author email address must be a valid email address
+
+!!! note
+ Email addresses are [notoriously hard to validate and the official email valid spec is often too loose for any real world application](http://stackoverflow.com/a/201378/381010).
+ Gitlint by default takes a pragmatic approach and requires users to enter email addresses that contain a name, domain and tld and has no spaces.
+
+
+
+### Options ###
+
+Name | gitlint version | Default | Description
+----------------------|-------------------|------------------------------|----------------------------------
+regex | >= 0.8.3 | ```[^@ ]+@[^@ ]+\.[^@ ]+``` | Regex the commit author email address is matched against
+
+
+!!! note
+ An often recurring use-case is to only allow email addresses from a certain domain. The following regular expression achieves this: ```[^@]+@foo.com``` \ No newline at end of file
diff --git a/gitlint/config.py b/gitlint/config.py
index 860eba1..a665b47 100644
--- a/gitlint/config.py
+++ b/gitlint/config.py
@@ -59,7 +59,8 @@ class LintConfig(object):
rules.BodyTrailingWhitespace,
rules.BodyHardTab,
rules.BodyFirstLineEmpty,
- rules.BodyChangedFileMention)
+ rules.BodyChangedFileMention,
+ rules.AuthorValidEmail)
def __init__(self):
# Use an ordered dict so that the order in which rules are applied is always the same
diff --git a/gitlint/files/gitlint b/gitlint/files/gitlint
index 597d6e3..644e5fc 100644
--- a/gitlint/files/gitlint
+++ b/gitlint/files/gitlint
@@ -46,3 +46,9 @@
# 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
+
+# [author-valid-email]
+# python like regex (https://docs.python.org/2/library/re.html) that the
+# commit author email address should be matched to
+# For example, use the following regex if you only want to allow email addresses from foo.com
+# regex = "[^@]+@foo.com" \ No newline at end of file
diff --git a/gitlint/lint.py b/gitlint/lint.py
index 30f0643..a31d165 100644
--- a/gitlint/lint.py
+++ b/gitlint/lint.py
@@ -74,17 +74,20 @@ class GitLinter(object):
violations.extend(self._apply_line_rules(commit.message.body, commit, self.body_line_rules, 2))
violations.extend(self._apply_commit_rules(self.commit_rules, commit))
- # sort violations by line number
- violations.sort(key=lambda v: (v.line_nr, v.rule_id)) # sort violations by line number and rule_id
+ # Sort violations by line number and rule_id. If there's no line nr specified (=common certain commit rules),
+ # we replace None with -1 so that it always get's placed first. Note that we need this to do this to support
+ # python 3, as None is not allowed in a list that is being sorted.
+ violations.sort(key=lambda v: (-1 if v.line_nr is None else v.line_nr, v.rule_id))
return violations
def print_violations(self, violations):
""" Print a given set of violations to the standard error output """
for v in violations:
- self.display.e(u"{0}: {1}".format(v.line_nr, v.rule_id), exact=True)
- self.display.ee(u"{0}: {1} {2}".format(v.line_nr, v.rule_id, v.message), exact=True)
+ line_nr = v.line_nr if v.line_nr else "-"
+ self.display.e(u"{0}: {1}".format(line_nr, v.rule_id), exact=True)
+ self.display.ee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
if v.content:
- self.display.eee(u"{0}: {1} {2}: \"{3}\"".format(v.line_nr, v.rule_id, v.message, v.content),
+ self.display.eee(u"{0}: {1} {2}: \"{3}\"".format(line_nr, v.rule_id, v.message, v.content),
exact=True)
else:
- self.display.eee(u"{0}: {1} {2}".format(v.line_nr, v.rule_id, v.message), exact=True)
+ self.display.eee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
diff --git a/gitlint/rules.py b/gitlint/rules.py
index 69a4517..5c38f4c 100644
--- a/gitlint/rules.py
+++ b/gitlint/rules.py
@@ -286,3 +286,23 @@ class BodyChangedFileMention(CommitRule):
violation_message = u"Body does not mention changed file '{0}'".format(needs_mentioned_file)
violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1))
return violations if violations else None
+
+
+class AuthorValidEmail(CommitRule):
+ name = "author-valid-email"
+ id = "M1"
+ options_spec = [StrOption('regex', "[^@ ]+@[^@ ]+\.[^@ ]+", "Regex that author email address should match")]
+ email_regex = None
+
+ # Note that unicode is allowed in email addresses
+ # See http://stackoverflow.com/questions/3844431/are-email-addresses-allowed-to-contain-non-alphanumeric-characters
+
+ def validate(self, commit):
+
+ # Only compile the regex the first time so that we don't need to re-compile it if we are linting
+ # multiple commits at once
+ if not self.email_regex:
+ self.email_regex = re.compile(self.options['regex'].value, re.UNICODE)
+
+ if commit.author_email and not self.email_regex.match(commit.author_email):
+ return [RuleViolation(self.id, "Author email for commit is invalid", commit.author_email)]
diff --git a/gitlint/tests/expected/debug_configuration_output1 b/gitlint/tests/expected/debug_configuration_output1
index 18d9e53..ca9a6cf 100644
--- a/gitlint/tests/expected/debug_configuration_output1
+++ b/gitlint/tests/expected/debug_configuration_output1
@@ -29,3 +29,5 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ M1: author-valid-email
+ regex=[^@ ]+@[^@ ]+\.[^@ ]+
diff --git a/gitlint/tests/test_lint.py b/gitlint/tests/test_lint.py
index dba3426..e7f8f03 100644
--- a/gitlint/tests/test_lint.py
+++ b/gitlint/tests/test_lint.py
@@ -96,6 +96,19 @@ class RuleOptionTests(BaseTestCase):
"This line has a trailing tab.\t", 5)]
self.assertListEqual(violations, expected)
+ 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_ignore(self):
lint_config = LintConfig()
lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"]
@@ -124,7 +137,7 @@ class RuleOptionTests(BaseTestCase):
self.assertTrue(len(violations) > 0)
def test_print_violations(self):
- violations = [RuleViolation("RULE_ID_1", u"Error Messåge 1", "Violating Content 1", 1),
+ 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())
@@ -137,18 +150,18 @@ class RuleOptionTests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
linter.config.verbosity = 1
linter.print_violations(violations)
- expected = u"1: RULE_ID_1\n2: RULE_ID_2\n"
+ 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"1: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n"
+ 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"1: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
+ 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())
diff --git a/gitlint/tests/test_meta_rules.py b/gitlint/tests/test_meta_rules.py
new file mode 100644
index 0000000..88a0be9
--- /dev/null
+++ b/gitlint/tests/test_meta_rules.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from gitlint.tests.base import BaseTestCase
+from gitlint.rules import AuthorValidEmail, RuleViolation
+
+from gitlint.git import GitCommit
+
+
+class MetaRuleTests(BaseTestCase):
+ def test_author_valid_email_rule(self):
+ rule = AuthorValidEmail()
+
+ # valid email addresses
+ valid_email_addresses = [u"föo@bar.com", u"Jöhn.Doe@bar.com", u"jöhn+doe@bar.com", u"jöhn/doe@bar.com",
+ u"jöhn.doe@subdomain.bar.com"]
+ for email in valid_email_addresses:
+ commit = GitCommit(None, None, author_email=email)
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an
+ # email address)
+ commit = GitCommit(None, None)
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint)
+ invalid_email_addresses = [u"föo@bar", u"JöhnDoe", u"Jöhn Doe", u"Jöhn Doe@foo.com", u" JöhnDoe@foo.com",
+ u"JöhnDoe@ foo.com", u"JöhnDoe@foo. com", u"JöhnDoe@foo. com", u"@bår.com",
+ u"föo@.com"]
+ for email in invalid_email_addresses:
+ commit = GitCommit(None, None, author_email=email)
+ violations = rule.validate(commit)
+ self.assertListEqual(violations,
+ [RuleViolation("M1", "Author email for commit is invalid", email)])
+
+ def test_author_valid_email_rule_custom_regex(self):
+ # Custom domain
+ rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"})
+ valid_email_addresses = [u"föo@bår.com", u"Jöhn.Doe@bår.com", u"jöhn+doe@bår.com", u"jöhn/doe@bår.com"]
+ for email in valid_email_addresses:
+ commit = GitCommit(None, None, author_email=email)
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # Invalid email addresses
+ invalid_email_addresses = [u"föo@hur.com"]
+ for email in invalid_email_addresses:
+ commit = GitCommit(None, None, author_email=email)
+ violations = rule.validate(commit)
+ self.assertListEqual(violations,
+ [RuleViolation("M1", "Author email for commit is invalid", email)])
diff --git a/qa/base.py b/qa/base.py
index 9ca7d3e..e0820b7 100644
--- a/qa/base.py
+++ b/qa/base.py
@@ -5,7 +5,7 @@ import sys
from datetime import datetime
from uuid import uuid4
-from unittest import TestCase
+from unittest2 import TestCase
from sh import git, rm, touch, DEFAULT_ENCODING # pylint: disable=no-name-in-module
diff --git a/qa/expected/debug_output1 b/qa/expected/debug_output1
index ef47440..57d9343 100644
--- a/qa/expected/debug_output1
+++ b/qa/expected/debug_output1
@@ -33,6 +33,8 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ M1: author-valid-email
+ regex=[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.lint Linting commit {commit_sha}
DEBUG: gitlint.lint Commit Object