summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/configuration.md6
-rw-r--r--docs/index.md5
-rw-r--r--gitlint/cli.py3
-rw-r--r--gitlint/git.py86
-rw-r--r--gitlint/tests/base.py3
-rw-r--r--gitlint/tests/test_config.py14
-rw-r--r--gitlint/tests/test_git.py31
-rw-r--r--gitlint/tests/test_lint.py16
8 files changed, 112 insertions, 52 deletions
diff --git a/docs/configuration.md b/docs/configuration.md
index 951b624..6486d6d 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -54,7 +54,7 @@ Details about rule config options can be found in the Rules page.
# Commandline config #
-Alternatively, you can use one or more ```-c``` flags like so:
+You can also use one or more ```-c``` flags like so:
```
$ gitlint -c general.verbosity=2 -c title-max-length.line-length=80 -c B1.line-length=100
@@ -62,7 +62,9 @@ $ gitlint -c general.verbosity=2 -c title-max-length.line-length=80 -c B1.line-l
The generic config flag format is ```-c <rule>.<option>=<value>``` and supports all the same rules and options which
you can also use in a ```.gitlint``` config file.
-Finally, you can also disable gitlint for specific commit messages by adding ```gitlint-ignore: all``` to the commit
+# Commit specific config #
+
+You can also disable gitlint for specific commit messages by adding ```gitlint-ignore: all``` to the commit
message like so:
```
diff --git a/docs/index.md b/docs/index.md
index e630b78..3b1107b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -46,7 +46,10 @@ NOTE: The returned exit code equals the number of errors found. [Some exit codes
For a list of available rules and their configuration options, have a look at the [Rules](rules.md) page.
-You can modify verbosity using the ```-v``` flag, like so:
+The [Configuration](configuration.md) page explains how you can modify gitlint's runtime behavior via command-line
+flags, a ```.gitlint``` configuration file or on a per commit basis.
+
+As a simple example, you can modify gitlint's verbosity using the ```-v``` flag, like so:
```bash
$ cat examples/commit-message-2 | gitlint -v
1: T1
diff --git a/gitlint/cli.py b/gitlint/cli.py
index e794536..00ef3f9 100644
--- a/gitlint/cli.py
+++ b/gitlint/cli.py
@@ -101,8 +101,7 @@ def lint(ctx):
if sys.stdin.isatty():
gitcontext = GitContext.from_local_repository(lint_config.target)
else:
- gitcontext = GitContext()
- gitcontext.set_commit_msg(sys.stdin.read())
+ gitcontext = GitContext.from_commit_msg(sys.stdin.read())
except GitContextError as e:
click.echo(str(e))
ctx.exit(GIT_CONTEXT_ERROR_CODE)
diff --git a/gitlint/git.py b/gitlint/git.py
index 7aae7f5..f2c24af 100644
--- a/gitlint/git.py
+++ b/gitlint/git.py
@@ -22,6 +22,15 @@ class GitCommitMessage(object):
self.title = title
self.body = body
+ @staticmethod
+ def from_full_message(commit_msg_str):
+ """ Parses a full git commit message by parsing a given string into the different parts of a commit message """
+ lines = [line for line in commit_msg_str.split("\n") if not line.startswith("#")]
+ full = "\n".join(lines)
+ title = lines[0] if len(lines) > 0 else ""
+ body = lines[1:] if len(lines) > 1 else []
+ return GitCommitMessage(original=commit_msg_str, full=full, title=title, body=body)
+
def __str__(self):
return self.full # pragma: no cover
@@ -29,34 +38,76 @@ class GitCommitMessage(object):
return self.__str__() # pragma: no cover
+class GitCommit(object):
+ """ Class representing a git commit.
+ A commit consists of: message, author name, author email, date, list of changed files
+ In the context of gitlint, only the commit message is required.
+ """
+
+ def __init__(self, message, date=None, author_name=None, author_email=None, changed_files=None):
+ self.message = message
+ self.author_name = author_name
+ self.author_email = author_email
+ self.date = date
+
+ if not changed_files:
+ self.changed_files = []
+ else:
+ self.changed_files = changed_files
+
+ def __str__(self):
+ format_str = "Author: %s <%s>\nDate: %s\n%s" # pragma: no cover
+ return format_str % (self.author_name, self.author_email, self.date, str(self.message)) # pragma: no cover
+
+ def __repr__(self):
+ return self.__str__() # pragma: no cover
+
+
class GitContext(object):
""" Class representing the git context in which gitlint is operating: a data object storing information about
the git repository that gitlint is linting.
"""
def __init__(self):
- self.commit_msg = None
- self.changed_files = []
+ self.commits = []
- def set_commit_msg(self, commit_msg_str):
- """ Sets the commit message by parsing a given string into the different parts of a commit message """
- lines = [line for line in commit_msg_str.split("\n") if not line.startswith("#")]
- full = "\n".join(lines)
- title = lines[0] if len(lines) > 0 else ""
- body = lines[1:] if len(lines) > 1 else []
- self.commit_msg = GitCommitMessage(original=commit_msg_str, full=full, title=title, body=body)
+ @property
+ def commit_msg(self):
+ if len(self.commits) > 0:
+ return self.commits[-1].message
+ return None
+
+ @staticmethod
+ def from_commit_msg(commit_msg_str):
+ """ Determines git context based on a commit message.
+ :param commit_msg_str: Full git commit message.
+ """
+ commit_msg_obj = GitCommitMessage.from_full_message(commit_msg_str)
+ commit = GitCommit(commit_msg_obj)
+
+ context = GitContext()
+ context.commits.append(commit)
+ return context
@staticmethod
def from_local_repository(repository_path):
+ """ Retrieves the git context from a local git repository.
+ :param repository_path: Path to the git repository to retrieve the context from
+ """
try:
# Special arguments passed to sh: http://amoffat.github.io/sh/special_arguments.html
sh_special_args = {
'_tty_out': False,
'_cwd': repository_path
}
+
# Get info from the local git repository
- # last commit message
+ # https://git-scm.com/docs/pretty-formats
commit_msg = sh.git.log("-1", "--pretty=%B", **sh_special_args)
+ commit_author_name = sh.git.log("-1", "--pretty=%aN", **sh_special_args)
+ commit_author_email = sh.git.log("-1", "--pretty=%aE", **sh_special_args)
+ commit_date = sh.git.log("-1", "--pretty=%aD", **sh_special_args)
+
# changed files in last commit
changed_files_str = sh.git("diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD", **sh_special_args)
except CommandNotFound:
@@ -71,8 +122,13 @@ class GitContext(object):
error_msg = "An error occurred while executing '{}': {}".format(e.full_cmd, error_msg)
raise GitContextError(error_msg)
- # Create GitContext object with the retrieved info and return
- commit_info = GitContext()
- commit_info.set_commit_msg(commit_msg)
- commit_info.changed_files = [changed_file for changed_file in changed_files_str.strip().split("\n")]
- return commit_info
+ # Create Git commit object with the retrieved info
+ changed_files = [changed_file for changed_file in changed_files_str.strip().split("\n")]
+ commit_msg_obj = GitCommitMessage.from_full_message(commit_msg)
+ commit = GitCommit(commit_msg_obj, author_name=commit_author_name, author_email=commit_author_email,
+ date=commit_date, changed_files=changed_files)
+
+ # Create GitContext info with the commit object and return
+ context = GitContext()
+ context.commits.append(commit)
+ return context
diff --git a/gitlint/tests/base.py b/gitlint/tests/base.py
index ed2f774..a8c9943 100644
--- a/gitlint/tests/base.py
+++ b/gitlint/tests/base.py
@@ -23,8 +23,7 @@ class BaseTestCase(TestCase):
def gitcontext(commit_msg_str, changed_files=None):
""" Utility method to easily create gitcontext objects based on a given commit msg string and set of
changed files"""
- gitcontext = GitContext()
- gitcontext.set_commit_msg(commit_msg_str)
+ gitcontext = GitContext.from_commit_msg(commit_msg_str)
if changed_files:
gitcontext.changed_files = changed_files
else:
diff --git a/gitlint/tests/test_config.py b/gitlint/tests/test_config.py
index 55787d7..82a7ff8 100644
--- a/gitlint/tests/test_config.py
+++ b/gitlint/tests/test_config.py
@@ -1,6 +1,5 @@
from gitlint.tests.base import BaseTestCase
from gitlint.config import LintConfig, LintConfigError, LintConfigGenerator, GITLINT_CONFIG_TEMPLATE_SRC_PATH
-from gitlint.git import GitContext
from gitlint import rules
from mock import patch
@@ -145,34 +144,31 @@ class LintConfigTests(BaseTestCase):
original_rules = config.rules
# nothing gitlint
- context = GitContext()
- context.set_commit_msg("test\ngitlint\nfoo")
+ context = self.gitcontext("test\ngitlint\nfoo")
config.apply_config_from_gitcontext(context)
self.assertListEqual(config.rules, original_rules)
# ignore all rules
- context = GitContext()
- context.set_commit_msg("test\ngitlint-ignore: all\nfoo")
+ context = self.gitcontext("test\ngitlint-ignore: all\nfoo")
config.apply_config_from_gitcontext(context)
self.assertEqual(config.rules, [])
# ignore all rules, no space
config = LintConfig()
- context.set_commit_msg("test\ngitlint-ignore:all\nfoo")
+ context = self.gitcontext("test\ngitlint-ignore:all\nfoo")
config.apply_config_from_gitcontext(context)
self.assertEqual(config.rules, [])
# ignore all rules, more spacing
config = LintConfig()
- context.set_commit_msg("test\ngitlint-ignore: \t all\nfoo")
+ context = self.gitcontext("test\ngitlint-ignore: \t all\nfoo")
config.apply_config_from_gitcontext(context)
self.assertEqual(config.rules, [])
def test_gitcontext_ignore_specific(self):
# ignore specific rules
config = LintConfig()
- context = GitContext()
- context.set_commit_msg("test\ngitlint-ignore: T1, body-hard-tab")
+ context = self.gitcontext("test\ngitlint-ignore: T1, body-hard-tab")
config.apply_config_from_gitcontext(context)
expected_rules = [rule for rule in config.rules if rule.id not in ["T1", "body-hard-tab"]]
self.assertEqual(config.rules, expected_rules)
diff --git a/gitlint/tests/test_git.py b/gitlint/tests/test_git.py
index 1681f9c..db3d700 100644
--- a/gitlint/tests/test_git.py
+++ b/gitlint/tests/test_git.py
@@ -1,13 +1,18 @@
from gitlint.tests.base import BaseTestCase
from gitlint.git import GitContext, GitContextError
from sh import ErrorReturnCode, CommandNotFound
-from mock import patch
+from mock import patch, call
class GitTests(BaseTestCase):
@patch('gitlint.git.sh')
def test_get_latest_commit(self, sh):
- sh.git.log.return_value = "commit-title\n\ncommit-body"
+ def git_log_side_effect(*args, **kwargs):
+ return_values = {'--pretty=%B': "commit-title\n\ncommit-body", '--pretty=%aN': "test author",
+ '--pretty=%aE': "test-email@foo.com", '--pretty=%aD': "Mon Feb 29 22:19:39 2016 +0100"}
+ return return_values[args[1]]
+
+ sh.git.log.side_effect = git_log_side_effect
sh.git.return_value = "file1.txt\npath/to/file2.txt\n"
context = GitContext.from_local_repository("fake/path")
@@ -15,15 +20,23 @@ class GitTests(BaseTestCase):
'_tty_out': False,
'_cwd': "fake/path"
}
- # assert that commit message was read using git command
- sh.git.log.assert_called_once_with('-1', '--pretty=%B', **expected_sh_special_args)
+ # assert that commit info was read using git command
+ expected_calls = [call('-1', '--pretty=%B', _cwd='fake/path', _tty_out=False),
+ call('-1', '--pretty=%aN', _cwd='fake/path', _tty_out=False),
+ call('-1', '--pretty=%aE', _cwd='fake/path', _tty_out=False),
+ call('-1', '--pretty=%aD', _cwd='fake/path', _tty_out=False)]
+
+ self.assertListEqual(sh.git.log.mock_calls, expected_calls)
+
self.assertEqual(context.commit_msg.title, "commit-title")
self.assertEqual(context.commit_msg.body, ["", "commit-body"])
+ self.assertEqual(context.commits[-1].author_name, "test author")
+ self.assertEqual(context.commits[-1].author_email, "test-email@foo.com")
# assert that changed files are read using git command
sh.git.assert_called_once_with('diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD',
**expected_sh_special_args)
- self.assertListEqual(context.changed_files, ["file1.txt", "path/to/file2.txt"])
+ self.assertListEqual(context.commits[-1].changed_files, ["file1.txt", "path/to/file2.txt"])
@patch('gitlint.git.sh')
def test_get_latest_commit_command_not_found(self, sh):
@@ -47,9 +60,8 @@ class GitTests(BaseTestCase):
# assert that commit message was read using git command
sh.git.log.assert_called_once_with('-1', '--pretty=%B', _tty_out=False, _cwd="fake/path")
- def test_set_commit_msg_full(self):
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample1"))
+ def test_from_commit_msg_full(self):
+ gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1"))
expected_title = "Commit title containing 'WIP', as well as trailing punctuation."
expected_body = ["This line should be empty",
@@ -66,8 +78,7 @@ class GitTests(BaseTestCase):
self.assertEqual(gitcontext.commit_msg.original, expected_original)
def test_set_commit_msg_just_title(self):
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample2"))
+ gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
self.assertEqual(gitcontext.commit_msg.title, "Just a title containing WIP")
self.assertEqual(gitcontext.commit_msg.body, [])
diff --git a/gitlint/tests/test_lint.py b/gitlint/tests/test_lint.py
index 300b242..8af8281 100644
--- a/gitlint/tests/test_lint.py
+++ b/gitlint/tests/test_lint.py
@@ -2,7 +2,6 @@ from gitlint.tests.base import BaseTestCase
from gitlint.lint import GitLinter
from gitlint.rules import RuleViolation
from gitlint.config import LintConfig
-from gitlint.git import GitContext
from mock import patch
try:
@@ -16,8 +15,7 @@ except ImportError:
class RuleOptionTests(BaseTestCase):
def test_lint_sample1(self):
linter = GitLinter(LintConfig())
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample1"))
+ gitcontext = self.gitcontext(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),
@@ -37,8 +35,7 @@ class RuleOptionTests(BaseTestCase):
def test_lint_sample2(self):
linter = GitLinter(LintConfig())
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample2"))
+ gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
violations = linter.lint(gitcontext)
expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
"Just a title containing WIP", 1),
@@ -48,8 +45,7 @@ class RuleOptionTests(BaseTestCase):
def test_lint_sample3(self):
linter = GitLinter(LintConfig())
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample3"))
+ gitcontext = self.gitcontext(self.get_sample("commit_message/sample3"))
violations = linter.lint(gitcontext)
title = " Commit title containing 'WIP', \tleading and trailing whitespace and longer than 72 characters."
@@ -73,8 +69,7 @@ class RuleOptionTests(BaseTestCase):
self.assertListEqual(violations, expected)
def test_lint_sample4(self):
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample4"))
+ gitcontext = self.gitcontext(self.get_sample("commit_message/sample4"))
lintconfig = LintConfig()
lintconfig.apply_config_from_gitcontext(gitcontext)
linter = GitLinter(lintconfig)
@@ -84,8 +79,7 @@ class RuleOptionTests(BaseTestCase):
self.assertListEqual(violations, expected)
def test_lint_sample5(self):
- gitcontext = GitContext()
- gitcontext.set_commit_msg(self.get_sample("commit_message/sample5"))
+ gitcontext = self.gitcontext(self.get_sample("commit_message/sample5"))
lintconfig = LintConfig()
lintconfig.apply_config_from_gitcontext(gitcontext)
linter = GitLinter(lintconfig)