From 9c6f70de92f95e1640e95e1d294fd1489b94d48f Mon Sep 17 00:00:00 2001 From: Joris Roovers Date: Fri, 2 Dec 2016 16:46:27 +0100 Subject: [PATCH] Documentation improvements + extra-path default path fix Extra-path now defaults to 'None' instead of the current working directory. Documentation improvements: - Features section on the homepage - More documentation of the user-defined rules (still WIP) --- CHANGELOG.md | 6 +- docs/configuration.md | 20 +++--- docs/index.md | 34 ++++++---- docs/user_defined_rules.md | 92 +++++++++++++++++++++++----- examples/gitlint | 3 +- examples/my_commit_rules.py | 11 +++- gitlint/config.py | 12 ++-- gitlint/files/gitlint | 3 +- gitlint/tests/expected/debug_output1 | 2 +- gitlint/tests/test_cli.py | 3 +- gitlint/tests/test_config.py | 3 + qa/expected/debug_output1 | 2 +- qa/test_config.py | 3 +- 13 files changed, 142 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89692b41..40031f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,13 @@ ## v0.8.0 (In Development) ## -- debug output improvements: Gitlint will now print a lot more information when using ```--debug``` +- Debug output improvements: Gitlint will now print a lot more information when using ```--debug``` ## v0.7.1 (2016-06-18) ## Bugfixes: -- **Behavior change**: gitlint no longer prints the file path by default when using a ```.gitlint``` file. The path -will still be printed when using the new ```--debug``` flag. Special thanks to [Slipcon](https://github.com/slipcon). +- **Behavior Change**: gitlint no longer prints the file path by default when using a ```.gitlint``` file. The path +will still be printed when using the new ```--debug``` flag. Special thanks to [Slipcon](https://github.com/slipcon) for submitting this. - Gitlint now prints a correct violation message for the ```title-match-regex``` rule. Special thanks to [Slipcon](https://github.com/slipcon) for submitting this. diff --git a/docs/configuration.md b/docs/configuration.md index fbb933a5..b2a1ba1e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ gitlint --config myconfigfile.ini ``` The block below shows a sample ```.gitlint``` file. Details about rule config options can be found on the -[Rules](rules.md) page, details about general can be found on the +[Rules](rules.md) page, details about the ```[general]``` section can be found in the [General Configuration](configuration.md#general-configuration) section of this page. ```ini @@ -24,6 +24,9 @@ verbosity = 2 # By default gitlint will ignore merge commits. Set to 'false' to disable. ignore-merge-commits=true +# Set the extra-path where gitlint will search for user defined rules +# See http://jorisroovers.github.io/gitlint/user_defined_rules for details +# extra-path=examples/ [title-max-length] line-length=20 @@ -99,10 +102,11 @@ Configuring gitlint happens the following order of precedence: The table below outlines configuration options that modify gitlint's overall behavior. These options can be specified using commandline flags or in ```general``` section in a ```.gitlint``` configuration file. -Name | gitlint version | commandline flag | Description ----------------------|-----------------|------------------------------------|------------------------------------- -silent | >= 0.1 | ```--silent``` | Enable silent mode (no output). Use [exit](index.md#exit-codes) code to determine result. -verbosity | >= 0.1 | ```--verbosity=3``` | Amount of output gitlint will show when printing errors. -ignore-merge-commits | >= 0.7.0 | Not available | Whether or not to ignore merge commits. -ignore | >= 0.1 | ```--ignore=T1,body-min-length``` | Comma seperated list of rules to ignore (by name or id) -debug | >= 0.7.1 | ```--debug``` | Enable debugging output \ No newline at end of file +Name | gitlint version | commandline flag | Description +---------------------|-----------------|---------------------------------------|------------------------------------- +silent | >= 0.1 | ```--silent``` | Enable silent mode (no output). Use [exit](index.md#exit-codes) code to determine result. +verbosity | >= 0.1 | ```--verbosity=3``` | Amount of output gitlint will show when printing errors. +ignore-merge-commits | >= 0.7.0 | Not available | Whether or not to ignore merge commits. +ignore | >= 0.1 | ```--ignore=T1,body-min-length``` | Comma seperated list of rules to ignore (by name or id) +debug | >= 0.7.1 | ```--debug``` | Enable debugging output +extra-path | >= 0.8.0 | ```---extra-path=/home/joe/rules/``` | Path where gitlint looks for [user-defined rules](user_defined_rules.md). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 96af42c0..3d102b2c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,17 +5,24 @@ Great for use as a ```commit-msg``` git hook or as part of your gating script in -Many of the gitlint validations are based on +!!! note + Gitlint is not the only git commit message linter out there, if you are looking for an alternative written in a different language, + have a look at [fit-commit](https://github.com/m1foley/fit-commit) (Ruby) or + [node-commit-msg](https://github.com/clns/node-commit-msg) (Node.js). + +## Features ## + - **Commit message hook**: Auto-trigger validations against new commit message right when you're committing. + - **Easily integrated**: Gitlint will validate any git commit message you give it via standard input. Perfect for integration with your own scripts or CI system. + - **Sane defaults:** Many of gitlint's validations are based on [well-known](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), [community](http://addamhardy.com/blog/2013/06/05/good-commit-messages-and-enforcing-them-with-git-hooks/), [standards](http://chris.beams.io/posts/git-commit/), others are based on checks that we've found -useful throughout the years. Gitlint has sane defaults, but -[you can also easily customize it to your own liking](configuration.md). - -Gitlint supports python versions 2.6, 2.7 and 3.3+. -If you are looking for an alternative written in a different language, have a look at -[fit-commit](https://github.com/m1foley/fit-commit) (Ruby) or -[node-commit-msg](https://github.com/clns/node-commit-msg) (Node.js). +useful throughout the years. + - **Easily configurable:** Gitlint has sane defaults, but [you can also easily customize it to your own liking](configuration.md). + - **User-defined Rules:** Want to do more then what gitlint offers out of the box? Write your own [user defined rules](user_defined_rules). + - **Broad python version support:** Gitlint supports python versions 2.6, 2.7 and 3.3+. + - **Production-ready:** Gitlint has very high unit test coverage, integration tests and python code standards (pep8, pylint) are strictly enforced. + Gitlint also eats its own dogfood: gitlint commit messages are checked by itself on every commit. ## Getting Started ## ```bash @@ -44,7 +51,8 @@ $ cat examples/commit-message-2 | gitlint 3: B2 Line has trailing whitespace: "Lines typically need to have a max length, meaning that they can't exceed a preset number of characters, usually 80 or 120. " 3: B3 Line contains hard tab characters (\t): "Lines typically need to have a max length, meaning that they can't exceed a preset number of characters, usually 80 or 120. " ``` -NOTE: The returned exit code equals the number of errors found. [Some exit codes are special](index.md#exit-codes). +!!! note + The returned exit code equals the number of errors found. [Some exit codes are special](index.md#exit-codes). For a list of available rules and their configuration options, have a look at the [Rules](rules.md) page. @@ -111,9 +119,11 @@ gitlint install-hook gitlint uninstall-hook ``` -Important: Gitlint cannot work together with an existing hook. If you already have a ```.git/hooks/commit-msg``` -file in your local repository, gitlint will refuse to install the ```commit-msg``` hook. gitlint will also only -uninstall unmodified commit-msg hooks that were installed by gitlint. +!!! important + + Gitlint cannot work together with an existing hook. If you already have a ```.git/hooks/commit-msg``` + file in your local repository, gitlint will refuse to install the ```commit-msg``` hook. gitlint will also only + uninstall unmodified commit-msg hooks that were installed by gitlint. ## Merge commits ## As of version 0.7.0, gitlint ignores merge commits by default. The rationale behind this is that in many cases diff --git a/docs/user_defined_rules.md b/docs/user_defined_rules.md index 7b6d4997..5935fda1 100644 --- a/docs/user_defined_rules.md +++ b/docs/user_defined_rules.md @@ -1,24 +1,83 @@ # User Defined Rules -Gitlint versions 0.8.0 and above support the concept of User Defined rules: the ability for you +*New in gitlint 0.8.0* + +Gitlint supports the concept of user-defined rules: the ability for users to write your own custom rules that are executed when gitlint is. -This can be done using the ```extra-path``` general option, which can be set using the ```--extra-path``` -commandline flag, by adding it under the ```[general]``` section in your ```.gitlint``` file or using -one of the other ways to configure gitlint. For more details, please refer to the -[Configuration](configuration.md) page. +In a nutshell, you use ```--extra-path /home/joe/myextensions``` to point gitlint to a ```myextensions``` directory where it will search +for python files containing gitlint rule classes. + +```bash +cat examples/commit-message-1 | gitlint --extra-path examples/ +1: UC2 Body does not contain a 'Signed-Off-By Line' +# other violations were removed for brevity +``` + +This was the result of executing the SignedOffBy user-defined ```CommitRule``` that was found in the +[examples/gitlint/my_commit_rules.py](https://github.com/jorisroovers/gitlint/blob/master/examples/my_commit_rules.py) file: + +```python +class SignedOffBy(CommitRule): + """ This rule will enforce that each commit contains a "Signed-Off-By" line. + We keep things simple here and just check whether the commit body contains a line that starts with "Signed-Off-By". + """ + + # A rule MUST have a human friendly name + name = "body-requires-signed-off-by" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). + id = "UC2" + + def validate(self, commit): + for line in commit.message.body: + if line.startswith("Signed-Off-By"): + return [] + + return [RuleViolation(self.id, "Body does not contain a 'Signed-Off-By Line'", "", 1)] +``` + +As always ```--extra-path``` can also be set by adding it under the ```[general]``` section in your ```.gitlint``` file or using +one of the other ways to configure gitlint. For more details, please refer to the [Configuration](configuration.md) page. -If you want to check whether your rules are discovered, you can use the ```--debug``` flag: + +If you want to check whether your rules are properly discovered by gitlint, you can use the ```--debug``` flag: ```bash -$ gitlint --debug -TODO: CONTINUE +$ gitlint --debug --extra-path examples/ +[output cut for brevity] + UC1: body-max-line-count + body-max-line-count=3 + UC2: body-requires-signed-off-by + UL1: title-no-special-chars + special-chars=['$', '^', '%', '@', '!', '*', '(', ')'] ``` +!!! Note + In most cases it's really the easiest to just copy an example from the + [examples](https://github.com/jorisroovers/gitlint/tree/master/examples) directory and modify it to your needs. + The remainder of this page contains the technical details, mostly for reference. + +## Line and Commit Rules ## +TODO + +The classes below are examples of user-defined CommitRules. Commit rules are gitlint rules that +act on the entire commit at once. Once the rules are discovered, gitlint will automatically take care of applying them +to the entire commit. This happens exactly once per commit. + +A CommitRule contrasts with a LineRule (see examples/my_line_rules.py) in that a commit rule is only applied once on +an entire commit. This allows commit rules to implement more complex checks that span multiple lines and/or checks +that should only be done once per gitlint run. + +While every LineRule can be implemented as a CommitRule, it's usually easier and more concise to go with a LineRule if +that fits your needs. + +## Violations ## +TODO +## Options ## +TODO -## TODO: -- Document extra_config parameter in the configuration section -## Rule requirements +## Rule requirements ## As long as you stick with simple scenarios that are similar to the sample User Defined rules (see the ```examples``` directory), gitlint should be able to discover and execute your custom rules. If you want to do something more exotic however, you might run into some issues. @@ -35,10 +94,11 @@ ultimate source of truth, here are some of the requirements that gitlint enforce ### Rule class requirements ### -- Rules *must* extend from ```LineRule``` or ```CommitRule``` -- Rule classes *must* have ```id``` and ```name``` string attributes. The ```options_spec``` is optional, but if set, it *must* be a list. -- Rule classes *must* have a ```validate``` method. In case of a ```CommitRule```, ```validate``` *must* take a single ```commit``` parameter. +- Rules **must** extend from ```LineRule``` or ```CommitRule``` +- Rule classes **must** have ```id``` and ```name``` string attributes. The ```options_spec``` is optional, but if set, it **must** be a list. +- Rule classes **must** have a ```validate``` method. In case of a ```CommitRule```, ```validate``` *must* take a single ```commit``` parameter. In case of ```LineRule```, ```validate``` must take ```line``` and ```commit``` as first and second parameters. -- User Rule id's *cannot* be of the form ```R[0-9]+```, ```T[0-9]+```, ```B[0-9]+``` or ```M[0-9]+``` as these rule ids are reserved for gitlint itself. -- Rule *should* have a unique id as only one rule can exist with a given id. While gitlint does not enforce this, the rule that will +- LineRule classes **must** +- User Rule id's **cannot** be of the form ```R[0-9]+```, ```T[0-9]+```, ```B[0-9]+``` or ```M[0-9]+``` as these rule ids are reserved for gitlint itself. +- Rule **should** have a unique id as only one rule can exist with a given id. While gitlint does not enforce this, the rule that will actually be chosen will be system specific. diff --git a/examples/gitlint b/examples/gitlint index 612b1250..fabef2af 100644 --- a/examples/gitlint +++ b/examples/gitlint @@ -8,7 +8,8 @@ ignore-merge-commits=true # Enable debug mode (prints more output). Disabled by default debug = true -# Set the extra path where gitlint will search for user defined rules (uncomment to test) +# Set the extra-path where gitlint will search for user defined rules +# See http://jorisroovers.github.io/gitlint/user_defined_rules for details # extra-path=examples/ [title-max-length] diff --git a/examples/my_commit_rules.py b/examples/my_commit_rules.py index d0fd7e5a..3d9fb3fa 100644 --- a/examples/my_commit_rules.py +++ b/examples/my_commit_rules.py @@ -16,8 +16,13 @@ class BodyMaxLineCount(CommitRule): + # A rule MUST have a human friendly name name = "body-max-line-count" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). id = "UC1" + + # A rule MAY have an option_spec if its behavior should be configurable. options_spec = [IntOption('body-max-line-count', 3, "Maximum body line count")] def validate(self, commit): @@ -29,7 +34,11 @@ class SignedOffBy(CommitRule): """ This rule will enforce that each commit contains a "Signed-Off-By" line. We keep things simple here and just check whether the commit body contains a line that starts with "Signed-Off-By". """ + + # A rule MUST have a human friendly name name = "body-requires-signed-off-by" + + # A rule MUST have an *unique* id, we recommend starting with UC (for User-defined Commit-rule). id = "UC2" def validate(self, commit): @@ -37,4 +46,4 @@ def validate(self, commit): if line.startswith("Signed-Off-By"): return [] - return [RuleViolation(self.id, "Body does not contain a 'Signed-Off-By Line'")] + return [RuleViolation(self.id, "Body does not contain a 'Signed-Off-By Line'", "", 1)] diff --git a/gitlint/config.py b/gitlint/config.py index 33b83a90..69cdce33 100644 --- a/gitlint/config.py +++ b/gitlint/config.py @@ -54,8 +54,7 @@ def __init__(self, config_path=None, target=None): self._ignore_merge_commits = options.BoolOption('ignore-merge-commits', True, "Ignore merge commits") self._debug = options.BoolOption('debug', False, "Enable debug mode") self.config_path = config_path - self._extra_path = options.DirectoryOption('extra_path', ".", - "Path to a directory with extra user-defined rules") + self._extra_path = None if target: self.target = target else: @@ -98,12 +97,17 @@ def debug(self, value): @property def extra_path(self): - return self._extra_path.value + return self._extra_path.value if self._extra_path else None @extra_path.setter def extra_path(self, value): try: - self._extra_path.set(value) + if self.extra_path: + self._extra_path.set(value) + else: + self._extra_path = options.DirectoryOption('extra_path', value, + "Path to a directory with extra user-defined rules") + rule_classes = user_rules.find_rule_classes(self.extra_path) # Add the newly found rules to the existing rules diff --git a/gitlint/files/gitlint b/gitlint/files/gitlint index 776418c7..597d6e34 100644 --- a/gitlint/files/gitlint +++ b/gitlint/files/gitlint @@ -8,7 +8,8 @@ # Enable debug mode (prints more output). Disabled by default. # debug=true -# Set the extra path where gitlint will search for user defined rules +# Set the extra-path where gitlint will search for user defined rules +# See http://jorisroovers.github.io/gitlint/user_defined_rules for details # extra-path=examples/ # [title-max-length] diff --git a/gitlint/tests/expected/debug_output1 b/gitlint/tests/expected/debug_output1 index b73266e2..307d46b5 100644 --- a/gitlint/tests/expected/debug_output1 +++ b/gitlint/tests/expected/debug_output1 @@ -1,6 +1,6 @@ [GENERAL] config path: {config_path} -extra path: {extra_path} +extra path: None ignore merge commits: False verbosity: 1 [RULES] diff --git a/gitlint/tests/test_cli.py b/gitlint/tests/test_cli.py index c304bd65..20f15c44 100644 --- a/gitlint/tests/test_cli.py +++ b/gitlint/tests/test_cli.py @@ -106,8 +106,7 @@ def test_config_file_debug(self, _git_linter): config_path = self.get_sample_path("config/gitlintconfig") result = self.cli.invoke(cli.cli, ["--config", config_path, "--debug"]) self.assertEqual(result.exit_code, 0) - expected = self.get_expected('debug_output1', {'config_path': config_path, - 'extra_path': os.path.abspath(os.getcwd())}) + expected = self.get_expected('debug_output1', {'config_path': config_path}) self.assertEqual(result.output, expected) def test_config_file_negative(self): diff --git a/gitlint/tests/test_config.py b/gitlint/tests/test_config.py index 80b0eb30..7e0ceca2 100644 --- a/gitlint/tests/test_config.py +++ b/gitlint/tests/test_config.py @@ -85,6 +85,9 @@ def test_set_general_option(self): # extra_path config.set_general_option("extra-path", self.get_rule_rules_path()) self.assertEqual(config.extra_path, self.get_rule_rules_path()) + # reset value (this is a different code path) + config.set_general_option("extra-path", self.SAMPLES_DIR) + self.assertEqual(config.extra_path, self.SAMPLES_DIR) def test_set_general_option_negative(self): config = LintConfig() diff --git a/qa/expected/debug_output1 b/qa/expected/debug_output1 index c8daa0a0..e9ac180a 100644 --- a/qa/expected/debug_output1 +++ b/qa/expected/debug_output1 @@ -1,6 +1,6 @@ [GENERAL] config path: /vagrant/qa/samples/config/gitlintconfig -extra path: {extra_path} +extra path: None ignore merge commits: True verbosity: 2 [RULES] diff --git a/qa/test_config.py b/qa/test_config.py index 1d1eda0c..47f08df5 100644 --- a/qa/test_config.py +++ b/qa/test_config.py @@ -70,8 +70,7 @@ def test_config_from_file_debug(self): config_path = self.get_sample_path("config/gitlintconfig") output = gitlint("--config", config_path, "--debug", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) - expected = self.get_expected('debug_output1', {'config_path': config_path, - 'extra_path': self.tmp_git_repo}) + expected = self.get_expected('debug_output1', {'config_path': config_path}) # TODO(jroovers): test for trailing whitespace -> git automatically strips whitespace when passing # taking a commit message via 'git commit -m'