Skip to content

Commit

Permalink
New Rule: title-min-length (#140)
Browse files Browse the repository at this point in the history
The title-min-length rule enforces a minimum length on commit titles.

This closes #138
  • Loading branch information
mrshu authored Oct 18, 2020
1 parent 2ce3103 commit d542b14
Show file tree
Hide file tree
Showing 19 changed files with 111 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Most general options can now be set through environment variables (e.g. set the `general.ignore` option via `GITLINT_IGNORE=T1,T2`). The list of available environment variables can be found in the [configuration documentation](TODO).
- Users can now use `self.log.debug("my message")` for debugging purposes in their user-defined rules
- Breaking: User-defined rule id's can no longer start with 'I', as those are reserved for built-in gitlint ignore rules.
- **New Rule**: [title-min-length](TODO) enforces a minimum length on titles (default: 5 chars) ([#130](https://github.com/jorisroovers/gitlint/issues/138))
- **New Rule**: [ignore-body-lines](TODO) allows users to
[ignore parts of a commit](http://jorisroovers.github.io/gitlint/#ignoring-commits) by matching a regex against
the lines in a commit message body. ([#126](https://github.com/jorisroovers/gitlint/issues/126)).
Expand Down
7 changes: 6 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ extra-path=examples/
[title-max-length]
line-length=80

# Conversely, you can also enforce minimal length of a title with the
# "title-min-length" rule:
# [title-min-length]
# min-length=5

[title-must-not-contain-word]
# Comma-separated list of words that should not occur in the title. Matching is case
# insensitive. It's fine if the keyword occurs as part of a larger word (so "WIPING"
Expand Down Expand Up @@ -449,4 +454,4 @@ GITLINT_STAGED=1 gitlint # using env variable
#.gitlint
[general]
staged=true
```
```
25 changes: 24 additions & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ T4 | title-hard-tab | >= 0.1.0 | Title cannot contain h
T5 | title-must-not-contain-word | >= 0.1.0 | Title cannot contain certain words (default: "WIP")
T6 | title-leading-whitespace | >= 0.4.0 | Title cannot have leading whitespace (space or tab)
T7 | title-match-regex | >= 0.5.0 | Title must match a given regex (default: None)
T8 | title-max-length | >= 0.14.0 | Title length must be > 5 chars.
B1 | body-max-line-length | >= 0.1.0 | Lines in the body must be < 80 chars
B2 | body-trailing-whitespace | >= 0.1.0 | Body cannot have trailing whitespace (space or tab)
B3 | body-hard-tab | >= 0.1.0 | Body cannot contain hard tab characters (\t)
Expand Down Expand Up @@ -126,6 +127,28 @@ regex | >= 0.5 | .* | [Python regex](https://docs.python.
regex=^US[1-9][0-9]*
```

## T8: title-min-length ##

ID | Name | gitlint version | Description
------|-----------------------------|-----------------|-------------------------------------------
T1 | title-min-length | >= | Title length must be > 5 chars.


### Options

Name | gitlint version | Default | Description
---------------|-----------------|---------|----------------------------------
min-length | >= 0.14.0 | 5 | Minimum required title length

### Examples

#### .gitlint

```ini
# Titles should be min 3 chars
[title-min-length]
min-length=3
```

## B1: body-max-line-length

Expand Down Expand Up @@ -378,4 +401,4 @@ regex=(^Co-Authored-By)|(^Signed-Off-By)
# Ignore lines that contain 'foobar'
[ignore-body-lines]
regex=(.*)foobar(.*)
```
```
1 change: 1 addition & 0 deletions gitlint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class LintConfig(object):
rules.TitleHardTab,
rules.TitleMustNotContainWord,
rules.TitleRegexMatches,
rules.TitleMinLength,
rules.BodyMaxLineLength,
rules.BodyMinLength,
rules.BodyMissing,
Expand Down
7 changes: 6 additions & 1 deletion gitlint/files/gitlint
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
# [title-max-length]
# line-length=50

# Conversely, you can also enforce minimal length of a title with the
# "title-min-length" rule:
# [title-min-length]
# min-length=5

# [title-must-not-contain-word]
# Comma-separated list of words that should not occur in the title. Matching is case
# insensitive. It's fine if the keyword occurs as part of a larger word (so "WIPING"
Expand Down Expand Up @@ -111,4 +116,4 @@
# under [general] section above.
# [contrib-title-conventional-commits]
# Specify allowed commit types. For details see: https://www.conventionalcommits.org/
# types = bugfix,user-story,epic
# types = bugfix,user-story,epic
14 changes: 14 additions & 0 deletions gitlint/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,20 @@ def validate(self, title, _commit):
return [RuleViolation(self.id, violation_msg, title)]


class TitleMinLength(LineRule):
name = "title-min-length"
id = "T8"
target = CommitMessageTitle
options_spec = [IntOption('min-length', 5, "Minimum required title length")]

def validate(self, title, _commit):
min_length = self.options['min-length'].value
actual_length = len(title)
if actual_length < min_length:
violation_message = "Title is too short ({0}<{1})".format(actual_length, min_length)
return [RuleViolation(self.id, violation_message, title, 1)]


class BodyMaxLineLength(MaxLineLength):
name = "body-max-line-length"
id = "B1"
Expand Down
2 changes: 1 addition & 1 deletion gitlint/tests/cli/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def test_debug(self, sh, _):
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
u"föo\nbar",
u"föobar\nbar",
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
Expand Down
8 changes: 4 additions & 4 deletions gitlint/tests/config/test_config_precedence.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LintConfigPrecedenceTests(BaseTestCase):
def setUp(self):
self.cli = CliRunner()

@patch('gitlint.cli.get_stdin_data', return_value=u"WIP\n\nThis is å test message\n")
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP:fö\n\nThis is å test message\n")
def test_config_precedence(self, _):
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
# to more easily test everything
Expand All @@ -41,14 +41,14 @@ def test_config_precedence(self, _):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")

# 2. environment variables
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path],
env={"GITLINT_VERBOSITY": "3"})
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")

# 3. commandline -c flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
Expand All @@ -66,7 +66,7 @@ def test_config_precedence(self, _):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli)
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")

@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test")
def test_ignore_precedence(self, get_stdin_data):
Expand Down
4 changes: 3 additions & 1 deletion gitlint/tests/expected/cli/test_cli/test_debug_1
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target: {target}
words=WIP,bögus
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=30
B5: body-min-length
Expand Down Expand Up @@ -103,7 +105,7 @@ DEBUG: gitlint.git ('branch', '--contains', '4da2656b0dadc76c7ee3fd0243a96cb6400
DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '4da2656b0dadc76c7ee3fd0243a96cb64007f125')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
föo
föobar
bar
--- Meta info ---------
Author: test åuthor3 <test-email3@föo.com>
Expand Down
2 changes: 2 additions & 0 deletions gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions gitlint/tests/expected/cli/test_cli/test_named_rules_2
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ target: {target}
words=WIP,bögus
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
34 changes: 33 additions & 1 deletion gitlint/tests/rules/test_title_rules.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from gitlint.tests.base import BaseTestCase
from gitlint.rules import TitleMaxLength, TitleTrailingWhitespace, TitleHardTab, TitleMustNotContainWord, \
TitleTrailingPunctuation, TitleLeadingWhitespace, TitleRegexMatches, RuleViolation
TitleTrailingPunctuation, TitleLeadingWhitespace, TitleRegexMatches, RuleViolation, TitleMinLength


class TitleRuleTests(BaseTestCase):
Expand Down Expand Up @@ -152,3 +152,35 @@ def test_regex_matches(self):
violations = rule.validate(commit.message.title, commit)
expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc")
self.assertListEqual(violations, [expected_violation])

def test_min_line_length(self):
rule = TitleMinLength()

# assert no error
violation = rule.validate(u"å" * 72, None)
self.assertIsNone(violation)

# assert error on line length < 5
expected_violation = RuleViolation("T8", "Title is too short (4<5)", u"å" * 4, 1)
violations = rule.validate(u"å" * 4, None)
self.assertListEqual(violations, [expected_violation])

# set line length to 3, and check no violation on length 4
rule = TitleMinLength({'min-length': 3})
violations = rule.validate(u"å" * 4, None)
self.assertIsNone(violations)

# assert no violations on length 3 (this asserts we've implemented a *strict* less than)
rule = TitleMinLength({'min-length': 3})
violations = rule.validate(u"å" * 3, None)
self.assertIsNone(violations)

# assert raise on 2
expected_violation = RuleViolation("T8", "Title is too short (2<3)", u"å" * 2, 1)
violations = rule.validate(u"å" * 2, None)
self.assertListEqual(violations, [expected_violation])

# assert raise on empty title
expected_violation = RuleViolation("T8", "Title is too short (0<3)", "", 1)
violations = rule.validate("", None)
self.assertListEqual(violations, [expected_violation])
2 changes: 2 additions & 0 deletions qa/expected/test_commits/test_lint_staged_msg_filename_1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions qa/expected/test_commits/test_lint_staged_stdin_1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions qa/expected/test_config/test_config_from_env_1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions qa/expected/test_config/test_config_from_env_2
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ target: {target}
words=WIP
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
Expand Down
2 changes: 2 additions & 0 deletions qa/expected/test_config/test_config_from_file_debug_1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ target: {target}
words=WIP,thåt
T7: title-match-regex
regex=None
T8: title-min-length
min-length=5
B1: body-max-line-length
line-length=30
B5: body-min-length
Expand Down

0 comments on commit d542b14

Please sign in to comment.