summaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docs/configuration.md628
-rw-r--r--docs/contrib_rules.md83
-rw-r--r--docs/contributing.md213
-rw-r--r--docs/demos/asciicinema.json3798
-rw-r--r--docs/demos/scenario.txt75
-rw-r--r--docs/extra.css12
-rw-r--r--docs/extra.js5
-rw-r--r--docs/images/RuleViolation.pngbin0 -> 27806 bytes
-rw-r--r--docs/images/RuleViolations.grafflebin0 -> 3291 bytes
-rw-r--r--docs/images/dev-container.pngbin0 -> 212226 bytes
-rw-r--r--docs/images/gitlint-packages.drawio.svg351
-rw-r--r--docs/images/gitlint-packages.pngbin0 -> 51975 bytes
-rw-r--r--docs/images/readme-gitlint.pngbin0 -> 348007 bytes
-rw-r--r--docs/index.md506
-rw-r--r--docs/rules.md461
-rw-r--r--docs/user_defined_rules.md415
16 files changed, 6547 insertions, 0 deletions
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..af49d7c
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,628 @@
+# Configuration
+Gitlint can be configured through different means.
+
+## The .gitlint file
+You can modify gitlint's behavior by adding a `.gitlint` file to your git repository.
+
+Generate a default `.gitlint` config file by running:
+```sh
+gitlint generate-config
+```
+You can also use a different config file like so:
+
+```sh
+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 the `[general]` section can be found in the
+[General Configuration](configuration.md#general-configuration) section of this page.
+
+```ini
+# Edit this file as you like.
+#
+# All these sections are optional. Each section with the exception of [general] represents
+# one rule and each key in it is an option for that specific rule.
+#
+# Rules and sections can be referenced by their full name or by id. For example
+# section "[body-max-line-length]" could also be written as "[B1]". Full section names are
+# used in here for clarity.
+# Rule reference documentation: http://jorisroovers.github.io/gitlint/rules/
+#
+# Use 'gitlint generate-config' to generate a config file with all possible options
+[general]
+# Ignore certain rules (comma-separated list), you can reference them by their
+# id or by their full name
+ignore=title-trailing-punctuation, T3
+
+# verbosity should be a value between 1 and 3, the commandline -v flags take
+# precedence over this
+verbosity = 2
+
+# By default gitlint will ignore merge, revert, fixup, fixup=amend, and squash commits.
+ignore-merge-commits=true
+ignore-revert-commits=true
+ignore-fixup-commits=true
+ignore-fixup-amend-commits=true
+ignore-squash-commits=true
+
+# Ignore any data sent to gitlint via stdin
+ignore-stdin=true
+
+# Fetch additional meta-data from the local repository when manually passing a
+# commit message to gitlint via stdin or --commit-msg. Disabled by default.
+staged=true
+
+# Hard fail when the target commit range is empty. Note that gitlint will
+# already fail by default on invalid commit ranges. This option is specifically
+# to tell gitlint to fail on *valid but empty* commit ranges.
+# Disabled by default.
+fail-without-commits=true
+
+# Whether to use Python `search` instead of `match` semantics in rules that use
+# regexes. Context: https://github.com/jorisroovers/gitlint/issues/254
+# Disabled by default, but will be enabled by default in the future.
+regex-style-search=true
+
+# Enable debug mode (prints more output). Disabled by default.
+debug=true
+
+# Enable community contributed rules
+# See http://jorisroovers.github.io/gitlint/contrib_rules for details
+contrib=contrib-title-conventional-commits,CC1
+
+# 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/
+
+# This is an example of how to configure the "title-max-length" rule and
+# set the line-length it enforces to 80
+[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"
+# will not cause a violation, but "WIP: my title" will.
+words=wip
+
+[title-match-regex]
+# python like regex (https://docs.python.org/3/library/re.html) that the
+# commit-msg title must be matched to.
+# Note that the regex can contradict with other rules if not used correctly
+# (e.g. title-must-not-contain-word).
+regex=^US[0-9]*
+
+[body-max-line-length]
+line-length=120
+
+[body-min-length]
+min-length=5
+
+[body-is-missing]
+# Whether to ignore this rule on merge commits (which typically only have a title)
+# default = True
+ignore-merge-commits=false
+
+[body-changed-file-mention]
+# List of files that need to be explicitly mentioned in the body when they are changed
+# This is useful for when developers often erroneously edit certain files or git submodules.
+# By specifying this rule, developers can only change the file when they explicitly
+# reference it in the commit message.
+files=gitlint-core/gitlint/rules.py,README.md
+
+[body-match-regex]
+# python-style regex that the commit-msg body must match.
+# E.g. body must end in My-Commit-Tag: foo
+regex=My-Commit-Tag: foo$
+
+[author-valid-email]
+# python like regex (https://docs.python.org/3/library/re.html) that the
+# commit author email address should be matched to
+# E.g.: For example, use the following regex if you only want to allow email
+# addresses from foo.com
+regex=[^@]+@foo.com
+
+[ignore-by-title]
+# Ignore certain rules for commits of which the title matches a regex
+# E.g. Match commit titles that start with "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
+
+[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
+
+[ignore-body-lines]
+# Ignore certain lines in a commit body that match a regex.
+# E.g. Ignore all lines that start with 'Co-Authored-By'
+regex=^Co-Authored-By
+
+[ignore-by-author-name]
+# Ignore certain rules for commits of which the author name matches a regex
+# E.g. Match commits made by dependabot
+regex=(.*)dependabot(.*)
+
+# 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
+
+# This is a contrib rule - a community contributed rule. These are disabled by default.
+# You need to explicitly enable them one-by-one by adding them to the "contrib" option
+# under [general] section above.
+[contrib-title-conventional-commits]
+# Specify allowed commit types. For details see: https://www.conventionalcommits.org/
+types = bugfix,user-story,epic
+```
+
+## Commandline config
+
+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
+```
+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.
+
+## Commit specific config
+
+You can also configure gitlint by adding specific lines to your commit message.
+For now, we only support ignoring commits by adding `gitlint-ignore: all` to the commit
+message like so:
+
+```
+WIP: This is my commit message
+
+I want gitlint to ignore this entire commit message.
+gitlint-ignore: all
+```
+
+`gitlint-ignore: all` can occur on any line, as long as it is at the start of the line.
+
+You can also specify specific rules to be ignored as follows:
+```
+WIP: This is my commit message
+
+I want gitlint to ignore this entire commit message.
+gitlint-ignore: T1, body-hard-tab
+```
+
+
+
+## Configuration precedence
+gitlint configuration is applied in the following order of precedence:
+
+1. Commit specific config (e.g.: `gitlint-ignore: all` in the commit message)
+2. Configuration Rules (e.g.: [ignore-by-title](rules.md#i1-ignore-by-title))
+3. Commandline convenience flags (e.g.: `-vv`, `--silent`, `--ignore`)
+4. Environment variables (e.g.: `GITLINT_VERBOSITY=3`)
+5. Commandline configuration flags (e.g.: `-c title-max-length=123`)
+6. Configuration file (local `.gitlint` file, or file specified using `-C`/`--config`)
+7. Default gitlint config
+
+## General Options
+Below we outline all 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.
+
+### silent
+
+Enable silent mode (no output). Use [exit](index.md#exit-codes) code to determine result.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| `False` | >= 0.1.0 | `--silent` | `GITLINT_SILENT` |
+
+#### Examples
+```sh
+# CLI
+gitlint --silent
+GITLINT_SILENT=1 gitlint # using env variable
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### verbosity
+
+Amount of output gitlint will show when printing errors.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| 3 | >= 0.1.0 | `-v` | `GITLINT_VERBOSITY` |
+
+
+#### Examples
+```sh
+# CLI
+gitlint -vvv # default (level 3)
+gitlint -vv # less output (level 2)
+gitlint -v # even less (level 1)
+gitlint --silent # no output (level 0)
+gitlint -c general.verbosity=1 # Set specific level
+gitlint -c general.verbosity=0 # Same as --silent
+GITLINT_VERBOSITY=2 gitlint # using env variable
+```
+```ini
+# .gitlint
+[general]
+verbosity=2
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore
+
+Comma separated list of rules to ignore (by name or id).
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ---------------- | --------------- | ---------------- | -------------------- |
+| [] (=empty list) | >= 0.1.0 | `--ignore` | `GITLINT_IGNORE` |
+
+#### Examples
+```sh
+# CLI
+gitlint --ignore=body-min-length # ignore single rule
+gitlint --ignore=T1,body-min-length # ignore multiple rule
+gitlint -c general.ignore=T1,body-min-length # different way of doing the same
+GITLINT_IGNORE=T1,body-min-length gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+ignore=T1,body-min-length
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### debug
+
+Enable debugging output.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| false | >= 0.7.1 | `--debug` | `GITLINT_DEBUG` |
+
+#### Examples
+```sh
+# CLI
+gitlint --debug
+GITLINT_DEBUG=1 gitlint # using env variable
+# --debug is special, the following does NOT work
+# gitlint -c general.debug=true
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### target
+
+Target git repository gitlint should be linting against.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| (empty) | >= 0.8.0 | `--target` | `GITLINT_TARGET` |
+
+#### Examples
+```sh
+# CLI
+gitlint --target=/home/joe/myrepo/
+gitlint -c general.target=/home/joe/myrepo/ # different way of doing the same
+GITLINT_TARGET=/home/joe/myrepo/ gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+target=/home/joe/myrepo/
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### config
+
+Path where gitlint looks for a config file.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| `.gitlint` | >= 0.1.0 | `--config` | `GITLINT_CONFIG` |
+
+#### Examples
+```sh
+gitlint --config=/home/joe/gitlint.ini
+gitlint -C /home/joe/gitlint.ini # different way of doing the same
+GITLINT_CONFIG=/home/joe/gitlint.ini # using env variable
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### extra-path
+
+Path where gitlint looks for [user-defined rules](user_defined_rules.md).
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| (empty) | >= 0.8.0 | `--extra-path` | `GITLINT_EXTRA_PATH` |
+
+#### Examples
+```sh
+# CLI
+gitlint --extra-path=/home/joe/rules/
+gitlint -c general.extra-path=/home/joe/rules/ # different way of doing the same
+GITLINT_EXTRA_PATH=/home/joe/rules/ gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+extra-path=/home/joe/rules/
+```
+------------------------------------------------------------------------------------------------------------------------
+### contrib
+
+Comma-separated list of [Contrib rules](contrib_rules.md) to enable (by name or id).
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| (empty) | >= 0.12.0 | `--contrib` | `GITLINT_CONTRIB` |
+
+#### Examples
+```sh
+# CLI
+gitlint --contrib=contrib-title-conventional-commits,CC1
+# different way of doing the same
+gitlint -c general.contrib=contrib-title-conventional-commits,CC1
+# using env variable
+GITLINT_CONTRIB=contrib-title-conventional-commits,CC1 gitlint
+```
+```ini
+#.gitlint
+[general]
+contrib=contrib-title-conventional-commits,CC1
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### staged
+
+Attempt smart guesses about meta info (like author name, email, branch, changed files, etc) when manually passing a
+commit message to gitlint via stdin or `--commit-msg`.
+
+Since in such cases no actual git commit exists (yet) for the message being linted, gitlint
+needs to apply some heuristics (like checking `git config` and any staged changes) to make a smart guess about what the
+likely author name, email, commit date, changed files and branch of the ensuing commit would be.
+
+When not using the `--staged` flag while linting a commit message via stdin or `--commit-msg`, gitlint will only have
+access to the commit message itself for linting and won't be able to enforce rules like
+[M1:author-valid-email](rules.md#m1-author-valid-email).
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| false | >= 0.13.0 | `--staged` | `GITLINT_STAGED` |
+
+#### Examples
+```sh
+# CLI
+gitlint --staged
+gitlint -c general.staged=true # different way of doing the same
+GITLINT_STAGED=1 gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+staged=true
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### fail-without-commits
+
+Hard fail when the target commit range is empty. Note that gitlint will
+already fail by default on invalid commit ranges. This option is specifically
+to tell gitlint to fail on **valid but empty** commit ranges.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ------------------------ | ------------------------------ |
+| false | >= 0.15.2 | `--fail-without-commits` | `GITLINT_FAIL_WITHOUT_COMMITS` |
+
+#### Examples
+```sh
+# CLI
+# The following will cause gitlint to hard fail (i.e. exit code > 0)
+# since HEAD..HEAD is a valid but empty commit range.
+gitlint --fail-without-commits --commits HEAD..HEAD
+GITLINT_FAIL_WITHOUT_COMMITS=1 gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+fail-without-commits=true
+```
+
+---
+### regex-style-search
+
+Whether to use Python `re.search()` instead of `re.match()` semantics in all built-in rules that use regular expressions.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| false | >= 0.18.0 | Not Available | Not Available |
+
+!!! important
+ At this time, `regex-style-search` is **disabled** by default, but it will be **enabled** by default in the future.
+
+
+
+Gitlint will log a warning when you're using a rule that uses a custom regex and this option is not enabled:
+
+```plain
+WARNING: I1 - ignore-by-title: gitlint will be switching from using Python regex 'match' (match beginning) to
+'search' (match anywhere) semantics. Please review your ignore-by-title.regex option accordingly.
+To remove this warning, set general.regex-style-search=True.
+More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
+```
+
+*If you don't have any custom regex specified, gitlint will not log a warning and no action is needed.*
+
+**To remove the warning:**
+
+1. Review your regex in the rules gitlint warned for and ensure it's still accurate when using [`re.search()` semantics](https://docs.python.org/3/library/re.html#search-vs-match).
+2. Enable `regex-style-search` in your `.gitlint` file (or using [any other way to configure gitlint](http://127.0.0.1:8000/gitlint/configuration/)):
+
+```ini
+[general]
+regex-style-search=true
+```
+
+#### More context
+Python offers [two different primitive operations based on regular expressions](https://docs.python.org/3/library/re.html#search-vs-match):
+`re.match()` checks for a match only at the beginning of the string, while `re.search()` checks for a match anywhere
+in the string.
+
+
+
+Most rules in gitlint already use `re.search()` instead of `re.match()`, but there's a few notable exceptions that
+use `re.match()`, which can lead to unexpected matching behavior.
+
+- M1 - author-valid-email
+- I1 - ignore-by-title
+- I2 - ignore-by-body
+- I3 - ignore-body-lines
+- I4 - ignore-by-author-name
+
+The `regex-style-search` option is meant to fix this inconsistency. Setting it to `true` will force the above rules to
+use `re.search()` instead of `re.match()`. For detailed context, see [issue #254](https://github.com/jorisroovers/gitlint/issues/254).
+
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.regex-style-search=true
+```
+```ini
+#.gitlint
+[general]
+regex-style-search=true
+```
+------------------------------------------------------------------------------------------------------------------------
+### ignore-stdin
+
+Ignore any stdin data. Sometimes useful when running gitlint in a CI server.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | ---------------------- |
+| false | >= 0.12.0 | `--ignore-stdin` | `GITLINT_IGNORE_STDIN` |
+
+#### Examples
+```sh
+# CLI
+gitlint --ignore-stdin
+gitlint -c general.ignore-stdin=true # different way of doing the same
+GITLINT_IGNORE_STDIN=1 gitlint # using env variable
+```
+```ini
+#.gitlint
+[general]
+ignore-stdin=true
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore-merge-commits
+
+Whether or not to ignore merge commits.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| true | >= 0.7.0 | Not Available | Not Available |
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.ignore-merge-commits=false
+```
+```ini
+#.gitlint
+[general]
+ignore-merge-commits=false
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore-revert-commits
+
+Whether or not to ignore revert commits.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| true | >= 0.13.0 | Not Available | Not Available |
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.ignore-revert-commits=false
+```
+```ini
+#.gitlint
+[general]
+ignore-revert-commits=false
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore-fixup-commits
+
+Whether or not to ignore [fixup](https://git-scm.com/docs/git-commit#git-commit---fixupltcommitgt) commits.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| true | >= 0.9.0 | Not Available | Not Available |
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.ignore-fixup-commits=false
+```
+```ini
+#.gitlint
+[general]
+ignore-fixup-commits=false
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore-fixup-amend-commits
+
+Whether or not to ignore [fixup=amend](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---fixupamendrewordltcommitgt) commits.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| true | >= 0.18.0 | Not Available | Not Available |
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.ignore-fixup-amend-commits=false
+```
+```ini
+#.gitlint
+[general]
+ignore-fixup-amend-commits=false
+```
+------------------------------------------------------------------------------------------------------------------------
+
+### ignore-squash-commits
+
+Whether or not to ignore [squash](https://git-scm.com/docs/git-commit#git-commit---squashltcommitgt) commits.
+
+| Default value | gitlint version | commandline flag | environment variable |
+| ------------- | --------------- | ---------------- | -------------------- |
+| true | >= 0.9.0 | Not Available | Not Available |
+
+#### Examples
+```sh
+# CLI
+gitlint -c general.ignore-squash-commits=false
+```
+```ini
+#.gitlint
+[general]
+ignore-squash-commits=false
+``` \ No newline at end of file
diff --git a/docs/contrib_rules.md b/docs/contrib_rules.md
new file mode 100644
index 0000000..e085f23
--- /dev/null
+++ b/docs/contrib_rules.md
@@ -0,0 +1,83 @@
+# Using Contrib Rules
+
+_Introduced in gitlint v0.12.0_
+
+Contrib rules are community-**contrib**uted rules that are disabled by default, but can be enabled through configuration.
+
+Contrib rules are meant to augment default gitlint behavior by providing users with rules for common use-cases without
+forcing these rules on all gitlint users. This also means that users don't have to
+re-implement these commonly used rules themselves as [user-defined](user_defined_rules.md) rules.
+
+To enable certain contrib rules, you can use the `--contrib` flag.
+```sh
+$ cat examples/commit-message-1 | gitlint --contrib contrib-title-conventional-commits,CC1
+1: CC1 Body does not contain a 'Signed-off-by' line
+1: CL1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test: "WIP: This is the title of a commit message."
+
+# These are the default violations
+1: T3 Title has trailing punctuation (.): "WIP: This is the title of a commit message."
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: This is the title of a commit message."
+2: B4 Second line is not empty: "The second line should typically be empty"
+3: B1 Line exceeds max length (123>80): "Lines typically need to have a max length, meaning that they can't exceed a preset number of characters, usually 80 or 120."
+```
+
+Same thing using a `.gitlint` file:
+
+```ini
+[general]
+# You HAVE to add the rule here to enable it, only configuring (such as below)
+# does NOT enable it.
+contrib=contrib-title-conventional-commits,CC1
+
+
+[contrib-title-conventional-commits]
+# Specify allowed commit types. For details see: https://www.conventionalcommits.org/
+types = bugfix,user-story,epic
+```
+
+You can also configure contrib rules using [any of the other ways to configure gitlint](configuration.md).
+
+## Available Contrib Rules
+
+ID | Name | gitlint version | Description
+------|-------------------------------------|------------------ |-------------------------------------------
+CT1 | contrib-title-conventional-commits | >= 0.12.0 | Enforces [Conventional Commits](https://www.conventionalcommits.org/) commit message style on the title.
+CC1 | contrib-body-requires-signed-off-by | >= 0.12.0 | Commit body must contain a `Signed-off-by` line.
+CC2 | contrib-disallow-cleanup-commits | >= 0.18.0 | Commit title must not contain `fixup!`, `squash!`, `amend!`.
+CC3 | contrib-allowed-authors | >= 0.18.0 | Enforce that only authors listed in the `AUTHORS` file are allowed to commit.
+
+## CT1: contrib-title-conventional-commits ##
+
+ID | Name | gitlint version | Description
+------|---------------------------------------|--------------------|-------------------------------------------
+CT1 | contrib-title-conventional-commits | >= 0.12.0 | Enforces [Conventional Commits](https://www.conventionalcommits.org/) commit message style on the title.
+
+### Options ###
+
+Name | gitlint version | Default | Description
+---------------|--------------------|--------------|----------------------------------
+types | >= 0.12.0 | `fix,feat,chore,docs,style,refactor,perf,test,revert,ci,build` | Comma separated list of allowed commit types.
+
+
+## CC1: contrib-body-requires-signed-off-by ##
+
+ID | Name | gitlint version | Description
+------|---------------------------------------|--------------------|-------------------------------------------
+CC1 | contrib-body-requires-signed-off-by | >= 0.12.0 | Commit body must contain a `Signed-off-by` line. This means, a line that starts with the `Signed-off-by` keyword.
+
+
+## CC2: contrib-disallow-cleanup-commits ##
+
+ID | Name | gitlint version | Description
+------|----------------------------------|--------------------|-------------------------------------------
+CC2 | contrib-disallow-cleanup-commits | >= 0.18.0 | Commit title must not contain `fixup!`, `squash!` or `amend!`. This means `git commit --fixup` and `git commit --squash` commits are not allowed.
+
+## CC3: contrib-allowed-authors ##
+
+ID | Name | gitlint version | Description
+------|----------------------------------|--------------------|-------------------------------------------
+CC3 | contrib-allowed-authors | >= 0.18.0 | The commit author must be listed in an `AUTHORS` file to be allowed to commit. Possible file names are also `AUTHORS.txt` and `AUTHORS.md`.
+
+## Contributing Contrib rules
+
+We'd love for you to contribute new Contrib rules to gitlint or improve existing ones! Please visit the [Contributing](contributing.md) page on how to get started.
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..d111bc6
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,213 @@
+# Contributing
+
+We'd love for you to contribute to gitlint. Thanks for your interest!
+The [source-code and issue tracker](https://github.com/jorisroovers/gitlint) are hosted on Github.
+
+!!! note
+ Often it takes a while for us (well, actually just [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 [loose project plan on github projects](https://github.com/users/jorisroovers/projects/1/), but
+ that's open to a lot of change and input.
+
+## Overall Guidelines
+
+When contributing code, please consider all the parts that are typically required:
+
+- [Unit tests](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/tests) (automatically
+ [enforced by CI](https://github.com/jorisroovers/gitlint/actions)). Please consider writing
+ new ones for your functionality, not only updating existing ones to make the build pass.
+- [Integration tests](https://github.com/jorisroovers/gitlint/tree/main/qa) (also automatically
+ [enforced by CI](https://github.com/jorisroovers/gitlint/actions)). Again, please consider writing new ones
+ for your functionality, not only updating existing ones to make the build pass.
+- [Documentation](https://github.com/jorisroovers/gitlint/tree/main/docs).
+
+Since we want to maintain a high standard of quality, all of these things will have to be done regardless before code
+can make it as part of a release. **Gitlint commits and pull requests are gated on all of our tests and checks as well as
+code-review**. If you can already include them as part of your PR, it's a huge timesaver for us
+and it's likely that your PR will be merged and released a lot sooner.
+
+!!! important
+ It's a good idea to open an issue before submitting a PR for non-trivial changes, so we can discuss what you have
+ in mind before you spend the effort. Thanks!
+
+## Releases
+Gitlint releases typically go out when there's either enough new features and fixes
+to make it worthwhile or when there's a critical fix for a bug that fundamentally breaks gitlint.
+
+While the amount of overhead of doing a release isn't huge, it's also not zero. In practice this means that it might
+take weeks or months before merged code actually gets released - we know that can be frustrating but please
+understand it's a well-considered trade-off based on available time.
+
+### Dev Builds
+While final releases are usually months apart, we do dev builds on every commit to `main`:
+
+- **gitlint**: [https://pypi.org/project/gitlint/#history](https://pypi.org/project/gitlint/#history)
+- **gitlint-core**: [https://pypi.org/project/gitlint-core/#history](https://pypi.org/project/gitlint-core/#history)
+
+It usually takes about 5 min after merging a PR to `main` for new dev builds to show up. Note that the installation
+of a recently published version can still fail for a few minutes after a new version shows up on PyPI while the package
+is replicated to all download mirrors.
+
+To install a dev build of gitlint:
+```sh
+# Find latest dev build on https://pypi.org/project/gitlint/#history
+pip install gitlint=="0.19.0.dev68"
+```
+
+
+## Environment setup
+### Local setup
+
+Gitlint uses [hatch](https://hatch.pypa.io/latest/) for project management.
+You do not need to setup a `virtualenv`, hatch will take care of that for you.
+
+```sh
+pip install hatch
+```
+
+### Github Devcontainer
+
+We provide a devcontainer on github to make it easier to get started with gitlint development using VSCode.
+
+To start one, click the plus button under the *Code* dropdown on
+[the gitlint repo on github](https://github.com/jorisroovers/gitlint).
+
+**It can take ~15min for all post installation steps to finish.**
+
+![Gitlint Dev Container Instructions](images/dev-container.png)
+
+
+By default we have python 3.11 installed in the dev container, but you can also use [asdf](https://asdf-vm.com/)
+(preinstalled) to install additional python versions:
+
+```sh
+# Ensure ASDF overrides system python in PATH
+# You can also append this line to your ~/.bash_profile in the devcontainer to have this happen automatically on login
+source "$(brew --prefix asdf)/libexec/asdf.sh"
+
+# Install python 3.9.15
+asdf install python 3.9.15
+# List all available python versions
+asdf list all python
+# List installed python versions
+asdf list python
+```
+
+## Running tests
+```sh
+# Gitlint
+hatch run dev:gitlint # run the local source copy of gitlint
+hatch run dev:gitlint --version # This is just the gitlint binary, any flag will work
+hatch run dev:gitlint --debug
+
+# Unit tests
+hatch run test:unit-tests # run unit tests
+hatch run test:unit-tests gitlint-core/gitlint/tests/rules/test_body_rules.py::BodyRuleTests::test_body_missing # run a single test
+hatch run test:unit-tests -k test_body_missing_merge_commit # Run a specific tests using a pytest keyword expression
+hatch run test:unit-tests-no-cov # run unit tests without test coverage
+
+# Integration tests
+hatch run qa:install-local # One-time install: install the local gitlint source copy for integration testing
+hatch run qa:integration-tests # Run integration tests
+
+# Formatting check (black)
+hatch run test:format # Run formatting checks
+
+# Linting (ruff)
+hatch run test:lint # Run Ruff
+
+# Project stats
+hatch run test:stats
+```
+## Autoformatting and autofixing
+
+We use [black](https://black.readthedocs.io/en/stable/) for code formatting.
+
+```sh
+hatch run test:autoformat # format all python code
+hatch run test:autoformat gitlint-core/gitlint/lint.py # format a specific file
+```
+
+We use [ruff](https://github.com/charliermarsh/ruff) for linting, it can autofix many of the issue it finds
+(although not always perfect).
+```sh
+hatch run test:autofix # Attempt to fix linting issues
+```
+
+## Documentation
+We use [mkdocs](https://www.mkdocs.org/) for generating our documentation from markdown.
+
+To use it:
+```sh
+hatch run docs:serve
+```
+
+Then access the documentation website on [http://localhost:8000]().
+
+## Packaging
+
+Gitlint consists of 2 python packages: [gitlint](https://pypi.org/project/gitlint/)
+and [gitlint-core](https://pypi.org/project/gitlint-core/).
+
+The `gitlint` package is just a wrapper package around `gitlint-core[trusted-deps]` which strictly pins gitlint
+dependencies to known working versions.
+
+There are scenarios where users (or OS package managers) may want looser dependency requirements.
+In these cases, users can just install `gitlint-core` directly (`pip install gitlint-core`).
+
+[Issue 162](https://github.com/jorisroovers/gitlint/issues/162) has all the background of how we got to the decision
+to split gitlint in 2 packages.
+
+![Gitlint package structure](images/gitlint-packages.png)
+
+To build the packages locally:
+```sh
+# gitlint
+hatch build
+hatch clean # cleanup
+
+# gitlint-core
+cd gitlint-core
+hatch build
+hatch clean # cleanup
+```
+
+## Tools
+We keep a small set of scripts in the `tools/` directory:
+
+```sh
+tools/create-test-repo.sh # Create a test git repo in your /tmp directory
+tools/windows/create-test-repo.bat # Windows: create git test repo
+tools/windows/run_tests.bat # Windows run unit tests
+```
+
+## Contrib rules
+Since gitlint 0.12.0, we support [Contrib rules](contrib_rules.md): community contributed rules that are part of gitlint
+itself. Thanks for considering to add a new one to gitlint!
+
+Before starting, please read all the other documentation on this page about contributing first.
+Then, we suggest taking the following approach to add a Contrib rule:
+
+1. **Write your rule as a [user-defined rule](user_defined_rules.md)**. In terms of code, Contrib rules are identical to
+ user-defined rules, they just happen to have their code sit within the gitlint codebase itself.
+2. **Add your user-defined rule to gitlint**. You should put your file(s) in the [gitlint/contrib/rules](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/contrib/rules) directory.
+3. **Write unit tests**. The gitlint codebase contains [Contrib rule test files you can copy and modify](https://github.com/jorisroovers/gitlint/tree/main/gitlint-core/gitlint/tests/contrib/rules).
+4. **Write documentation**. In particular, you should update the [gitlint/docs/contrib_rules.md](https://github.com/jorisroovers/gitlint/blob/main/docs/contrib_rules.md) file with details on your Contrib rule.
+5. **Create a Pull Request**: code review typically requires a bit of back and forth. Thanks for your contribution!
+
+
+### Contrib rule requirements
+If you follow the steps above and follow the existing gitlint conventions wrt naming things, you should already be fairly close to done.
+
+In case you're looking for a slightly more formal spec, here's what gitlint requires of Contrib rules.
+
+- Since Contrib rules are really just user-defined rules that live within the gitlint code-base, all the [user-rule requirements](user_defined_rules.md#rule-requirements) also apply to Contrib rules.
+- All contrib rules **must** have associated unit tests. We *sort of* enforce this by a unit test that verifies that there's a
+ test file for each contrib file.
+- All contrib rules **must** have names that start with `contrib-`. This is to easily distinguish them from default gitlint rules.
+- All contrib rule ids **must** start with `CT` (for LineRules targeting the title), `CB` (for LineRules targeting the body) or `CC` (for CommitRules). Again, this is to easily distinguish them from default gitlint rules.
+- All contrib rules **must** have unique names and ids.
+- You **can** add multiple rule classes to the same file, but classes **should** be logically grouped together in a single file that implements related rules.
+- Contrib rules **should** be meaningfully different from one another. If a behavior change or tweak can be added to an existing rule by adding options, that should be considered first. However, large [god classes](https://en.wikipedia.org/wiki/God_object) that implement multiple rules in a single class should obviously also be avoided.
+- Contrib rules **should** use [options](user_defined_rules.md#options) to make rules configurable.
diff --git a/docs/demos/asciicinema.json b/docs/demos/asciicinema.json
new file mode 100644
index 0000000..a5664c7
--- /dev/null
+++ b/docs/demos/asciicinema.json
@@ -0,0 +1,3798 @@
+{
+ "version": 1,
+ "width": 102,
+ "height": 28,
+ "duration": 161.307896,
+ "command": "/bin/bash",
+ "title": "",
+ "env": {
+ "TERM": "xterm-256color",
+ "SHELL": "/bin/bash"
+ },
+ "stdout": [
+ [
+ 0.007348,
+ "\u001b[?1034h"
+ ],
+ [
+ 0.000015,
+ "bash-3.2$ "
+ ],
+ [
+ 0.504301,
+ "#"
+ ],
+ [
+ 0.139436,
+ " "
+ ],
+ [
+ 0.324556,
+ "I"
+ ],
+ [
+ 0.088019,
+ "n"
+ ],
+ [
+ 0.104007,
+ "s"
+ ],
+ [
+ 0.079986,
+ "t"
+ ],
+ [
+ 0.056291,
+ "a"
+ ],
+ [
+ 0.063684,
+ "l"
+ ],
+ [
+ 0.136015,
+ "l"
+ ],
+ [
+ 0.047705,
+ " "
+ ],
+ [
+ 0.144308,
+ "g"
+ ],
+ [
+ 0.087760,
+ "i"
+ ],
+ [
+ 0.088234,
+ "t"
+ ],
+ [
+ 0.119918,
+ "l"
+ ],
+ [
+ 0.031966,
+ "i"
+ ],
+ [
+ 0.056016,
+ "n"
+ ],
+ [
+ 0.104074,
+ "t"
+ ],
+ [
+ 0.151839,
+ "\r\n"
+ ],
+ [
+ 0.000117,
+ "bash-3.2$ "
+ ],
+ [
+ 0.247690,
+ "p"
+ ],
+ [
+ 0.064297,
+ "i"
+ ],
+ [
+ 0.119980,
+ "p"
+ ],
+ [
+ 0.112350,
+ " "
+ ],
+ [
+ 0.119395,
+ "i"
+ ],
+ [
+ 0.055802,
+ "n"
+ ],
+ [
+ 0.064480,
+ "s"
+ ],
+ [
+ 0.048012,
+ "t"
+ ],
+ [
+ 0.039930,
+ "a"
+ ],
+ [
+ 0.071932,
+ "l"
+ ],
+ [
+ 0.152065,
+ "l"
+ ],
+ [
+ 0.432253,
+ " "
+ ],
+ [
+ 0.143697,
+ "g"
+ ],
+ [
+ 0.056276,
+ "i"
+ ],
+ [
+ 0.127369,
+ "t"
+ ],
+ [
+ 0.104317,
+ "l"
+ ],
+ [
+ 0.039881,
+ "i"
+ ],
+ [
+ 0.072170,
+ "n"
+ ],
+ [
+ 0.119946,
+ "t"
+ ],
+ [
+ 0.168100,
+ "\r\n"
+ ],
+ [
+ 0.179873,
+ "Collecting gitlint\r\n"
+ ],
+ [
+ 0.031411,
+ " Using cached gitlint-0.6.1-py2.py3-none-any.whl\r\n"
+ ],
+ [
+ 0.011427,
+ "Requirement already satisfied (use --upgrade to upgrade): sh==1.11 in ./repos/demo-env/lib/python2.7/site-packages (from gitlint)\r\n"
+ ],
+ [
+ 0.000262,
+ "Requirement already satisfied (use --upgrade to upgrade): Click==5.1 in ./repos/demo-env/lib/python2.7/site-packages (from gitlint)\r\n"
+ ],
+ [
+ 0.000334,
+ "Installing collected packages: gitlint\r\n"
+ ],
+ [
+ 0.047796,
+ "Successfully installed gitlint-0.6.1\r\n"
+ ],
+ [
+ 0.022382,
+ "bash-3.2$ "
+ ],
+ [
+ 0.762766,
+ "#"
+ ],
+ [
+ 0.151744,
+ " "
+ ],
+ [
+ 0.431785,
+ "G"
+ ],
+ [
+ 0.095891,
+ "o"
+ ],
+ [
+ 0.192284,
+ " "
+ ],
+ [
+ 0.184164,
+ "t"
+ ],
+ [
+ 0.039770,
+ "o"
+ ],
+ [
+ 0.127949,
+ " "
+ ],
+ [
+ 0.232071,
+ "y"
+ ],
+ [
+ 0.071710,
+ "o"
+ ],
+ [
+ 0.023881,
+ "u"
+ ],
+ [
+ 0.184228,
+ "r"
+ ],
+ [
+ 0.144517,
+ " "
+ ],
+ [
+ 0.159631,
+ "g"
+ ],
+ [
+ 0.087950,
+ "i"
+ ],
+ [
+ 0.087976,
+ "t"
+ ],
+ [
+ 0.136095,
+ " "
+ ],
+ [
+ 0.183896,
+ "r"
+ ],
+ [
+ 0.047895,
+ "e"
+ ],
+ [
+ 0.072082,
+ "p"
+ ],
+ [
+ 0.072384,
+ "o"
+ ],
+ [
+ 0.359651,
+ "\r\n"
+ ],
+ [
+ 0.000096,
+ "bash-3.2$ "
+ ],
+ [
+ 0.463951,
+ "c"
+ ],
+ [
+ 0.463994,
+ "d"
+ ],
+ [
+ 0.079579,
+ " "
+ ],
+ [
+ 0.192355,
+ "m"
+ ],
+ [
+ 0.183732,
+ "\u0007"
+ ],
+ [
+ 0.496586,
+ "y"
+ ],
+ [
+ 0.175813,
+ "-git-repo/"
+ ],
+ [
+ 0.455841,
+ "\r\n"
+ ],
+ [
+ 0.000186,
+ "bash-3.2$ "
+ ],
+ [
+ 1.791755,
+ "#"
+ ],
+ [
+ 0.255933,
+ " "
+ ],
+ [
+ 0.296001,
+ "R"
+ ],
+ [
+ 0.159913,
+ "u"
+ ],
+ [
+ 0.064074,
+ "n"
+ ],
+ [
+ 0.175969,
+ " "
+ ],
+ [
+ 0.352173,
+ "g"
+ ],
+ [
+ 0.079863,
+ "i"
+ ],
+ [
+ 0.095948,
+ "t"
+ ],
+ [
+ 0.111985,
+ "l"
+ ],
+ [
+ 0.040922,
+ "i"
+ ],
+ [
+ 0.055153,
+ "n"
+ ],
+ [
+ 0.095944,
+ "t"
+ ],
+ [
+ 0.096118,
+ " "
+ ],
+ [
+ 0.095832,
+ "t"
+ ],
+ [
+ 0.024113,
+ "o"
+ ],
+ [
+ 0.144015,
+ " "
+ ],
+ [
+ 0.455972,
+ "c"
+ ],
+ [
+ 0.120097,
+ "h"
+ ],
+ [
+ 0.000354,
+ "e"
+ ],
+ [
+ 0.103450,
+ "c"
+ ],
+ [
+ 0.104258,
+ "k"
+ ],
+ [
+ 0.127354,
+ " "
+ ],
+ [
+ 0.536192,
+ "y"
+ ],
+ [
+ 0.088300,
+ "o"
+ ],
+ [
+ 0.055953,
+ "u"
+ ],
+ [
+ 0.111366,
+ "r"
+ ],
+ [
+ 0.136559,
+ " "
+ ],
+ [
+ 0.207924,
+ "l"
+ ],
+ [
+ 0.056410,
+ "a"
+ ],
+ [
+ 0.095988,
+ "s"
+ ],
+ [
+ 0.087665,
+ "t"
+ ],
+ [
+ 0.112209,
+ " "
+ ],
+ [
+ 0.215799,
+ "c"
+ ],
+ [
+ 0.015909,
+ "o"
+ ],
+ [
+ 0.159743,
+ "m"
+ ],
+ [
+ 0.200550,
+ "m"
+ ],
+ [
+ 0.135722,
+ "i"
+ ],
+ [
+ 0.120069,
+ "t"
+ ],
+ [
+ 0.087796,
+ " "
+ ],
+ [
+ 0.152194,
+ "m"
+ ],
+ [
+ 0.096258,
+ "e"
+ ],
+ [
+ 0.200052,
+ "s"
+ ],
+ [
+ 0.167944,
+ "s"
+ ],
+ [
+ 0.079747,
+ "a"
+ ],
+ [
+ 0.079582,
+ "g"
+ ],
+ [
+ 0.120304,
+ "e"
+ ],
+ [
+ 0.039891,
+ " "
+ ],
+ [
+ 0.208356,
+ "f"
+ ],
+ [
+ 0.071741,
+ "o"
+ ],
+ [
+ 0.080006,
+ "r"
+ ],
+ [
+ 0.119789,
+ " "
+ ],
+ [
+ 0.144509,
+ "s"
+ ],
+ [
+ 0.128456,
+ "t"
+ ],
+ [
+ 0.103452,
+ "y"
+ ],
+ [
+ 0.104515,
+ "l"
+ ],
+ [
+ 0.143681,
+ "e"
+ ],
+ [
+ 0.368030,
+ "\r\n"
+ ],
+ [
+ 0.000109,
+ "bash-3.2$ "
+ ],
+ [
+ 0.463969,
+ "g"
+ ],
+ [
+ 0.080036,
+ "i"
+ ],
+ [
+ 0.143920,
+ "t"
+ ],
+ [
+ 0.120008,
+ "l"
+ ],
+ [
+ 0.040025,
+ "i"
+ ],
+ [
+ 0.072262,
+ "n"
+ ],
+ [
+ 0.087179,
+ "t"
+ ],
+ [
+ 0.560443,
+ "\r\n"
+ ],
+ [
+ 0.123301,
+ "1: T3 Title has trailing punctuation (.): \"WIP: This is a commit message title.\"\r\n1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is a commit message title.\"\r\n"
+ ],
+ [
+ 0.000027,
+ "2: B4 Second line is not empty: \"Second line not empty\"\r\n3: B1 Line exceeds max length (97\u003e80): \"This body line exceeds the defacto standard length of 80 characters per line in a commit message.\"\r\n"
+ ],
+ [
+ 0.005792,
+ "bash-3.2$ "
+ ],
+ [
+ 2.814656,
+ "#"
+ ],
+ [
+ 0.376209,
+ " "
+ ],
+ [
+ 0.167631,
+ "F"
+ ],
+ [
+ 0.104242,
+ "o"
+ ],
+ [
+ 0.112316,
+ "r"
+ ],
+ [
+ 0.103655,
+ " "
+ ],
+ [
+ 0.112300,
+ "r"
+ ],
+ [
+ 0.079789,
+ "e"
+ ],
+ [
+ 0.111886,
+ "f"
+ ],
+ [
+ 0.096013,
+ "e"
+ ],
+ [
+ 0.080098,
+ "r"
+ ],
+ [
+ 0.087892,
+ "e"
+ ],
+ [
+ 0.064119,
+ "n"
+ ],
+ [
+ 0.192204,
+ "c"
+ ],
+ [
+ 0.112131,
+ "e"
+ ],
+ [
+ 0.375630,
+ ","
+ ],
+ [
+ 0.056176,
+ " "
+ ],
+ [
+ 0.239821,
+ "h"
+ ],
+ [
+ 0.047836,
+ "e"
+ ],
+ [
+ 0.080354,
+ "r"
+ ],
+ [
+ 0.080001,
+ "e"
+ ],
+ [
+ 0.095738,
+ "'"
+ ],
+ [
+ 0.216132,
+ "s"
+ ],
+ [
+ 0.071779,
+ " "
+ ],
+ [
+ 0.128086,
+ "t"
+ ],
+ [
+ 0.104034,
+ "h"
+ ],
+ [
+ 0.055946,
+ "a"
+ ],
+ [
+ 0.072018,
+ "t"
+ ],
+ [
+ 0.128240,
+ " "
+ ],
+ [
+ 0.239827,
+ "l"
+ ],
+ [
+ 0.080168,
+ "a"
+ ],
+ [
+ 0.055749,
+ "s"
+ ],
+ [
+ 0.103959,
+ "t"
+ ],
+ [
+ 0.080368,
+ " "
+ ],
+ [
+ 0.168008,
+ "c"
+ ],
+ [
+ 0.047675,
+ "o"
+ ],
+ [
+ 0.199913,
+ "m"
+ ],
+ [
+ 0.168041,
+ "m"
+ ],
+ [
+ 0.184377,
+ "i"
+ ],
+ [
+ 0.111843,
+ "t"
+ ],
+ [
+ 0.104075,
+ " "
+ ],
+ [
+ 0.119731,
+ "m"
+ ],
+ [
+ 0.079482,
+ "e"
+ ],
+ [
+ 0.216511,
+ "s"
+ ],
+ [
+ 0.167541,
+ "s"
+ ],
+ [
+ 0.176420,
+ "a"
+ ],
+ [
+ 0.487983,
+ "g"
+ ],
+ [
+ 0.063926,
+ "e"
+ ],
+ [
+ 0.240043,
+ "\r\n"
+ ],
+ [
+ 0.000103,
+ "bash-3.2$ "
+ ],
+ [
+ 0.303813,
+ "g"
+ ],
+ [
+ 0.088101,
+ "i"
+ ],
+ [
+ 0.095792,
+ "t"
+ ],
+ [
+ 0.535908,
+ " "
+ ],
+ [
+ 0.080185,
+ "l"
+ ],
+ [
+ 0.112012,
+ "o"
+ ],
+ [
+ 0.056328,
+ "g"
+ ],
+ [
+ 0.127542,
+ " "
+ ],
+ [
+ 0.136200,
+ "-"
+ ],
+ [
+ 0.143844,
+ "1"
+ ],
+ [
+ 0.416074,
+ "\r\n"
+ ],
+ [
+ 0.012270,
+ "\u001b[?1h\u001b=\r"
+ ],
+ [
+ 0.000209,
+ "\u001b[33mcommit c8ad52bbf7386d2e6ca39e479456a8bfae086629\u001b[m\u001b[m\r\nAuthor: Joris Roovers \u003cjroovers@cisco.com\u003e\u001b[m\r\nDate: Sun Nov 22 17:31:49 2015 +0100\u001b[m\r\n\u001b[m\r\n WIP: This is a commit message title.\u001b[m\r\n Second line not empty\u001b[m\r\n This body line exceeds the defacto standard length of 80 characters per line in a commit message.\u001b[m\r\n\r\u001b[K\u001b[?1l\u001b\u003e"
+ ],
+ [
+ 0.000643,
+ "bash-3.2$ "
+ ],
+ [
+ 1.618373,
+ "#"
+ ],
+ [
+ 0.152173,
+ " "
+ ],
+ [
+ 0.296271,
+ "Y"
+ ],
+ [
+ 0.143886,
+ "o"
+ ],
+ [
+ 0.032116,
+ "u"
+ ],
+ [
+ 0.255972,
+ " "
+ ],
+ [
+ 0.056004,
+ "c"
+ ],
+ [
+ 0.080252,
+ "a"
+ ],
+ [
+ 0.095730,
+ "n"
+ ],
+ [
+ 0.136000,
+ " "
+ ],
+ [
+ 0.112007,
+ "a"
+ ],
+ [
+ 0.119993,
+ "l"
+ ],
+ [
+ 0.104070,
+ "s"
+ ],
+ [
+ 0.095935,
+ "o"
+ ],
+ [
+ 0.191605,
+ " "
+ ],
+ [
+ 0.456320,
+ "i"
+ ],
+ [
+ 0.032035,
+ "n"
+ ],
+ [
+ 0.071923,
+ "s"
+ ],
+ [
+ 0.080070,
+ "t"
+ ],
+ [
+ 0.079964,
+ "a"
+ ],
+ [
+ 0.088007,
+ "l"
+ ],
+ [
+ 0.144266,
+ "l"
+ ],
+ [
+ 0.071532,
+ " "
+ ],
+ [
+ 0.424083,
+ "g"
+ ],
+ [
+ 0.064402,
+ "i"
+ ],
+ [
+ 0.119971,
+ "t"
+ ],
+ [
+ 0.087788,
+ "l"
+ ],
+ [
+ 0.047978,
+ "i"
+ ],
+ [
+ 0.055909,
+ "n"
+ ],
+ [
+ 0.104026,
+ "t"
+ ],
+ [
+ 0.079939,
+ " "
+ ],
+ [
+ 0.152052,
+ "a"
+ ],
+ [
+ 0.079983,
+ "s"
+ ],
+ [
+ 0.127987,
+ " "
+ ],
+ [
+ 0.776097,
+ "a"
+ ],
+ [
+ 0.416226,
+ " "
+ ],
+ [
+ 0.191962,
+ "c"
+ ],
+ [
+ 0.031735,
+ "o"
+ ],
+ [
+ 0.200042,
+ "m"
+ ],
+ [
+ 0.159913,
+ "m"
+ ],
+ [
+ 0.127947,
+ "i"
+ ],
+ [
+ 0.456063,
+ "t"
+ ],
+ [
+ 0.136031,
+ "-"
+ ],
+ [
+ 0.215964,
+ "m"
+ ],
+ [
+ 0.175984,
+ "s"
+ ],
+ [
+ 0.272013,
+ "g"
+ ],
+ [
+ 0.119964,
+ " "
+ ],
+ [
+ 0.184214,
+ "h"
+ ],
+ [
+ 0.192024,
+ "o"
+ ],
+ [
+ 0.119950,
+ "o"
+ ],
+ [
+ 0.047751,
+ "k"
+ ],
+ [
+ 0.431743,
+ "\r\n"
+ ],
+ [
+ 0.000085,
+ "bash-3.2$ "
+ ],
+ [
+ 0.760296,
+ "g"
+ ],
+ [
+ 0.079911,
+ "i"
+ ],
+ [
+ 0.191987,
+ "t"
+ ],
+ [
+ 0.304096,
+ "l"
+ ],
+ [
+ 0.287428,
+ "i"
+ ],
+ [
+ 0.064628,
+ "n"
+ ],
+ [
+ 0.127768,
+ "t"
+ ],
+ [
+ 0.072263,
+ " "
+ ],
+ [
+ 0.351385,
+ "i"
+ ],
+ [
+ 0.031803,
+ "n"
+ ],
+ [
+ 0.088639,
+ "s"
+ ],
+ [
+ 0.080034,
+ "t"
+ ],
+ [
+ 0.064156,
+ "a"
+ ],
+ [
+ 0.071949,
+ "l"
+ ],
+ [
+ 0.135713,
+ "l"
+ ],
+ [
+ 0.192018,
+ "-"
+ ],
+ [
+ 0.191953,
+ "h"
+ ],
+ [
+ 0.207996,
+ "o"
+ ],
+ [
+ 0.127991,
+ "o"
+ ],
+ [
+ 0.088152,
+ "k"
+ ],
+ [
+ 0.431858,
+ "\r\n"
+ ],
+ [
+ 0.072226,
+ "Successfully installed gitlint commit-msg hook in /Users/jroovers/my-git-repo/.git/hooks/commit-msg\r\n"
+ ],
+ [
+ 0.003614,
+ "bash-3.2$ "
+ ],
+ [
+ 1.036119,
+ "#"
+ ],
+ [
+ 0.160217,
+ " "
+ ],
+ [
+ 0.295771,
+ "L"
+ ],
+ [
+ 0.151619,
+ "e"
+ ],
+ [
+ 0.096263,
+ "t"
+ ],
+ [
+ 0.895797,
+ "'"
+ ],
+ [
+ 0.184596,
+ "s"
+ ],
+ [
+ 0.143677,
+ " "
+ ],
+ [
+ 0.103909,
+ "t"
+ ],
+ [
+ 0.175892,
+ "r"
+ ],
+ [
+ 0.072131,
+ "y"
+ ],
+ [
+ 0.144032,
+ " "
+ ],
+ [
+ 0.160272,
+ "i"
+ ],
+ [
+ 0.119444,
+ "t"
+ ],
+ [
+ 0.088258,
+ " "
+ ],
+ [
+ 0.207962,
+ "o"
+ ],
+ [
+ 0.056392,
+ "u"
+ ],
+ [
+ 0.103632,
+ "t"
+ ],
+ [
+ 0.552056,
+ "\r\n"
+ ],
+ [
+ 0.000096,
+ "bash-3.2$ "
+ ],
+ [
+ 0.0591595,
+ "e"
+ ],
+ [
+ 0.104138,
+ "c"
+ ],
+ [
+ 0.104065,
+ "h"
+ ],
+ [
+ 0.064048,
+ "o"
+ ],
+ [
+ 0.135782,
+ " "
+ ],
+ [
+ 0.192483,
+ "\""
+ ],
+ [
+ 0.175634,
+ "t"
+ ],
+ [
+ 0.072179,
+ "e"
+ ],
+ [
+ 0.151799,
+ "s"
+ ],
+ [
+ 0.080120,
+ "t"
+ ],
+ [
+ 0.175911,
+ "\""
+ ],
+ [
+ 0.135948,
+ " "
+ ],
+ [
+ 0.208327,
+ "\u003e"
+ ],
+ [
+ 0.079867,
+ " "
+ ],
+ [
+ 0.240416,
+ "f"
+ ],
+ [
+ 0.096300,
+ "o"
+ ],
+ [
+ 0.119709,
+ "o"
+ ],
+ [
+ 0.184111,
+ "."
+ ],
+ [
+ 0.199981,
+ "t"
+ ],
+ [
+ 0.223634,
+ "x"
+ ],
+ [
+ 0.232100,
+ "t"
+ ],
+ [
+ 0.839909,
+ "\r\n"
+ ],
+ [
+ 0.000434,
+ "bash-3.2$ "
+ ],
+ [
+ 0.743621,
+ "g"
+ ],
+ [
+ 0.047948,
+ "i"
+ ],
+ [
+ 0.103991,
+ "t"
+ ],
+ [
+ 0.088317,
+ " "
+ ],
+ [
+ 0.159935,
+ "a"
+ ],
+ [
+ 0.200067,
+ "d"
+ ],
+ [
+ 0.159339,
+ "d"
+ ],
+ [
+ 0.144280,
+ " "
+ ],
+ [
+ 0.136254,
+ "."
+ ],
+ [
+ 0.399760,
+ "\r\n"
+ ],
+ [
+ 0.010930,
+ "bash-3.2$ "
+ ],
+ [
+ 0.213093,
+ "g"
+ ],
+ [
+ 0.095974,
+ "i"
+ ],
+ [
+ 0.103967,
+ "t"
+ ],
+ [
+ 0.120050,
+ " "
+ ],
+ [
+ 0.176294,
+ "c"
+ ],
+ [
+ 0.127966,
+ "o"
+ ],
+ [
+ 0.183809,
+ "m"
+ ],
+ [
+ 0.160022,
+ "m"
+ ],
+ [
+ 0.120056,
+ "i"
+ ],
+ [
+ 0.143736,
+ "t"
+ ],
+ [
+ 1.104266,
+ "\r\n"
+ ],
+ [
+ 0.090605,
+ "\u001b[?1049h\u001b[?1h\u001b="
+ ],
+ [
+ 0.003626,
+ "\u001b[1;28r\u001b[?12;25h\u001b[?12l\u001b[?25h\u001b[27m\u001b[m\u001b[H\u001b[2J\u001b[?25l\u001b[28;1H\"~/my-git-repo/.git/COMMIT_EDITMSG\""
+ ],
+ [
+ 0.000779,
+ " 7L, 206C"
+ ],
+ [
+ 0.004938,
+ "\u001b[\u003ec"
+ ],
+ [
+ 0.002767,
+ "\u001b[1;1H\u001b[93m 1 \r\n 2 \u001b[m\u001b[96m# Please enter the commit message for your changes. Lines starting\u001b[m\r\n\u001b[93m 3 \u001b[m\u001b[96m# with '#' will be ignored, and an empty message aborts the commit.\u001b[m\r\n\u001b[93m 4 \u001b[m\u001b[96m# On branch \u001b[m\u001b[38;5;224mmain\u001b[m\r\n\u001b[93m 5 \u001b[m\u001b[96m# \u001b[m\u001b[38;5;81mChanges to be committed:\u001b[m\r\n\u001b[93m 6 \u001b[m\u001b[96m# \u001b[m\u001b[38;5;121mnew file\u001b[m\u001b[96m: \u001b[m\u001b[95m foo.txt\u001b[m\r\n\u001b[93m 7 \u001b[m\u001b[96m#\u001b[m\r\n\u001b[94m~ \u001b[9;1H~ \u001b[10;1H~ \u001b[11;1H~ \u001b[12;1H~ \u001b[13;1H~ "
+ ],
+ [
+ 0.000062,
+ " \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[17;1H~ \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ \u001b[21;1H~ \u001b[22;1H~ "
+ ],
+ [
+ 0.000865,
+ "\u001b[23;1H~ \u001b[24;1H~ \u001b[25;1H~ \u001b[26;1H~ \u001b[27;1H~ \u001b[1;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.468652,
+ "\u001b[?25l\u001b[m\u001b[28;1H\u001b[1m-- INSERT --\u001b[m\u001b[28;13H\u001b[K\u001b[1;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.292362,
+ "\u001b[?25l\u0008\u001b[93m W\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.112916,
+ "\u001b[?25l\u0008WI"
+ ],
+ [
+ 0.000464,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000027,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000025,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000084,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000029,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000004,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000273,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000004,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000920,
+ "\u001b[1;7H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.076999,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mIP\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.463368,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.352661,
+ "\u001b[?25l\u001b[m\u001b[1;8H\u001b[K"
+ ],
+ [
+ 0.000019,
+ "\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000039,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000039,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000081,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000018,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000022,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000227,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000004,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000839,
+ "\u001b[1;8H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.214580,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mP:\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.208063,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.167897,
+ "\u001b[?25l\u0008 T\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.119757,
+ "\u001b[?25l\u0008Th"
+ ],
+ [
+ 0.000048,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000014,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000022,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000070,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000016,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000022,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000225,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000031,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000752,
+ "\u001b[1;12H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.055116,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mhi\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.128002,
+ "\u001b[?25l\u0008is\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.064056,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.143867,
+ "\u001b[?25l\u0008 i\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.072155,
+ "\u001b[?25l\u0008is"
+ ],
+ [
+ 0.000057,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000040,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000019,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000100,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000040,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000005,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000279,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000004,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000905,
+ "\u001b[1;17H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.134356,
+ "\u001b[?25l\u001b[m\u001b[93m \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.072054,
+ "\u001b[?25l\u0008 a\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.520162,
+ "\u001b[?25l\u0008an"
+ ],
+ [
+ 0.000090,
+ "\u001b[m\u001b[2;17H\u001b[48;5;242m and \u001b[m\u001b[3;17H\u001b[105m an \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000021,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000023,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000074,
+ "\u001b[2;17H\u001b[105m and "
+ ],
+ [
+ 0.000043,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000355,
+ "\u001b[m\u001b[2;16H\u001b[96mter the commit me\u001b[3;16Hwill be ignored, "
+ ],
+ [
+ 0.000123,
+ "\u001b[m\u001b[28;29H\u001b[1m \u001b[m\u001b[38;5;121mmatch 1 of 2"
+ ],
+ [
+ 0.000067,
+ "\u001b[1;20H"
+ ],
+ [
+ 0.000004,
+ "\u001b[m\u001b[2;17H\u001b[48;5;242m and \u001b[m\u001b[3;17H\u001b[105m an "
+ ],
+ [
+ 0.000021,
+ "\u001b[1;20H"
+ ],
+ [
+ 0.000078,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.157947,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000033,
+ "\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000037,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000308,
+ "\u001b[m\u001b[1;20H\u001b[93m \u001b[m\u001b[2;16H\u001b[96mter the commit me\u001b[3;16Hwill be ignored, "
+ ],
+ [
+ 0.000548,
+ "\u001b[1;21H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.735999,
+ "\u001b[?25l\u001b[m\u0008\u001b[93m p\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.104200,
+ "\u001b[?25l\u0008pa"
+ ],
+ [
+ 0.000005,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000041,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000033,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000071,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000038,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000020,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000245,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000033,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000896,
+ "\u001b[1;23H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.086431,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mat\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.176505,
+ "\u001b[?25l\u0008tc\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.103498,
+ "\u001b[?25l\u0008ch\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.080001,
+ "\u001b[?25l\u0008hs\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.176470,
+ "\u001b[?25l\u0008se\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.063481,
+ "\u001b[?25l\u0008et\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.079520,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.128786,
+ "\u001b[?25l\u0008 t\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.199925,
+ "\u001b[?25l\u0008th"
+ ],
+ [
+ 0.000115,
+ "\u001b[m\u001b[2;29H\u001b[48;5;242m the \u001b[m\u001b[3;29H\u001b[105m This \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000013,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000024,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000064,
+ "\u001b[2;29H\u001b[105m the "
+ ],
+ [
+ 0.000048,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000335,
+ "\u001b[m\u001b[2;28H\u001b[96mit message for yo\u001b[3;28Hred, and an empty"
+ ],
+ [
+ 0.000100,
+ "\u001b[m\u001b[28;29H\u001b[1m \u001b[m\u001b[38;5;121mmatch 1 of 2"
+ ],
+ [
+ 0.000048,
+ "\u001b[1;32H"
+ ],
+ [
+ 0.000004,
+ "\u001b[m\u001b[2;29H\u001b[48;5;242m the \u001b[m\u001b[3;29H\u001b[105m This "
+ ],
+ [
+ 0.000026,
+ "\u001b[1;32H"
+ ],
+ [
+ 0.000068,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.014726,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mha\u001b[m\u001b[2;28H\u001b[96mit message for yo\u001b[3;28Hred, and an empty"
+ ],
+ [
+ 0.000780,
+ "\u001b[1;33H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.679312,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mat"
+ ],
+ [
+ 0.000870,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000049,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000015,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000129,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000035,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000020,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000373,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000006,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.001035,
+ "\u001b[1;34H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.085617,
+ "\u001b[?25l\u001b[m\u001b[93m \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.327942,
+ "\u001b[?25l\u0008 I\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.167989,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.136505,
+ "\u001b[?25l\u0008 n\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.111668,
+ "\u001b[?25l\u0008ne"
+ ],
+ [
+ 0.000119,
+ "\u001b[m\u001b[2;36H\u001b[48;5;242m new \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000024,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000051,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000054,
+ "\u001b[2;36H\u001b[105m new "
+ ],
+ [
+ 0.000066,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000361,
+ "\u001b[m\u001b[2;35H\u001b[96mage for your chan"
+ ],
+ [
+ 0.000117,
+ "\u001b[m\u001b[28;29H\u001b[1m The only match"
+ ],
+ [
+ 0.000049,
+ "\u001b[1;39H"
+ ],
+ [
+ 0.000030,
+ "\u001b[m\u001b[2;36H\u001b[48;5;242m new "
+ ],
+ [
+ 0.000008,
+ "\u001b[1;39H"
+ ],
+ [
+ 0.000084,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.142635,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mee\u001b[m\u001b[2;35H\u001b[96mage for your chan"
+ ],
+ [
+ 0.000743,
+ "\u001b[1;40H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.079680,
+ "\u001b[?25l\u001b[m\u0008\u001b[93med\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000006,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000047,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000099,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000010,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000027,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000251,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000026,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000876,
+ "\u001b[1;41H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.086590,
+ "\u001b[?25l\u001b[m\u001b[93m \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.159864,
+ "\u001b[?25l\u0008 t\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.079981,
+ "\u001b[?25l\u0008to"
+ ],
+ [
+ 0.000080,
+ "\u001b[m\u001b[2;41H\u001b[48;5;242m to \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000021,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000020,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000076,
+ "\u001b[2;41H\u001b[105m to "
+ ],
+ [
+ 0.000039,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000345,
+ "\u001b[m\u001b[2;40H\u001b[96mor your changes. "
+ ],
+ [
+ 0.000085,
+ "\u001b[m\u001b[28;29H\u001b[1m The only match"
+ ],
+ [
+ 0.000041,
+ "\u001b[1;44H"
+ ],
+ [
+ 0.000029,
+ "\u001b[m\u001b[2;41H\u001b[48;5;242m to "
+ ],
+ [
+ 0.000009,
+ "\u001b[1;44H"
+ ],
+ [
+ 0.000071,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.190677,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000033,
+ "\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000040,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000500,
+ "\u001b[m\u001b[1;44H\u001b[93m \u001b[m\u001b[2;40H\u001b[96mor your changes. "
+ ],
+ [
+ 0.000827,
+ "\u001b[1;45H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.095121,
+ "\u001b[?25l\u001b[m\u0008\u001b[93m c\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.096188,
+ "\u001b[?25l\u0008co"
+ ],
+ [
+ 0.000114,
+ "\u001b[m\u001b[2;44H\u001b[48;5;242m commit \u001b[m\u001b[3;44H\u001b[105m committed \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000027,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000025,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000098,
+ "\u001b[2;44H\u001b[105m commit "
+ ],
+ [
+ 0.000033,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000401,
+ "\u001b[m\u001b[2;43H\u001b[96myour changes. Lin\u001b[3;43Hty message aborts"
+ ],
+ [
+ 0.000108,
+ "\u001b[m\u001b[28;29H\u001b[1m \u001b[m\u001b[38;5;121mmatch 1 of 2"
+ ],
+ [
+ 0.000043,
+ "\u001b[1;47H"
+ ],
+ [
+ 0.000031,
+ "\u001b[m\u001b[2;44H\u001b[48;5;242m commit \u001b[m\u001b[3;44H\u001b[105m committed "
+ ],
+ [
+ 0.000011,
+ "\u001b[1;47H"
+ ],
+ [
+ 0.000074,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.070604,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mon\u001b[m\u001b[2;43H\u001b[96myour changes. Lin\u001b[3;43Hty message aborts"
+ ],
+ [
+ 0.000833,
+ "\u001b[1;48H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.110931,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mnt"
+ ],
+ [
+ 0.000665,
+ "\u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000021,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000027,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000096,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000013,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000026,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000273,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000034,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000994,
+ "\u001b[1;49H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.158110,
+ "\u001b[?25l\u001b[m\u0008\u001b[93mti\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.120153,
+ "\u001b[?25l\u0008in\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.456043,
+ "\u001b[?25l\u0008nu\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.136040,
+ "\u001b[?25l\u0008ue\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.087847,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.200047,
+ "\u001b[?25l\u0008 w\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.072235,
+ "\u001b[?25l\u0008w\u001b[mo"
+ ],
+ [
+ 0.000052,
+ "\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000004,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000027,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000071,
+ "\u001b[28;1H="
+ ],
+ [
+ 0.000014,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000021,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000258,
+ "\u001b[28;1H\u001b[1m-- Keyword completion (^N^P) \u001b[m\u001b[97m\u001b[41mPattern not found\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000004,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000928,
+ "\u001b[1;56H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.142550,
+ "\u001b[?25l\u001b[m\u0008or\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.072562,
+ "\u001b[?25l\u0008rk\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.143446,
+ "\u001b[?25l\u0008ki\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.064195,
+ "\u001b[?25l\u0008in\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.064352,
+ "\u001b[?25l\u0008ng\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.135448,
+ "\u001b[?25l \u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.431640,
+ "\u001b[?25l\u0008 o\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.080069,
+ "\u001b[?25l\u0008on"
+ ],
+ [
+ 0.000051,
+ "\u001b[2;61H\u001b[48;5;242m On \u001b[m\u001b[28;1H\u001b[K\u001b[28;1H="
+ ],
+ [
+ 0.000018,
+ "acp#onPopupPost()\r"
+ ],
+ [
+ 0.000040,
+ "\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000054,
+ "\u001b[2;61H\u001b[105m On "
+ ],
+ [
+ 0.000010,
+ "\u001b[m\u001b[28;1H\u001b[1m-- Keyword completion (^N^P)"
+ ],
+ [
+ 0.000288,
+ "\u001b[m\u001b[2;60H\u001b[96mes starting\u001b[m\u001b[2;71H\u001b[K"
+ ],
+ [
+ 0.000076,
+ "\u001b[28;29H\u001b[1m The only match"
+ ],
+ [
+ 0.000039,
+ "\u001b[1;64H"
+ ],
+ [
+ 0.000016,
+ "\u001b[m\u001b[2;61H\u001b[48;5;242m On "
+ ],
+ [
+ 0.000028,
+ "\u001b[1;64H"
+ ],
+ [
+ 0.000055,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 1.438871,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000006,
+ "\u001b[m\u001b[28;1H\u001b[K"
+ ],
+ [
+ 0.000050,
+ "\u001b[28;1H\u001b[1m-- INSERT --"
+ ],
+ [
+ 0.000498,
+ "\u001b[m\u001b[1;63Hn!\u001b[2;60H\u001b[96mes starting\u001b[m\u001b[2;71H\u001b[K"
+ ],
+ [
+ 0.000856,
+ "\u001b[1;65H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.435983,
+ "\u001b[28;1H\u001b[K\u001b[1;64H"
+ ],
+ [
+ 0.314354,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000414,
+ "\u001b[?12l\u001b[?25h\u001b[?25l\u001b[28;1H:"
+ ],
+ [
+ 0.000020,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.191831,
+ "w"
+ ],
+ [
+ 0.039852,
+ "q"
+ ],
+ [
+ 0.560137,
+ "\r"
+ ],
+ [
+ 0.000384,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000061,
+ "\".git/COMMIT_EDITMSG\""
+ ],
+ [
+ 0.001403,
+ " 7L, 266C written"
+ ],
+ [
+ 0.001698,
+ "\r\r\r\n\u001b[?1l\u001b\u003e\u001b[?12l\u001b[?25h\u001b[?1049l"
+ ],
+ [
+ 0.003162,
+ "gitlint: checking commit message...\r\n"
+ ],
+ [
+ 0.052844,
+ "1: T3 Title has trailing punctuation (!): \"WIP: This is a patchset that I need to continue working on!\"\r\n1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is a patchset that I need to continue working on!\"\r\n3: B6 Body message is missing\r\n"
+ ],
+ [
+ 0.006075,
+ "-----------------------------------------------\r\n"
+ ],
+ [
+ 0.000020,
+ "gitlint: \u001b[31mYour commit message contains violations.\u001b[0m\r\n"
+ ],
+ [
+ 0.002541,
+ "\u001b[?1034h"
+ ],
+ [
+ 0.000014,
+ "Continue with commit anyways (this keeps the current commit message)? [y/n] "
+ ],
+ [
+ 6.778903,
+ "y"
+ ],
+ [
+ 0.376370,
+ "\r\n"
+ ],
+ [
+ 0.004763,
+ "[main 4b1f92d] WIP: This is a patchset that I need to continue working on!\r\n"
+ ],
+ [
+ 0.001504,
+ " 1 file changed, 1 insertion(+)\r\n create mode 100644 foo.txt\r\n"
+ ],
+ [
+ 0.000420,
+ "bash-3.2$ "
+ ],
+ [
+ 0.913122,
+ "#"
+ ],
+ [
+ 0.143873,
+ " "
+ ],
+ [
+ 1.040537,
+ "Y"
+ ],
+ [
+ 0.159468,
+ "o"
+ ],
+ [
+ 0.032403,
+ "u"
+ ],
+ [
+ 0.255831,
+ " "
+ ],
+ [
+ 0.128028,
+ "c"
+ ],
+ [
+ 0.056381,
+ "a"
+ ],
+ [
+ 0.047650,
+ "n"
+ ],
+ [
+ 0.144010,
+ " "
+ ],
+ [
+ 0.143892,
+ "m"
+ ],
+ [
+ 0.096047,
+ "o"
+ ],
+ [
+ 0.127988,
+ "d"
+ ],
+ [
+ 0.144268,
+ "i"
+ ],
+ [
+ 0.183322,
+ "f"
+ ],
+ [
+ 0.136376,
+ "y"
+ ],
+ [
+ 0.192288,
+ " "
+ ],
+ [
+ 0.127701,
+ "g"
+ ],
+ [
+ 0.088308,
+ "i"
+ ],
+ [
+ 0.495279,
+ "t"
+ ],
+ [
+ 0.192340,
+ "l"
+ ],
+ [
+ 0.055845,
+ "i"
+ ],
+ [
+ 0.072236,
+ "n"
+ ],
+ [
+ 0.111890,
+ "t"
+ ],
+ [
+ 0.320013,
+ "'"
+ ],
+ [
+ 0.167698,
+ "s"
+ ],
+ [
+ 0.088331,
+ " "
+ ],
+ [
+ 0.208264,
+ "b"
+ ],
+ [
+ 0.087663,
+ "e"
+ ],
+ [
+ 0.352021,
+ "h"
+ ],
+ [
+ 0.191971,
+ "a"
+ ],
+ [
+ 0.176006,
+ "v"
+ ],
+ [
+ 0.104268,
+ "i"
+ ],
+ [
+ 0.023762,
+ "o"
+ ],
+ [
+ 0.128201,
+ "r"
+ ],
+ [
+ 0.119900,
+ " "
+ ],
+ [
+ 0.183877,
+ "b"
+ ],
+ [
+ 0.143917,
+ "y"
+ ],
+ [
+ 0.240199,
+ " "
+ ],
+ [
+ 0.647870,
+ "c"
+ ],
+ [
+ 0.041003,
+ "o"
+ ],
+ [
+ 0.063052,
+ "n"
+ ],
+ [
+ 0.144261,
+ "f"
+ ],
+ [
+ 0.103317,
+ "i"
+ ],
+ [
+ 0.128402,
+ "g"
+ ],
+ [
+ 0.080038,
+ "u"
+ ],
+ [
+ 0.128003,
+ "r"
+ ],
+ [
+ 0.480050,
+ "i"
+ ],
+ [
+ 0.047741,
+ "n"
+ ],
+ [
+ 0.103828,
+ "g"
+ ],
+ [
+ 0.126593,
+ " "
+ ],
+ [
+ 0.113591,
+ "a"
+ ],
+ [
+ 0.104071,
+ " "
+ ],
+ [
+ 0.343976,
+ "."
+ ],
+ [
+ 0.215812,
+ "g"
+ ],
+ [
+ 0.088229,
+ "i"
+ ],
+ [
+ 0.167944,
+ "t"
+ ],
+ [
+ 0.104389,
+ "l"
+ ],
+ [
+ 0.055649,
+ "i"
+ ],
+ [
+ 0.064009,
+ "n"
+ ],
+ [
+ 0.128039,
+ "t"
+ ],
+ [
+ 0.111929,
+ " "
+ ],
+ [
+ 0.151932,
+ "f"
+ ],
+ [
+ 0.072042,
+ "i"
+ ],
+ [
+ 0.072020,
+ "l"
+ ],
+ [
+ 0.079850,
+ "e"
+ ],
+ [
+ 0.656150,
+ "\r\n"
+ ],
+ [
+ 0.000100,
+ "bash-3.2$ "
+ ],
+ [
+ 0.735877,
+ "g"
+ ],
+ [
+ 0.103942,
+ "i"
+ ],
+ [
+ 0.184038,
+ "t"
+ ],
+ [
+ 0.111946,
+ "l"
+ ],
+ [
+ 0.064269,
+ "i"
+ ],
+ [
+ 0.063764,
+ "n"
+ ],
+ [
+ 0.472229,
+ "t"
+ ],
+ [
+ 0.183704,
+ " "
+ ],
+ [
+ 0.416073,
+ "g"
+ ],
+ [
+ 0.096000,
+ "e"
+ ],
+ [
+ 0.143925,
+ "n"
+ ],
+ [
+ 0.064290,
+ "e"
+ ],
+ [
+ 0.079792,
+ "r"
+ ],
+ [
+ 0.095868,
+ "a"
+ ],
+ [
+ 0.104267,
+ "t"
+ ],
+ [
+ 0.207732,
+ "e"
+ ],
+ [
+ 0.184086,
+ "-"
+ ],
+ [
+ 0.171619,
+ "c"
+ ],
+ [
+ 0.084287,
+ "o"
+ ],
+ [
+ 0.064003,
+ "n"
+ ],
+ [
+ 0.111626,
+ "f"
+ ],
+ [
+ 0.168397,
+ "i"
+ ],
+ [
+ 0.135945,
+ "g"
+ ],
+ [
+ 0.344287,
+ "\r\n"
+ ],
+ [
+ 0.054614,
+ "Please specify a location for the sample gitlint config file [.gitlint]: "
+ ],
+ [
+ 1.281099,
+ "\r\n"
+ ],
+ [
+ 0.001231,
+ "Successfully generated /Users/jroovers/my-git-repo/.gitlint\r\n"
+ ],
+ [
+ 0.005057,
+ "bash-3.2$ "
+ ],
+ [
+ 1.481485,
+ "v"
+ ],
+ [
+ 0.056099,
+ "i"
+ ],
+ [
+ 0.063695,
+ "m"
+ ],
+ [
+ 0.159794,
+ " "
+ ],
+ [
+ 0.138400,
+ "."
+ ],
+ [
+ 0.198256,
+ "g"
+ ],
+ [
+ 0.119954,
+ "i"
+ ],
+ [
+ 0.119891,
+ "t"
+ ],
+ [
+ 0.120085,
+ "l"
+ ],
+ [
+ 0.055836,
+ "i"
+ ],
+ [
+ 0.080111,
+ "n"
+ ],
+ [
+ 0.135971,
+ "t"
+ ],
+ [
+ 0.928127,
+ "\r\n"
+ ],
+ [
+ 0.039380,
+ "\u001b[?1049h\u001b[?1h\u001b="
+ ],
+ [
+ 0.001629,
+ "\u001b[1;28r\u001b[?12;25h\u001b[?12l\u001b[?25h\u001b[27m\u001b[m\u001b[H\u001b[2J\u001b[?25l\u001b[28;1H\".gitlint\""
+ ],
+ [
+ 0.000064,
+ " 41L, 1416C"
+ ],
+ [
+ 0.003242,
+ "\u001b[\u003ec"
+ ],
+ [
+ 0.007250,
+ "\u001b[1;1H\u001b[93m 1 \u001b[m\u001b[96m# All these sections are optional, edit this file as you like.\u001b[m\r\n\u001b[93m 2 \u001b[m\u001b[96m# [general]\u001b[m\r\n\u001b[93m 3 \u001b[m\u001b[96m# ignore=title-trailing-punctuation, T3\u001b[m\r\n\u001b[93m 4 \u001b[m\u001b[96m# verbosity should be a value between 1 and 3, the commandline -v flags take pre\u001b[m\u001b[97m\u001b[101mcedence over\u001b[m\r\n\u001b[93m 5 \u001b[m\u001b[96m# this\u001b[m\r\n\u001b[93m 6 \u001b[m\u001b[96m# verbosity = 2\u001b[m\r\n\u001b[93m 7 \r\n 8 \u001b[m\u001b[96m# [title-max-length]\u001b[m\r\n\u001b[93m 9 \u001b[m\u001b[96m# line-length=80\u001b[m\r\n\u001b[93m 10 \r\n 11 \u001b[m\u001b[96m# [title-must-not-contain-word]\u001b[m\r\n\u001b[93m 12 \u001b[m\u001b[96m# Comma-separated list of words that should not occur in the title. Matching is \u001b[m\u001b[97m\u001b[101mcase\u001b[m\r\n\u001b[93m 13 \u001b[m\u001b[96m# insensitive. It's fine if the keyword occurs as part of a larger word (so \"WIP\u001b[m\u001b[97m\u001b[101mING\"\u001b[m\r\n\u001b[93m 14 \u001b[m\u001b[96m# will not cause a violation, but \"WIP: my title\" will.\u001b[m\r\n\u001b[93m 15 \u001b[m\u001b[96m# words=wip\u001b[m\r\n\u001b[93m 16 \r\n 17 \u001b[m\u001b[96m# [title-match-regex]\u001b[m\r\n\u001b[93m 18 \u001b[m\u001b[96m# python like regex (https://docs.python.org/2/library/re.html) that the\u001b[m\r\n\u001b[93m 19 "
+ ],
+ [
+ 0.000011,
+ "\u001b[m\u001b[96m# commit-msg title must be matched to.\u001b[m\r\n\u001b[93m 20 \u001b[m\u001b[96m# Note that the regex can contradict with other rules if not used correctly\u001b[m\r\n\u001b[93m 21 \u001b[m\u001b[96m# (e.g. title-must-not-contain-word).\u001b[m\r\n\u001b[93m 22 \u001b[m\u001b[96m# regex=^US[0-9]*\u001b[m\r\n\u001b[93m 23 \r\n 24 \u001b[m\u001b[96m# [B1]\u001b[m\r\n\u001b[93m 25 \u001b[m\u001b[96m# B1 = body-max-line-length\u001b[m\r\n\u001b[93m 26 \u001b[m\u001b[96m# line-length=120\u001b[m\r\n\u001b[93m 27 \u001b[1;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 1.532701,
+ "\r\n 2 "
+ ],
+ [
+ 0.104014,
+ "\r\n 3 "
+ ],
+ [
+ 0.191728,
+ "\r\n 4 "
+ ],
+ [
+ 0.135939,
+ "\r\n 5 "
+ ],
+ [
+ 0.144151,
+ "\r\n 6 "
+ ],
+ [
+ 0.151956,
+ "\r\n 7 "
+ ],
+ [
+ 0.143897,
+ "\r\n 8 "
+ ],
+ [
+ 0.424058,
+ "\u001b[?25l\u0008 \u001b[m [title-max-length]\u001b[8;24H\u001b[K\u001b[8;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.160610,
+ "\u001b[?25l\u0008\u001b[93m \u001b[m\u001b[46m[\u001b[mtitle-max-length\u001b[46m]\u001b[m\u001b[8;23H\u001b[K\u001b[8;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.192102,
+ "\u001b[?25l[\u001b[16C]\u001b[9;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.231348,
+ "\u001b[?25l\u0008\u001b[93m \u001b[m line-length=80\u001b[9;20H\u001b[K\u001b[9;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.144198,
+ "\u001b[?25l\u0008\u001b[93m \u001b[mline-length=80\u001b[9;19H\u001b[K\u001b[9;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.552150,
+ "\u001b[?25l\u001b[28;1H\u001b[1m-- INSERT --\u001b[m\u001b[28;13H\u001b[K\u001b[9;5H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.303680,
+ "l"
+ ],
+ [
+ 0.201138,
+ "i"
+ ],
+ [
+ 0.017440,
+ "n"
+ ],
+ [
+ 0.017353,
+ "e"
+ ],
+ [
+ 0.016784,
+ "-"
+ ],
+ [
+ 0.017813,
+ "l"
+ ],
+ [
+ 0.017864,
+ "e"
+ ],
+ [
+ 0.017036,
+ "n"
+ ],
+ [
+ 0.017743,
+ "g"
+ ],
+ [
+ 0.017214,
+ "t"
+ ],
+ [
+ 0.017098,
+ "h"
+ ],
+ [
+ 0.018114,
+ "="
+ ],
+ [
+ 0.017698,
+ "8"
+ ],
+ [
+ 0.015624,
+ "0"
+ ],
+ [
+ 0.391058,
+ "\u0008"
+ ],
+ [
+ 0.320130,
+ "\u001b[?25l\u0008\u0008=0\u001b[9;18H\u001b[K\u001b[9;17H\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.271868,
+ "\u001b[?25l\u0008=50\u0008\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.581296,
+ "\u001b[28;1H\u001b[K\u001b[9;17H"
+ ],
+ [
+ 0.242328,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000209,
+ "\u001b[?12l\u001b[?25h\u001b[?25l\u001b[28;1H:"
+ ],
+ [
+ 0.000006,
+ "\u001b[?12l\u001b[?25h"
+ ],
+ [
+ 0.199592,
+ "w"
+ ],
+ [
+ 0.048010,
+ "q"
+ ],
+ [
+ 1.080364,
+ "\r"
+ ],
+ [
+ 0.000041,
+ "\u001b[?25l"
+ ],
+ [
+ 0.000082,
+ "\".gitlint\""
+ ],
+ [
+ 0.001253,
+ " 41L, 1412C written"
+ ],
+ [
+ 0.001495,
+ "\r\r\r\n\u001b[?1l\u001b\u003e\u001b[?12l\u001b[?25h\u001b[?1049l"
+ ],
+ [
+ 0.000815,
+ "bash-3.2$ "
+ ],
+ [
+ 1.491944,
+ "g"
+ ],
+ [
+ 0.071961,
+ "i"
+ ],
+ [
+ 0.160254,
+ "t"
+ ],
+ [
+ 0.128030,
+ "l"
+ ],
+ [
+ 0.071789,
+ "i"
+ ],
+ [
+ 0.055927,
+ "n"
+ ],
+ [
+ 0.127907,
+ "t"
+ ],
+ [
+ 0.728389,
+ "\r\n"
+ ],
+ [
+ 0.053628,
+ "Using config from /Users/jroovers/my-git-repo/.gitlint\r\n"
+ ],
+ [
+ 0.050694,
+ "1: T1 Title exceeds max length (60\u003e50): \"WIP: This is a patchset that I need to continue working on!\"\r\n"
+ ],
+ [
+ 0.000006,
+ "1: T3 Title has trailing punctuation (!): \"WIP: This is a patchset that I need to continue working on!\"\r\n1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is a patchset that I need to continue working on!\"\r\n3: B6 Body message is missing\r\n"
+ ],
+ [
+ 0.005418,
+ "bash-3.2$ "
+ ],
+ [
+ 2.577825,
+ "#"
+ ],
+ [
+ 0.264000,
+ " "
+ ],
+ [
+ 1.095576,
+ "O"
+ ],
+ [
+ 0.205521,
+ "r"
+ ],
+ [
+ 0.083054,
+ " "
+ ],
+ [
+ 0.127713,
+ "s"
+ ],
+ [
+ 0.096301,
+ "p"
+ ],
+ [
+ 0.079687,
+ "e"
+ ],
+ [
+ 0.088075,
+ "c"
+ ],
+ [
+ 0.087991,
+ "i"
+ ],
+ [
+ 0.144246,
+ "f"
+ ],
+ [
+ 0.839973,
+ "y"
+ ],
+ [
+ 0.392068,
+ " "
+ ],
+ [
+ 0.487898,
+ "a"
+ ],
+ [
+ 0.208003,
+ "d"
+ ],
+ [
+ 0.135717,
+ "d"
+ ],
+ [
+ 0.079782,
+ "i"
+ ],
+ [
+ 0.151837,
+ "t"
+ ],
+ [
+ 0.096448,
+ "i"
+ ],
+ [
+ 0.079993,
+ "o"
+ ],
+ [
+ 0.000349,
+ "n"
+ ],
+ [
+ 0.135585,
+ "a"
+ ],
+ [
+ 0.095980,
+ "l"
+ ],
+ [
+ 0.151930,
+ " "
+ ],
+ [
+ 0.151950,
+ "c"
+ ],
+ [
+ 0.079947,
+ "o"
+ ],
+ [
+ 0.032040,
+ "n"
+ ],
+ [
+ 0.103990,
+ "f"
+ ],
+ [
+ 0.112311,
+ "i"
+ ],
+ [
+ 0.127670,
+ "g"
+ ],
+ [
+ 0.191958,
+ " "
+ ],
+ [
+ 0.335974,
+ "v"
+ ],
+ [
+ 0.072137,
+ "i"
+ ],
+ [
+ 0.127902,
+ "a"
+ ],
+ [
+ 0.103834,
+ " "
+ ],
+ [
+ 0.168232,
+ "t"
+ ],
+ [
+ 0.112211,
+ "h"
+ ],
+ [
+ 0.095730,
+ "e"
+ ],
+ [
+ 0.112047,
+ " "
+ ],
+ [
+ 0.087745,
+ "c"
+ ],
+ [
+ 0.063982,
+ "o"
+ ],
+ [
+ 0.368225,
+ "m"
+ ],
+ [
+ 0.168146,
+ "m"
+ ],
+ [
+ 0.063823,
+ "a"
+ ],
+ [
+ 0.112252,
+ "n"
+ ],
+ [
+ 0.087711,
+ "d"
+ ],
+ [
+ 0.112231,
+ "l"
+ ],
+ [
+ 0.079753,
+ "i"
+ ],
+ [
+ 0.056014,
+ "n"
+ ],
+ [
+ 0.111900,
+ "e"
+ ],
+ [
+ 1.128086,
+ "\r\n"
+ ],
+ [
+ 0.000116,
+ "bash-3.2$ "
+ ],
+ [
+ 1.096057,
+ "g"
+ ],
+ [
+ 0.063721,
+ "i"
+ ],
+ [
+ 0.151557,
+ "t"
+ ],
+ [
+ 0.288291,
+ "l"
+ ],
+ [
+ 0.040064,
+ "i"
+ ],
+ [
+ 0.063972,
+ "n"
+ ],
+ [
+ 0.119883,
+ "t"
+ ],
+ [
+ 0.192140,
+ " "
+ ],
+ [
+ 0.383892,
+ "-"
+ ],
+ [
+ 0.143814,
+ "-"
+ ],
+ [
+ 0.200589,
+ "i"
+ ],
+ [
+ 0.063787,
+ "g"
+ ],
+ [
+ 0.151539,
+ "n"
+ ],
+ [
+ 0.112696,
+ "o"
+ ],
+ [
+ 0.095761,
+ "r"
+ ],
+ [
+ 0.056248,
+ "e"
+ ],
+ [
+ 0.471314,
+ " "
+ ],
+ [
+ 0.496411,
+ "t"
+ ],
+ [
+ 0.032231,
+ "i"
+ ],
+ [
+ 0.775702,
+ "t"
+ ],
+ [
+ 0.071997,
+ "l"
+ ],
+ [
+ 0.119928,
+ "e"
+ ],
+ [
+ 0.152044,
+ "-"
+ ],
+ [
+ 0.192289,
+ "t"
+ ],
+ [
+ 0.168098,
+ "r"
+ ],
+ [
+ 0.095641,
+ "a"
+ ],
+ [
+ 0.079916,
+ "i"
+ ],
+ [
+ 0.088017,
+ "l"
+ ],
+ [
+ 0.208343,
+ "i"
+ ],
+ [
+ 0.087674,
+ "n"
+ ],
+ [
+ 0.192216,
+ "g"
+ ],
+ [
+ 0.463349,
+ "-"
+ ],
+ [
+ 0.224422,
+ "p"
+ ],
+ [
+ 0.303974,
+ "u"
+ ],
+ [
+ 0.071948,
+ "n"
+ ],
+ [
+ 0.472005,
+ "c"
+ ],
+ [
+ 0.368016,
+ "t"
+ ],
+ [
+ 0.303934,
+ "u"
+ ],
+ [
+ 0.112267,
+ "a"
+ ],
+ [
+ 0.087621,
+ "t"
+ ],
+ [
+ 0.080151,
+ "i"
+ ],
+ [
+ 0.048003,
+ "o"
+ ],
+ [
+ 0.031962,
+ "n"
+ ],
+ [
+ 1.887520,
+ "\r\n"
+ ],
+ [
+ 0.052100,
+ "Using config from /Users/jroovers/my-git-repo/.gitlint\r\n"
+ ],
+ [
+ 0.050989,
+ "1: T1 Title exceeds max length (60\u003e50): \"WIP: This is a patchset that I need to continue working on!\"\r\n1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is a patchset that I need to continue working on!\"\r\n"
+ ],
+ [
+ 0.000025,
+ "3: B6 Body message is missing\r\n"
+ ],
+ [
+ 0.006495,
+ "bash-3.2$ "
+ ],
+ [
+ 1.578501,
+ "#"
+ ],
+ [
+ 0.177781,
+ " "
+ ],
+ [
+ 0.222470,
+ "F"
+ ],
+ [
+ 0.088284,
+ "o"
+ ],
+ [
+ 0.127955,
+ "r"
+ ],
+ [
+ 0.056062,
+ " "
+ ],
+ [
+ 0.144004,
+ "m"
+ ],
+ [
+ 0.095681,
+ "o"
+ ],
+ [
+ 0.032018,
+ "r"
+ ],
+ [
+ 0.047994,
+ "e"
+ ],
+ [
+ 0.096045,
+ " "
+ ],
+ [
+ 0.111871,
+ "i"
+ ],
+ [
+ 0.071986,
+ "n"
+ ],
+ [
+ 0.056142,
+ "f"
+ ],
+ [
+ 0.095939,
+ "o"
+ ],
+ [
+ 0.279967,
+ ","
+ ],
+ [
+ 0.087962,
+ " "
+ ],
+ [
+ 0.175948,
+ "v"
+ ],
+ [
+ 0.072089,
+ "i"
+ ],
+ [
+ 0.144243,
+ "s"
+ ],
+ [
+ 0.031668,
+ "i"
+ ],
+ [
+ 0.232173,
+ "t"
+ ],
+ [
+ 0.143995,
+ ":"
+ ],
+ [
+ 0.200215,
+ " "
+ ],
+ [
+ 0.359698,
+ "h"
+ ],
+ [
+ 0.127942,
+ "t"
+ ],
+ [
+ 0.151997,
+ "t"
+ ],
+ [
+ 0.048065,
+ "p"
+ ],
+ [
+ 0.319959,
+ ":"
+ ],
+ [
+ 0.256283,
+ "/"
+ ],
+ [
+ 0.143558,
+ "/"
+ ],
+ [
+ 0.487848,
+ "j"
+ ],
+ [
+ 0.048256,
+ "o"
+ ],
+ [
+ 0.079996,
+ "r"
+ ],
+ [
+ 0.104020,
+ "i"
+ ],
+ [
+ 0.095905,
+ "s"
+ ],
+ [
+ 0.240093,
+ "r"
+ ],
+ [
+ 0.136044,
+ "o"
+ ],
+ [
+ 0.127483,
+ "o"
+ ],
+ [
+ 0.072697,
+ "v"
+ ],
+ [
+ 0.103625,
+ "e"
+ ],
+ [
+ 0.088072,
+ "r"
+ ],
+ [
+ 0.112033,
+ "s"
+ ],
+ [
+ 0.143951,
+ "."
+ ],
+ [
+ 0.648188,
+ "g"
+ ],
+ [
+ 0.279829,
+ "i"
+ ],
+ [
+ 0.463949,
+ "t"
+ ],
+ [
+ 0.079922,
+ "h"
+ ],
+ [
+ 0.120064,
+ "u"
+ ],
+ [
+ 0.080043,
+ "b"
+ ],
+ [
+ 0.231966,
+ "."
+ ],
+ [
+ 0.239964,
+ "i"
+ ],
+ [
+ 0.056111,
+ "o"
+ ],
+ [
+ 0.303921,
+ "/"
+ ],
+ [
+ 0.367976,
+ "g"
+ ],
+ [
+ 0.055984,
+ "i"
+ ],
+ [
+ 0.135983,
+ "t"
+ ],
+ [
+ 0.104035,
+ "l"
+ ],
+ [
+ 0.056048,
+ "i"
+ ],
+ [
+ 0.072242,
+ "n"
+ ],
+ [
+ 0.111889,
+ "t"
+ ],
+ [
+ 0.439701,
+ "\r\n"
+ ],
+ [
+ 0.000100,
+ "bash-3.2$ "
+ ],
+ [
+ 0.919921,
+ "e"
+ ],
+ [
+ 0.176231,
+ "x"
+ ],
+ [
+ 0.119224,
+ "i"
+ ],
+ [
+ 0.104616,
+ "t"
+ ],
+ [
+ 1.008087,
+ "\r\n"
+ ],
+ [
+ 0.000129,
+ "exit\r\n"
+ ]
+ ]
+} \ No newline at end of file
diff --git a/docs/demos/scenario.txt b/docs/demos/scenario.txt
new file mode 100644
index 0000000..7a4b692
--- /dev/null
+++ b/docs/demos/scenario.txt
@@ -0,0 +1,75 @@
+sudo pip uninstall gitlint
+
+virtualenv ~/gitlint-demo
+
+source ~/gitlint-demo
+
+mkdir ~/my-git-repo
+
+git init
+
+echo "test" > myfile.txt
+
+git add .
+
+git commit
+
+WIP: This is a commit message title.
+Second line not empty
+This body line exceeds the defacto standard length of 80 characters per line in a commit m
+essage.
+
+cd ..
+
+
+asciicinema rec demo.json
+
+------------------------------------
+
+pip install gitlint
+
+# Go to your git repo
+
+cd my-git-repo
+
+# Run gitlint to check for violations in the last commit message
+
+gitlint
+
+# For reference, here you can see that last commit message
+
+git log -1
+
+# You can also install gitlint as a git commit-msg hook
+
+gitlint install-hook
+
+# Let's try it out
+
+echo "This is a test" > foo.txt
+
+git add .
+
+git commit
+
+WIP: Still working on this awesome patchset that will change the world forever!
+
+[Keep commit -> yes]
+
+# You can modify gitlint's behavior by adding a .gitlint file
+
+gitlint generate-config
+
+vim .gitlint
+
+gitlint
+
+# Or specify additional config via the commandline
+
+gitlint --ignore title-trailing-punctuation
+
+# For more info, visit: http://jorisroovers.github.io/gitlint
+
+exit
+
+------------------------------ \ No newline at end of file
diff --git a/docs/extra.css b/docs/extra.css
new file mode 100644
index 0000000..12a7663
--- /dev/null
+++ b/docs/extra.css
@@ -0,0 +1,12 @@
+a.toctree-l3 {
+ margin-left: 10px;
+ /* display: none; */
+}
+
+.wy-nav-content {
+ max-width: 1000px;
+}
+
+.document hr {
+ border-top: 1px solid #666;
+} \ No newline at end of file
diff --git a/docs/extra.js b/docs/extra.js
new file mode 100644
index 0000000..4af1fa4
--- /dev/null
+++ b/docs/extra.js
@@ -0,0 +1,5 @@
+document.addEventListener("DOMContentLoaded", function () {
+ document.querySelectorAll("table").forEach(function (table) {
+ table.classList.add("docutils");
+ });
+}); \ No newline at end of file
diff --git a/docs/images/RuleViolation.png b/docs/images/RuleViolation.png
new file mode 100644
index 0000000..410dca9
--- /dev/null
+++ b/docs/images/RuleViolation.png
Binary files differ
diff --git a/docs/images/RuleViolations.graffle b/docs/images/RuleViolations.graffle
new file mode 100644
index 0000000..1fea2dd
--- /dev/null
+++ b/docs/images/RuleViolations.graffle
Binary files differ
diff --git a/docs/images/dev-container.png b/docs/images/dev-container.png
new file mode 100644
index 0000000..6cac5a2
--- /dev/null
+++ b/docs/images/dev-container.png
Binary files differ
diff --git a/docs/images/gitlint-packages.drawio.svg b/docs/images/gitlint-packages.drawio.svg
new file mode 100644
index 0000000..6098e3d
--- /dev/null
+++ b/docs/images/gitlint-packages.drawio.svg
@@ -0,0 +1,351 @@
+<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="491px" height="391px" viewBox="-0.5 -0.5 491 391" content="&lt;mxfile&gt;&lt;diagram id=&quot;x7jBp0SZ1TbX-vMHKkT6&quot; name=&quot;Page-1&quot;&gt;7Vtbb+o4EP41SGcfQEmccHks0O4e6axUbbW3p5VLDFg1MccxBc6vX9+SkNiUAEkvu62q4owvib/5ZjwzoR0wWe1+ZnC9/JXGiHQCL951wLQTBFHoi79SsNeCsB9owYLhWIv8QvCAfyAj9Ix0g2OUlgZySgnH67JwRpMEzXhJBhmj2/KwOSXlu67hAlmChxkktvRPHPOllg6DQSH/BeHFMruz3x/pnhXMBpudpEsY0+2BCNx2wIRRynVrtZsgIrHLcNHz7o705g/GUMLrTAB6wjMkG7O3TtAnYur4UTQWsrHAnOCEd2eUoaxTLJj3m43wfYbOdok5eljDmbzeCgKIQUu+IuLKF01I8CIRbaYBGj8jxrHA9sbIOZUTUjEfJ4vfDIogv5EcjnZHt+vnIAryIbpCnO3FEDMhZ5AhHsj0sC3UGAyNbHmgwiAyQmios8jXLtAVDQOwG+zQAfZl2BE05wVI39TVtN8QRiAqYzSwMfI9B0a58BqMoqOExAXhtGBOxYakkRPK1ND+9w3VA8B87nnycQqRnsvZJuUo7sZIuIqCzHqp8vJCjC/luJvTDSgHDN5SOYPT3gLtOIP/MPR9gxlKa/kLgQZ303wmUEHMgecKx7GcPhb3wD/go1rKE9drKlyV2mE07kRTudaG01SfIHLplDP6hCaGMwlN5CpzTEhF1IiuKs4mqqmroAFVDU+rCicph4R8KkthHozeTlkjS1kPdMOEbwm8iYidOsFEtr59FX/FdtRjK+x0B+Kzc/1TI8dEmd3hyHGUhpENWL8BwLJj3EHvGD9n5J0QPHuSvWA67Hk9kFsAq/p6cZAmmUwFiHqW3wt6vj2r1+sd2Mrh3APxwXOcYUTmYP+QJgSGFUZ4NiNGDgtqIrTy/fqECPpwJe1B60Zw4xxeVCd/0qO2wwhP02PQFj3sPOd+f/9VPf/sSWZ7ZzpQRjdJjOLm3KlfMR7g1zOeJo4fv386WDBZ4H8tAfRHvn1quRLAUdQAzsFHY6E/tOFpjYV2FHQps14zPRYBjc0gV6AYNsCg4Hjc8xnW1wnrA8/WVmthvW8nYaViWpQHBG9a25Aqkr9ezx/0vGPUeIWMIgRlXQHQi2xtjRw5BWhCW6G1dRQv0IO5pIwv6YImkNwWUhsBOeXl/Qceh2yBsujGDQlDBHL8XF7KtTsz9V6ng3koHpX9uJV0pSq/NLMKjG4Yg/uDYcaUz77PXc3xYbk8LRr6CQqF5ZjU848jS4cHGpKWcAdXmMi7/4FYDBNoxJPc0MDdnTIvMFap9l/GqamLv5WVRNnldHfYOd2bq8uIAAatMCF3bpmGwmEtJtgLDU4spDdjLXSJFl3JXEWt6RKuZRPOuFTcKfdUjpSOabwBJ1Y1iCB0+LDQFR4MGggP7ADzowIXOvKf1mA7/tJBZ+8H+GWHrkSiOzfO5EY9FHlGMkBScHiqX8c8stcP1ruDjq3ZgewKDYIeQVxEWl0TterOhLIVJLpfHsRdE5bJvjwyy/qw0FRiVvWy+6kezmCSzsVa2aomvvK2lMXlO+YTY5yuCTS7w4kIW8ycOaGQVxaqxiJWYLPGa7WMik1Fy84nrRjIKp5cFJg44sr6oW2F755n+E7gIyJjkbQtlH2UTEL+NBQQVXzJ0DYJZ+zaRDT0wguET5to1yaqb9rbMowj3K5vHa9jBX453w5dbzxBS1YAjpeV350VvCOm/54iBQ3DMxlBioQUyYebYVmN8L6saCrFmxSx9KdmSW2R1+H+r+HzMaO5hud5rc1B7H5bxHZFip/ErkdsQqn6LPO6+hroC5Qk34v0SnwwnD41TPV377+zNxn91+Q1sNC8vADQqZnIt560h0Ul7Ny0PbCXKi/UXNoO+hb2ZxXQ8npL1+t5Xl5l0UUXH5youqire8SweGJJeyWsp/CKFz+3chO2woHqm6qwah4NlfCqWUZ2n6OEqowHQ1DhzXUlPFDjW5JiBl6n6NzqRc3z/WzCXJnkVUuirqp34PCXTbyiAC8WTC+tgLZmdprznYOCadkU9TvEK2yxNm6ur0L+j1gqzL4tjorL4vvv2oEU/0QAbv8F&lt;/diagram&gt;&lt;/mxfile&gt;">
+ <defs/>
+ <g>
+ <rect x="210" y="140" width="280" height="250" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 275px; height: 1px; padding-top: 147px; margin-left: 210px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: right;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ <b>
+ gitlint-core
+ </b>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="485" y="159" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="end">
+ gitlint-core
+ </text>
+ </switch>
+ </g>
+ <rect x="235" y="190" width="100" height="100" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <rect x="375" y="190" width="100" height="100" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 197px; margin-left: 376px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ <i>
+ <font color="#ff0000">
+ trusted-deps
+ </font>
+ </i>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="425" y="209" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ trusted-deps
+ </text>
+ </switch>
+ </g>
+ <rect x="370" y="170" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 180px; margin-left: 420px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
+ <b>
+ extra_requires
+ </b>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="420" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ extra_requires
+ </text>
+ </switch>
+ </g>
+ <rect x="229" y="170" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 180px; margin-left: 279px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
+ <b>
+ install_requires
+ </b>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="279" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ install_requires
+ </text>
+ </switch>
+ </g>
+ <rect x="230" y="310" width="245" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 243px; height: 1px; padding-top: 340px; margin-left: 231px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ Source Code, CLI entry point, etc
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="353" y="344" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ Source Code, CLI entry point, etc
+ </text>
+ </switch>
+ </g>
+ <rect x="380" y="220" width="90" height="50" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 245px; margin-left: 382px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
+ <div>
+ Click==8.0.3
+ <br/>
+ <span>
+ arrow==1.2.1
+ <br/>
+ ...
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="382" y="249" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px">
+ Click==8.0.3...
+ </text>
+ </switch>
+ </g>
+ <rect x="240" y="220" width="70" height="50" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 245px; margin-left: 242px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
+ <div>
+ Click&gt;=8
+ <br/>
+ <span>
+ arrow&gt;=1
+ <br/>
+ ...
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="242" y="249" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px">
+ Click&gt;=8...
+ </text>
+ </switch>
+ </g>
+ <rect x="180" y="130" width="90" height="20" rx="3" ry="3" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 140px; margin-left: 181px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ PyPI package
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="225" y="144" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ PyPI package
+ </text>
+ </switch>
+ </g>
+ <rect x="210" y="11" width="280" height="95" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 275px; height: 1px; padding-top: 18px; margin-left: 210px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: right;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ <b>
+ gitlint
+ </b>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="485" y="30" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="end">
+ gitlint
+ </text>
+ </switch>
+ </g>
+ <rect x="180" y="1" width="90" height="20" rx="3" ry="3" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 11px; margin-left: 181px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ PyPI package
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="225" y="15" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ PyPI package
+ </text>
+ </switch>
+ </g>
+ <rect x="235" y="46" width="200" height="45" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <rect x="229" y="26" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 36px; margin-left: 279px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">
+ <b>
+ install_requires
+ </b>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="279" y="40" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ install_requires
+ </text>
+ </switch>
+ </g>
+ <rect x="243" y="53.5" width="195" height="30" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 193px; height: 1px; padding-top: 61px; margin-left: 245px;">
+ <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
+ gitlint-core[
+ <i>
+ <font color="#ff0000">
+ trusted-deps
+ </font>
+ </i>
+ ]==0.17.0
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="245" y="73" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px">
+ gitlint-core[trusted-deps]==0.17...
+ </text>
+ </switch>
+ </g>
+ <path d="M 350 80 L 350 230 Q 350 240 359.32 240 L 368.63 240" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
+ <path d="M 373.88 240 L 366.88 243.5 L 368.63 240 L 366.88 236.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <path d="M 100 68 L 193.63 68" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
+ <path d="M 198.88 68 L 191.88 71.5 L 193.63 68 L 191.88 64.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <path d="M 50 91.5 C 50 72.7 50 63.3 70 63.3 C 56.67 63.3 56.67 44.5 70 44.5 C 83.33 44.5 83.33 63.3 70 63.3 C 90 63.3 90 72.7 90 91.5 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <path d="M 50 277 C 50 258.2 50 248.8 70 248.8 C 56.67 248.8 56.67 230 70 230 C 83.33 230 83.33 248.8 70 248.8 C 90 248.8 90 258.2 90 277 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <rect x="20" y="100" width="100" height="30" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 115px; margin-left: 21px;">
+ <div data-drawio-colors="color: #000000; background-color: #FFFFFF; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: normal; overflow-wrap: normal;">
+ <span style="font-family: &quot;helvetica&quot; ; font-size: 12px ; font-weight: 400 ; letter-spacing: normal ; text-align: center ; text-indent: 0px ; text-transform: none ; word-spacing: 0px ; display: inline ; float: none">
+ <i>
+ pip install gitlint
+ </i>
+ </span>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="70" y="119" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ pip install gitl...
+ </text>
+ </switch>
+ </g>
+ <rect x="15" y="290" width="130" height="30" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 305px; margin-left: 16px;">
+ <div data-drawio-colors="color: #000000; background-color: #FFFFFF; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: normal; overflow-wrap: normal;">
+ <span style="font-family: &quot;helvetica&quot; ; font-size: 12px ; font-weight: 400 ; letter-spacing: normal ; text-align: center ; text-indent: 0px ; text-transform: none ; word-spacing: 0px ; display: inline ; float: none">
+ <i>
+ pip install gitlint-core
+ </i>
+ </span>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="80" y="309" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ pip install gitlint-c...
+ </text>
+ </switch>
+ </g>
+ <rect x="0" y="0" width="160" height="30" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 15px; margin-left: 1px;">
+ <div data-drawio-colors="color: #000000; background-color: #FFFFFF; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: normal; overflow-wrap: normal;">
+ <span style="font-family: &quot;helvetica&quot; ; font-size: 12px ; font-weight: 400 ; letter-spacing: normal ; text-indent: 0px ; text-transform: none ; word-spacing: 0px ; display: inline ; float: none">
+ Use strict dependencies (most users)
+ </span>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="80" y="19" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ Use strict dependencies (m...
+ </text>
+ </switch>
+ </g>
+ <rect x="0" y="180" width="160" height="30" fill="none" stroke="none" pointer-events="all"/>
+ <g transform="translate(-0.5 -0.5)">
+ <switch>
+ <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
+ <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 195px; margin-left: 1px;">
+ <div data-drawio-colors="color: #000000; background-color: #FFFFFF; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
+ <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: normal; overflow-wrap: normal;">
+ <span style="font-family: &quot;helvetica&quot; ; font-size: 12px ; font-weight: 400 ; letter-spacing: normal ; text-indent: 0px ; text-transform: none ; word-spacing: 0px ; display: inline ; float: none">
+ Use loose dependencies
+ <br/>
+ (at your risk)
+ </span>
+ </div>
+ </div>
+ </div>
+ </foreignObject>
+ <text x="80" y="199" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
+ Use loose dependencies...
+ </text>
+ </switch>
+ </g>
+ <path d="M 100 253.5 L 193.63 253.03" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
+ <path d="M 198.88 253.01 L 191.9 256.54 L 193.63 253.03 L 191.86 249.54 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <path d="M 210 250 L 215 250 Q 220 250 220 240 L 220 213 Q 220 203 224.07 203 L 228.13 203" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
+ <path d="M 233.38 203 L 226.38 206.5 L 228.13 203 L 226.38 199.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <ellipse cx="210" cy="253.5" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ <path d="M 220 68 L 228.64 68.29" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
+ <path d="M 233.88 68.46 L 226.77 71.73 L 228.64 68.29 L 227 64.73 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
+ <ellipse cx="210" cy="68" rx="10" ry="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
+ </g>
+ <switch>
+ <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
+ <a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
+ <text text-anchor="middle" font-size="10px" x="50%" y="100%">
+ Viewer does not support full SVG 1.1
+ </text>
+ </a>
+ </switch>
+</svg> \ No newline at end of file
diff --git a/docs/images/gitlint-packages.png b/docs/images/gitlint-packages.png
new file mode 100644
index 0000000..00d3ec1
--- /dev/null
+++ b/docs/images/gitlint-packages.png
Binary files differ
diff --git a/docs/images/readme-gitlint.png b/docs/images/readme-gitlint.png
new file mode 100644
index 0000000..516c915
--- /dev/null
+++ b/docs/images/readme-gitlint.png
Binary files differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..b735b6b
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,506 @@
+# Introduction
+Gitlint is a git commit message linter written in python: it checks your commit messages for style.
+
+Great for use as a [commit-msg git hook](#using-gitlint-as-a-commit-msg-hook) or as part of your gating script in a
+[CI pipeline (e.g. Jenkins)](index.md#using-gitlint-in-a-ci-environment).
+
+<script type="text/javascript" src="https://asciinema.org/a/30477.js" id="asciicast-30477" async></script>
+
+!!! note
+ **Gitlint works on Windows**, but [there are some known issues](https://github.com/jorisroovers/gitlint/issues?q=is%3Aissue+is%3Aopen+label%3Awindows).
+
+ Also, 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),
+ [node-commit-msg](https://github.com/clns/node-commit-msg) (Node.js) or [commitlint](http://marionebl.github.io/commitlint) (Node.js).
+
+
+!!! important
+ **Gitlint requires Python 3.7 (or above). For Python 2.7 and Python 3.5 use `gitlint==0.14.0` (released 2020-10-24), for Python 3.6 `gitlint==0.18.0` (released 2022-11-16).**
+
+## Features
+ - **Commit message hook**: [Auto-trigger validations against new commit message right when you're committing](#using-gitlint-as-a-commit-msg-hook). Also [works with pre-commit](#using-gitlint-through-pre-commit).
+ - **Easily integrated**: Gitlint is designed to work [with your own scripts or CI system](#using-gitlint-in-a-ci-environment).
+ - **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](https://addamhardy.com/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.
+ - **Easily configurable:** Gitlint has sane defaults, but [you can also easily customize it to your own liking](configuration.md).
+ - **Community contributed rules**: Conventions that are common but not universal [can be selectively enabled](contrib_rules.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.md).
+ - **Full unicode support:** Lint your Russian, Chinese or Emoji commit messages with ease!
+ - **Production-ready:** Gitlint checks a lot of the boxes you're looking for: actively maintained, high unit test coverage, integration tests,
+ python code standards ([black](https://github.com/psf/black), [ruff](https://github.com/charliermarsh/ruff)),
+ good documentation, widely used, proven track record.
+
+## Getting Started
+### Installation
+```sh
+# Pip is recommended to install the latest version
+pip install gitlint
+
+# Alternative: by default, gitlint is installed with pinned dependencies.
+# To install gitlint with looser dependency requirements, only install gitlint-core.
+pip install gitlint-core
+
+# Community maintained packages:
+brew install gitlint # Homebrew (macOS)
+sudo port install gitlint # Macports (macOS)
+apt-get install gitlint # Ubuntu
+# Other package managers, see https://repology.org/project/gitlint/versions
+
+# Docker: https://hub.docker.com/r/jorisroovers/gitlint
+docker run --ulimit nofile=1024 -v $(pwd):/repo jorisroovers/gitlint
+# NOTE: --ulimit is required to work around a limitation in Docker
+# Details: https://github.com/jorisroovers/gitlint/issues/129
+```
+
+### Usage
+```sh
+# Check the last commit message
+gitlint
+# Alternatively, pipe a commit message to gitlint:
+cat examples/commit-message-1 | gitlint
+# or
+git log -1 --pretty=%B | gitlint
+# Or read the commit-msg from a file, like so:
+gitlint --msg-filename examples/commit-message-2
+# Lint all commits in your repo
+gitlint --commits HEAD
+
+# To install a gitlint as a commit-msg git hook:
+gitlint install-hook
+```
+
+Output example:
+```sh
+$ cat examples/commit-message-2 | gitlint
+1: T1 Title exceeds max length (134>80): "This is the title of a commit message that is over 80 characters and contains hard tabs and trailing whitespace and the word wiping "
+1: T2 Title has trailing whitespace: "This is the title of a commit message that is over 80 characters and contains hard tabs and trailing whitespace and the word wiping "
+1: T4 Title contains hard tab characters (\t): "This is the title of a commit message that is over 80 characters and contains hard tabs and trailing whitespace and the word wiping "
+2: B4 Second line is not empty: "This line should not contain text"
+3: B1 Line exceeds max length (125>80): "Lines typically need to have a max length, meaning that they can't exceed a preset number of characters, usually 80 or 120. "
+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).
+
+### Shell completion
+
+```sh
+# Bash: add to ~/.bashrc
+eval "$(_GITLINT_COMPLETE=bash_source gitlint)"
+
+# Zsh: add to ~/.zshrc
+eval "$(_GITLINT_COMPLETE=zsh_source gitlint)"
+
+# Fish: add to ~/.config/fish/completions/foo-bar.fish
+eval (env _GITLINT_COMPLETE=fish_source gitlint)
+```
+
+## Configuration
+
+For in-depth documentation of general and rule-specific configuration options, have a look at the [Configuration](configuration.md) and [Rules](rules.md) pages.
+
+Short example `.gitlint` file ([full reference](configuration.md)):
+
+```ini
+[general]
+# Ignore certain rules (comma-separated list), you can reference them by
+# their id or by their full name
+ignore=body-is-missing,T3
+
+# Ignore any data sent to gitlint via stdin
+ignore-stdin=true
+
+# Configure title-max-length rule, set title length to 80 (72 = default)
+[title-max-length]
+line-length=80
+
+# You can also reference rules by their id (B1 = body-max-line-length)
+[B1]
+line-length=123
+```
+
+Example use of flags:
+
+```sh
+# Change gitlint's verbosity.
+$ gitlint -v
+# Ignore certain rules
+$ gitlint --ignore body-is-missing,T3
+# Enable debug mode
+$ gitlint --debug
+# Load user-defined rules (see http://jorisroovers.github.io/gitlint/user_defined_rules)
+$ gitlint --extra-path /home/joe/mygitlint_rules
+```
+
+Other commands and variations:
+
+```no-highlight
+$ gitlint --help
+Usage: gitlint [OPTIONS] COMMAND [ARGS]...
+
+ Git lint tool, checks your git commit messages for styling issues
+
+ Documentation: http://jorisroovers.github.io/gitlint
+
+Options:
+ --target DIRECTORY Path of the target git repository. [default:
+ current working directory]
+ -C, --config FILE Config file location [default: .gitlint]
+ -c TEXT Config flags in format <rule>.<option>=<value>
+ (e.g.: -c T1.line-length=80). Flag can be
+ used multiple times to set multiple config values.
+ --commit TEXT Hash (SHA) of specific commit to lint.
+ --commits TEXT The range of commits (refspec or comma-separated
+ hashes) to lint. [default: HEAD]
+ -e, --extra-path PATH Path to a directory or python module with extra
+ user-defined rules
+ --ignore TEXT Ignore rules (comma-separated by id or name).
+ --contrib TEXT Contrib rules to enable (comma-separated by id or
+ name).
+ --msg-filename FILENAME Path to a file containing a commit-msg.
+ --ignore-stdin Ignore any stdin data. Useful for running in CI
+ server.
+ --staged Attempt smart guesses about meta info (like
+ author name, email, branch, changed files, etc)
+ for staged commits.
+ --fail-without-commits Hard fail when the target commit range is empty.
+ -v, --verbose Verbosity, more v's for more verbose output
+ (e.g.: -v, -vv, -vvv). [default: -vvv]
+ -s, --silent Silent mode (no output).
+ Takes precedence over -v, -vv, -vvv.
+ -d, --debug Enable debugging output.
+ --version Show the version and exit.
+ --help Show this message and exit.
+
+Commands:
+ generate-config Generates a sample gitlint config file.
+ install-hook Install gitlint as a git commit-msg hook.
+ lint Lints a git repository [default command]
+ run-hook Runs the gitlint commit-msg hook.
+ uninstall-hook Uninstall gitlint commit-msg hook.
+
+ When no COMMAND is specified, gitlint defaults to 'gitlint lint'.
+```
+
+
+## Using gitlint as a commit-msg hook
+_Introduced in gitlint v0.4.0_
+
+You can also install gitlint as a git `commit-msg` hook so that gitlint checks your commit messages automatically
+after each commit.
+
+```sh
+gitlint install-hook
+# To remove the 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.
+ If you're looking to use gitlint in conjunction with other hooks, you should consider
+ [using gitlint with pre-commit](#using-gitlint-through-pre-commit).
+
+## Using gitlint through [pre-commit](https://pre-commit.com)
+
+`gitlint` can be configured as a plugin for the `pre-commit` git hooks
+framework. Simply add the configuration to your `.pre-commit-config.yaml`:
+
+```yaml
+- repo: https://github.com/jorisroovers/gitlint
+ rev: # Fill in a tag / sha here
+ hooks:
+ - id: gitlint
+```
+
+You then need to install the pre-commit hook like so:
+```sh
+pre-commit install --hook-type commit-msg
+```
+!!! important
+
+ It's important that you run `pre-commit install --hook-type commit-msg`, even if you've already used
+ `pre-commit install` before. `pre-commit install` does **not** install commit-msg hooks by default!
+
+To manually trigger gitlint using `pre-commit` for your last commit message, use the following command:
+```sh
+pre-commit run gitlint --hook-stage commit-msg --commit-msg-filename .git/COMMIT_EDITMSG
+```
+
+In case you want to change gitlint's behavior, you should either use a `.gitlint` file
+(see [Configuration](configuration.md)) or modify the gitlint invocation in
+your `.pre-commit-config.yaml` file like so:
+```yaml
+- repo: https://github.com/jorisroovers/gitlint
+ rev: # Fill in a tag / sha here (e.g. v0.18.0)
+ hooks:
+ - id: gitlint
+ args: [--contrib=CT1, --msg-filename]
+```
+
+!!! important
+
+ You need to add `--msg-filename` at the end of your custom `args` list as the gitlint-hook will fail otherwise.
+
+
+### gitlint and pre-commit in CI
+gitlint also supports a `gitlint-ci` pre-commit hook that can be used in CI environments.
+
+Configure it like so:
+```yaml
+- repo: https://github.com/jorisroovers/gitlint
+ rev: # insert ref, e.g. v0.18.0
+ hooks:
+ - id: gitlint # this is the regular commit-msg hook
+ - id: gitlint-ci # hook for CI environments
+```
+
+And invoke it in your CI environment like this:
+
+```sh
+pre-commit run --hook-stage manual gitlint-ci
+```
+
+By default this will only lint the latest commit.
+If you want to lint more commits you can modify the `gitlint-ci` hook like so:
+
+```yaml
+- repo: https://github.com/jorisroovers/gitlint
+ rev: # insert ref, e.g. v0.18.0
+ hooks:
+ - id: gitlint
+ - id: gitlint-ci
+ args: [--debug, --commits, mybranch] # enable debug mode, lint all commits in mybranch
+```
+
+## Using gitlint in a CI environment
+By default, when just running `gitlint` without additional parameters, gitlint lints the last commit in the current
+working directory.
+
+This makes it easy to use gitlint in a CI environment (Jenkins, TravisCI, Github Actions, pre-commit, CircleCI, Gitlab, etc).
+In fact, this is exactly what we do ourselves: on every commit,
+[we run gitlint as part of our CI checks](https://github.com/jorisroovers/gitlint/blob/v0.12.0/run_tests.sh#L133-L134).
+This will cause the build to fail when we submit a bad commit message.
+
+Alternatively, gitlint will also lint any commit message that you feed it via stdin like so:
+```sh
+# lint the last commit message
+git log -1 --pretty=%B | gitlint
+# lint a specific commit: 62c0519
+git log -1 --pretty=%B 62c0519 | gitlint
+```
+Note that gitlint requires that you specify `--pretty=%B` (=only print the log message, not the metadata),
+future versions of gitlint might fix this and not require the `--pretty` argument.
+
+## Linting specific commits or branches
+
+Gitlint can lint specific commits using `--commit`:
+```sh
+gitlint --commit 019cf40580a471a3958d3c346aa8bfd265fe5e16
+gitlint --commit 019cf40 # short SHAs work too
+gitlint --commit HEAD~2 # as do special references
+gitlint --commit mybranch # lint latest commit on a branch
+```
+
+You can also lint multiple commits using `--commits` (plural):
+
+```sh
+# Lint a specific commit range:
+gitlint --commits "019cf40...d6bc75a"
+# Lint all commits on a branch
+gitlint --commits mybranch
+# Lint all commits that are different between a branch and your main branch
+gitlint --commits "main..mybranch"
+# Use git's special references
+gitlint --commits "origin/main..HEAD"
+
+# You can also pass multiple, comma separated commit hashes:
+gitlint --commits 019cf40,c50eb150,d6bc75a
+# These can include special references as well
+gitlint --commits HEAD~1,mybranch-name,origin/main,d6bc75a
+# You can also lint a single commit with --commits:
+gitling --commits 019cf40,
+```
+
+The `--commits` flag takes a **single** refspec argument or commit range. Basically, any range that is understood
+by [git rev-list](https://git-scm.com/docs/git-rev-list) as a single argument will work.
+
+Alternatively, you can pass `--commits` a comma-separated list of commit hashes (both short and full-length SHAs work,
+as well as special references such as `HEAD` and branch names).
+Gitlint will treat these as pointers to **single** commits and lint these in the order you passed.
+`--commits` also accepts a single commit SHA with a trailing comma.
+
+For cases where the `--commits` option doesn't provide the flexibility you need, you can always use a simple shell
+script to lint an arbitrary set of commits, like shown in the example below.
+
+```sh
+#!/bin/sh
+
+for commit in $(git rev-list my-branch); do
+ echo "Commit $commit"
+ gitlint --commit $commit
+ echo "--------"
+done
+```
+
+!!! note
+ One downside to this approach is that you invoke gitlint once per commit vs. once per set of commits.
+ This means you'll incur the gitlint startup time once per commit, making it rather slow if you want to
+ lint a large set of commits. Always use `--commits` if you can to avoid this performance penalty.
+
+
+## Merge, fixup, squash and revert commits
+_Introduced in gitlint v0.7.0 (merge), v0.9.0 (fixup, squash), v0.13.0 (revert) and v0.18.0 (fixup=amend)_
+
+**Gitlint ignores merge, revert, fixup, and squash commits by default.**
+
+For merge and revert commits, the rationale for ignoring them is
+that most users keep git's default messages for these commits (i.e *Merge/Revert "[original commit message]"*).
+Often times these commit messages are also auto-generated through tools like github.
+These default/auto-generated commit messages tend to cause gitlint violations.
+For example, a common case is that *"Merge:"* being auto-prepended triggers a
+[title-max-length](rules.md#t1-title-max-length) violation. Most users don't want this, so we disable linting
+on Merge and Revert commits by default.
+
+For [squash](https://git-scm.com/docs/git-commit#git-commit---squashltcommitgt) and [fixup](https://git-scm.com/docs/git-commit#git-commit---fixupltcommitgt) (including [fixup=amend](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---fixupamendrewordltcommitgt)) commits, the rationale is that these are temporary
+commits that will be squashed into a different commit, and hence the commit messages for these commits are very
+short-lived and not intended to make it into the final commit history. In addition, by prepending *"fixup!"*,
+*"amend!"* or *"squash!"* to your commit message, certain gitlint rules might be violated
+(e.g. [title-max-length](rules.md#t1-title-max-length)) which is often undesirable.
+
+In case you *do* want to lint these commit messages, you can disable this behavior by setting the
+general `ignore-merge-commits`, `ignore-revert-commits`, `ignore-fixup-commits`, `ignore-fixup-amend-commits` or
+`ignore-squash-commits` option to `false`
+[using one of the various ways to configure gitlint](configuration.md).
+
+## Ignoring commits
+
+You can configure gitlint to ignore specific commits or parts of a commit.
+
+One way to do this, is by [adding a gitlint-ignore line to your commit message](configuration.md#commit-specific-config).
+
+If you have a case where you want to ignore a certain type of commits all-together, you can
+use gitlint's *ignore* rules.
+Here's a few examples snippets from a `.gitlint` file:
+
+```ini
+[ignore-by-title]
+# Match commit titles starting with Release
+regex=^Release(.*)
+ignore=title-max-length,body-min-length
+# ignore all rules by setting ignore to 'all'
+# ignore=all
+
+[ignore-by-body]
+# Match commits message bodies that have a line that contains 'release'
+regex=(.*)release(.*)
+ignore=all
+
+[ignore-by-author-name]
+# Match commits by author name (e.g. ignore all rules when a commit is made by dependabot)
+regex=dependabot
+ignore=all
+```
+
+If you just want to ignore certain lines in a commit, you can do that using the
+[ignore-body-lines](rules.md#i3-ignore-body-lines) rule.
+
+```ini
+# Ignore all lines that start with 'Co-Authored-By'
+[ignore-body-lines]
+regex=^Co-Authored-By
+```
+
+!!! warning
+
+ When ignoring specific lines, gitlint will no longer be aware of them while applying other rules.
+ This can sometimes be confusing for end-users, especially as line numbers of violations will typically no longer
+ match line numbers in the original commit message. Make sure to educate your users accordingly.
+
+!!! note
+
+ If you want to implement more complex ignore rules according to your own logic, you can do so using [user-defined
+ configuration rules](user_defined_rules.md#configuration-rules).
+
+## Named Rules
+
+Introduced in gitlint v0.14.0
+
+Named rules allow you to have multiple of the same rules active at the same time, which allows you to
+enforce the same rule multiple times but with different options. Named rules are so-called because they require an
+additional unique identifier (i.e. the rule *name*) during configuration.
+
+!!! warning
+
+ Named rules is an advanced topic. It's easy to make mistakes by defining conflicting instances of the same rule.
+ For example, by defining 2 `body-max-line-length` rules with different `line-length` options, you obviously create
+ a conflicting situation. Gitlint does not do any resolution of such conflicts, it's up to you to make sure
+ any configuration is non-conflicting. So caution advised!
+
+Defining a named rule is easy, for example using your `.gitlint` file:
+
+```ini
+# By adding the following section, you will add a second instance of the
+# title-must-not-contain-word (T5) rule (in addition to the one that is enabled
+# by default) with the name 'extra-words'.
+[title-must-not-contain-word:extra-words]
+words=foo,bar
+
+# So the generic form is
+# [<rule-id-or-name>:<your-chosen-name>]
+# Another example, referencing the rule type by id
+[T5:more-words]
+words=hur,dur
+
+# You can add as many additional rules and you can name them whatever you want
+# The only requirement is that names cannot contain whitespace or colons (:)
+[title-must-not-contain-word:This-Can_Be*Whatever$YouWant]
+words=wonderwoman,batman,power ranger
+```
+
+When executing gitlint, you will see the violations from the default `title-must-not-contain-word (T5)` rule, as well as
+the violations caused by the additional Named Rules.
+
+```sh
+$ gitlint
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: foo wonderwoman hur bar"
+1: T5:This-Can_Be*Whatever$YouWant Title contains the word 'wonderwoman' (case-insensitive): "WIP: foo wonderwoman hur bar"
+1: T5:extra-words Title contains the word 'foo' (case-insensitive): "WIP: foo wonderwoman hur bar"
+1: T5:extra-words Title contains the word 'bar' (case-insensitive): "WIP: foo wonderwoman hur bar"
+1: T5:more-words Title contains the word 'hur' (case-insensitive): "WIP: foo wonderwoman hur bar"
+```
+
+Named rules are further treated identical to all other rules in gitlint:
+
+- You can reference them by their full name, when e.g. adding them to your `ignore` configuration
+```ini
+# .gitlint file example
+[general]
+ignore=T5:more-words,title-must-not-contain-word:extra-words
+```
+
+- You can use them to instantiate multiple of the same [user-defined rule](user_defined_rules.md)
+- You can configure them using [any of the ways you can configure regular gitlint rules](configuration.md)
+
+
+## Exit codes
+Gitlint uses the exit code as a simple way to indicate the number of violations found.
+Some exit codes are used to indicate special errors as indicated in the table below.
+
+Because of these special error codes and the fact that
+[bash only supports exit codes between 0 and 255](http://tldp.org/LDP/abs/html/exitcodes.html), the maximum number
+of violations counted by the exit code is 252. Note that gitlint does not have a limit on the number of violations
+it can detect, it will just always return with exit code 252 when the number of violations is greater than or equal
+to 252.
+
+| Exit Code | Description |
+| --------- | ------------------------------------------ |
+| 253 | Wrong invocation of the `gitlint` command. |
+| 254 | Something went wrong when invoking git. |
+| 255 | Invalid gitlint configuration |
diff --git a/docs/rules.md b/docs/rules.md
new file mode 100644
index 0000000..a992f26
--- /dev/null
+++ b/docs/rules.md
@@ -0,0 +1,461 @@
+# Overview
+
+The table below shows an overview of all gitlint's built-in rules, with more specific details further down the page.
+
+Gitlint also has [community **contrib**uted rules](contrib_rules.md) which are not listed here as they're disabled by default.
+
+In addition, you can also [write your own user-defined rule](user_defined_rules.md) in case you don't find
+what you're looking for.
+
+
+| ID | Name | gitlint version | Description |
+| --- | --------------------------- | --------------- | ------------------------------------------------------------------------------------------- |
+| T1 | title-max-length | >= 0.1.0 | Title length must be &lt;= 72 chars. |
+| T2 | title-trailing-whitespace | >= 0.1.0 | Title cannot have trailing whitespace (space or tab) |
+| T3 | title-trailing-punctuation | >= 0.1.0 | Title cannot have trailing punctuation (?:!.,;) |
+| T4 | title-hard-tab | >= 0.1.0 | Title cannot contain hard tab characters (\t) |
+| 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-min-length | >= 0.14.0 | Title length must be &gt;= 5 chars. |
+| B1 | body-max-line-length | >= 0.1.0 | Lines in the body must be &lt;= 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) |
+| B4 | body-first-line-empty | >= 0.1.0 | First line of the body (second line of commit message) must be empty |
+| B5 | body-min-length | >= 0.4.0 | Body length must be at least 20 characters |
+| B6 | body-is-missing | >= 0.4.0 | Body message must be specified |
+| B7 | body-changed-file-mention | >= 0.4.0 | Body must contain references to certain files if those files are changed in the last commit |
+| B8 | body-match-regex | >= 0.14.0 | Body must match a given regex (default: None) |
+| M1 | author-valid-email | >= 0.9.0 | Author email address must be a valid email address |
+| I1 | ignore-by-title | >= 0.10.0 | Ignore a commit based on matching its title |
+| I2 | ignore-by-body | >= 0.10.0 | Ignore a commit based on matching its body |
+| I3 | ignore-body-lines | >= 0.14.0 | Ignore certain lines in a commit body that match a regex |
+| I4 | ignore-by-author-name | >= 0.16.0 | Ignore a commit based on matching its author name |
+
+
+
+## T1: title-max-length
+
+| ID | Name | gitlint version | Description |
+| --- | ---------------- | --------------- | ------------------------------------ |
+| T1 | title-max-length | >= 0.1 | Title length must be &lt;= 72 chars. |
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----------- | --------------- | ------- | ---------------------------- |
+| line-length | >= 0.2 | 72 | Maximum allowed title length |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Titles should be max 72 chars
+[title-max-length]
+line-length=72
+
+# It's the 21st century, titles can be 120 chars long
+[title-max-length]
+line-length=120
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## T2: title-trailing-whitespace
+
+| ID | Name | gitlint version | Description |
+| --- | ------------------------- | --------------- | ---------------------------------------------------- |
+| T2 | title-trailing-whitespace | >= 0.1 | Title cannot have trailing whitespace (space or tab) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## T3: title-trailing-punctuation
+
+| ID | Name | gitlint version | Description |
+| --- | -------------------------- | --------------- | ----------------------------------------------- |
+| T3 | title-trailing-punctuation | >= 0.1 | Title cannot have trailing punctuation (?:!.,;) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## T4: title-hard-tab
+
+| ID | Name | gitlint version | Description |
+| --- | -------------- | --------------- | --------------------------------------------- |
+| T4 | title-hard-tab | >= 0.1 | Title cannot contain hard tab characters (\t) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## T5: title-must-not-contain-word
+
+| ID | Name | gitlint version | Description |
+| --- | --------------------------- | --------------- | --------------------------------------------------- |
+| T5 | title-must-not-contain-word | >= 0.1 | Title cannot contain certain words (default: "WIP") |
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----- | --------------- | ------- | ------------------------------------------------------------------------------------------------ |
+| words | >= 0.3 | WIP | Comma-separated list of words that should not be used in the title. Matching is case insensitive |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ensure the title doesn't contain swear words
+[title-must-not-contain-word]
+words=crap,darn,damn
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## T6: title-leading-whitespace
+
+| ID | Name | gitlint version | Description |
+| --- | ------------------------ | --------------- | --------------------------------------------------- |
+| T6 | title-leading-whitespace | >= 0.4 | Title cannot have leading whitespace (space or tab) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## T7: title-match-regex
+
+| ID | Name | gitlint version | Description |
+| --- | ----------------- | --------------- | -------------------------------------------- |
+| T7 | title-match-regex | >= 0.5 | Title must match a given regex (default: .*) |
+
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----- | --------------- | ------- | ------------------------------------------------------------------------------------ |
+| regex | >= 0.5 | .* | [Python regex](https://docs.python.org/library/re.html) that the title should match. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ensure every title starts with a user-story like US123
+[title-match-regex]
+regex=^US[1-9][0-9]*
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## T8: title-min-length ##
+
+| ID | Name | gitlint version | Description |
+| --- | ---------------- | --------------- | ----------------------------------- |
+| T8 | title-min-length | >= 0.14.0 | Title length must be &gt;= 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
+
+| ID | Name | gitlint version | Description |
+| --- | -------------------- | --------------- | ---------------------------------------- |
+| B1 | body-max-line-length | >= 0.1 | Lines in the body must be &lt;= 80 chars |
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----------- | --------------- | ------- | ------------------------------------------------------ |
+| line-length | >= 0.2 | 80 | Maximum allowed line length in the commit message body |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# It's the 21st century, lines can be 120 chars long
+[body-max-line-length]
+line-length=120
+
+# Your tool prefers 72
+[body-max-line-length]
+line-length=72
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## B2: body-trailing-whitespace
+
+| ID | Name | gitlint version | Description |
+| --- | ------------------------ | --------------- | --------------------------------------------------- |
+| B2 | body-trailing-whitespace | >= 0.1 | Body cannot have trailing whitespace (space or tab) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## B3: body-hard-tab
+
+| ID | Name | gitlint version | Description |
+| --- | ------------- | --------------- | -------------------------------------------- |
+| B3 | body-hard-tab | >= 0.1 | Body cannot contain hard tab characters (\t) |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## B4: body-first-line-empty
+
+| ID | Name | gitlint version | Description |
+| --- | --------------------- | --------------- | -------------------------------------------------------------------- |
+| B4 | body-first-line-empty | >= 0.1 | First line of the body (second line of commit message) must be empty |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## B5: body-min-length
+
+| ID | Name | gitlint version | Description |
+| --- | --------------- | --------------- | ------------------------------------------------------------------------------------------------------------ |
+| B5 | body-min-length | >= 0.4 | Body length must be at least 20 characters. In versions >= 0.8.0, gitlint will not count newline characters. |
+
+### Options ###
+
+| Name | gitlint version | Default | Description |
+| ---------- | --------------- | ------- | --------------------------------------------- |
+| min-length | >= 0.4 | 20 | Minimum number of required characters in body |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# You want *something* in every commit body, but doesn't have to be as long as 20 chars.
+[body-min-length]
+min-length=5
+
+# You want a more elaborate message in every commit body
+[body-min-length]
+min-length=100
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## B6: body-is-missing
+
+| ID | Name | gitlint version | Description |
+| --- | --------------- | --------------- | ------------------------------ |
+| B6 | body-is-missing | >= 0.4 | Body message must be specified |
+
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| -------------------- | --------------- | ------- | ------------------------------------------------------------------------------------- |
+| ignore-merge-commits | >= 0.4 | true | Whether this rule should be ignored during merge commits. Allowed values: true,false. |
+
+------------------------------------------------------------------------------------------------------------------------
+
+## B7: body-changed-file-mention
+
+| ID | Name | gitlint version | Description |
+| --- | ------------------------- | --------------- | ------------------------------------------------------------------------------------------- |
+| B7 | body-changed-file-mention | >= 0.4 | Body must contain references to certain files if those files are changed in the last commit |
+
+### Options
+
+| 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. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Prevent that certain sensitive files are committed by mistake by forcing
+# users to mention them explicitly if they're deliberately changing them
+[body-changed-file-mention]
+files=generated.xml,secrets.txt,private-key.pem
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## B8: body-match-regex
+
+| ID | Name | gitlint version | Description |
+| --- | ---------------- | --------------- | ----------------------------- |
+| B8 | body-match-regex | >= 0.14 | Body must match a given regex |
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----- | --------------- | ------- | ----------------------------------------------------------------------------------- |
+| regex | >= 0.14 | None | [Python regex](https://docs.python.org/library/re.html) that the body should match. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ensure the body ends with Reviewed-By: <some value>
+[body-match-regex]
+regex=Reviewed-By:(.*)$
+
+# Ensure body contains the word "Foo" somewhere
+[body-match-regex]
+regex=(*.)Foo(.*)
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## 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.9.0 | `[^@ ]+@[^@ ]+\.[^@ ]+` | [Python regex](https://docs.python.org/library/re.html) the commit author email address is matched against |
+
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Only allow email addresses from a foo.com domain
+[author-valid-email]
+regex=[^@]+@foo.com
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## I1: ignore-by-title
+
+| ID | Name | gitlint version | Description |
+| --- | --------------- | --------------- | -------------------------------------------- |
+| I1 | ignore-by-title | >= 0.10.0 | Ignore a commit based on matching its title. |
+
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ------ | --------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| regex | >= 0.10.0 | None | [Python regex](https://docs.python.org/library/re.html) to match against commit title. On match, the commit will be ignored. |
+| ignore | >= 0.10.0 | all | Comma-separated list of rule names or ids to ignore when this rule is matched. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Match commit titles starting with Release
+# For those commits, ignore title-max-length and body-min-length rules
+[ignore-by-title]
+regex=^Release(.*)
+ignore=title-max-length,body-min-length
+# ignore all rules by setting ignore to 'all'
+# ignore=all
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## 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 | [Python regex](https://docs.python.org/library/re.html) to match against each line of the body. On match, the commit will be ignored. |
+| ignore | >= 0.10.0 | all | Comma-separated list of rule names or ids to ignore when this rule is matched. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ignore all commits with a commit message body with a line that contains 'release'
+[ignore-by-body]
+regex=(.*)release(.*)
+ignore=all
+
+# For matching commits, only ignore rules T1, body-min-length, B6.
+# You can use both names as well as ids to refer to other rules.
+[ignore-by-body]
+regex=(.*)release(.*)
+ignore=T1,body-min-length,B6
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## I3: ignore-body-lines
+
+| ID | Name | gitlint version | Description |
+| --- | ----------------- | --------------- | --------------------------------------------------------- |
+| I3 | ignore-body-lines | >= 0.14.0 | Ignore certain lines in a commit body that match a regex. |
+
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ----- | --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| regex | >= 0.14.0 | None | [Python regex](https://docs.python.org/library/re.html) to match against each line of the body. On match, that line will be ignored by gitlint (the rest of the body will still be linted). |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ignore all lines that start with 'Co-Authored-By'
+[ignore-body-lines]
+regex=^Co-Authored-By
+
+# Ignore lines that start with 'Co-Authored-By' or with 'Signed-off-by'
+[ignore-body-lines]
+regex=(^Co-Authored-By)|(^Signed-off-by)
+
+# Ignore lines that contain 'foobar'
+[ignore-body-lines]
+regex=(.*)foobar(.*)
+```
+------------------------------------------------------------------------------------------------------------------------
+
+## I4: ignore-by-author-name
+
+| ID | Name | gitlint version | Description |
+| --- | --------------------- | --------------- | -------------------------------------------------- |
+| I4 | ignore-by-author-name | >= 0.16.0 | Ignore a commit based on matching its author name. |
+
+### Options
+
+| Name | gitlint version | Default | Description |
+| ------ | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| regex | >= 0.16.0 | None | [Python regex](https://docs.python.org/library/re.html) to match against the commit author name. On match, the commit will be ignored. |
+| ignore | >= 0.16.0 | all | Comma-separated list of rule names or ids to ignore when this rule is matched. |
+
+### Examples
+
+#### .gitlint
+
+```ini
+# Ignore all commits authored by dependabot
+[ignore-by-author-name]
+regex=dependabot
+
+# For commits made by anyone with "[bot]" in their name, ignore
+# rules T1, body-min-length and B6
+[ignore-by-author-name]
+regex=(.*)\[bot\](.*)
+ignore=T1,body-min-length,B6
+```
diff --git a/docs/user_defined_rules.md b/docs/user_defined_rules.md
new file mode 100644
index 0000000..db21809
--- /dev/null
+++ b/docs/user_defined_rules.md
@@ -0,0 +1,415 @@
+# User Defined Rules
+_Introduced in gitlint v0.8.0_
+
+Gitlint supports the concept of **user-defined** rules: the ability for users to write their own custom rules in python.
+
+In a nutshell, use `--extra-path /home/joe/myextensions` to point gitlint to a `myextensions` directory where it will search
+for python files containing gitlint rule classes. You can also specify a single python module, ie
+`--extra-path /home/joe/my_rules.py`.
+
+```sh
+cat examples/commit-message-1 | gitlint --extra-path examples/
+# Example output of a user-defined Signed-off-by rule
+1: UC2 Body does not contain a 'Signed-off-by Line'
+# other violations were removed for brevity
+```
+
+The `SignedOffBy` user-defined `CommitRule` was discovered by gitlint when it scanned
+[examples/gitlint/my_commit_rules.py](https://github.com/jorisroovers/gitlint/blob/main/examples/my_commit_rules.py),
+which is part of the examples directory that was passed via `--extra-path`:
+
+```python
+# -*- coding: utf-8 -*-
+from gitlint.rules import CommitRule, RuleViolation
+
+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 a *unique* id, we recommend starting with UC
+ # (for User-defined Commit-rule).
+ id = "UC2"
+
+ def validate(self, commit):
+ self.log.debug("SignedOffBy: This will be visible when running `gitlint --debug`")
+
+ for line in commit.message.body:
+ if line.startswith("Signed-off-by"):
+ return
+
+ msg = "Body does not contain a 'Signed-off-by' line"
+ return [RuleViolation(self.id, msg, line_nr=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](configuration.md).
+
+If you want to check whether your rules are properly discovered by gitlint, you can use the `--debug` flag:
+
+```sh
+$ 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/main/examples) directory and modify it to your needs.
+ The remainder of this page contains the technical details, mostly for reference.
+
+## Line and Commit Rules
+The `SignedOffBy` class above was an example of a user-defined `CommitRule`. 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 e.g.: [examples/my_line_rules.py](https://github.com/jorisroovers/gitlint/blob/main/examples/my_line_rules.py))
+in that a `CommitRule` is only applied once on an entire commit while a `LineRule` is applied for every line in the commit
+(you can also apply it once to the title using a `target` - see the examples section below).
+
+The benefit of a commit rule is that it allows commit rules to implement more complex checks that span multiple lines and/or checks
+that should only be done once per commit.
+
+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.
+
+### Examples
+
+In terms of code, writing your own `CommitRule` or `LineRule` is very similar.
+The only 2 differences between a `CommitRule` and a `LineRule` are the parameters of the `validate(...)` method and the extra
+`target` attribute that `LineRule` requires.
+
+Consider the following `CommitRule` that can be found in [examples/my_commit_rules.py](https://github.com/jorisroovers/gitlint/blob/main/examples/my_commit_rules.py):
+
+```python
+# -*- coding: utf-8 -*-
+from gitlint.rules import CommitRule, RuleViolation
+
+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 a *unique* id, we recommend starting with UC
+ # (for User-defined Commit-rule).
+ id = "UC2"
+
+ def validate(self, commit):
+ self.log.debug("SignedOffBy: This will be visible when running `gitlint --debug`")
+
+ for line in commit.message.body:
+ if line.startswith("Signed-off-by"):
+ return
+
+ msg = "Body does not contain a 'Signed-off-by' line"
+ return [RuleViolation(self.id, msg, line_nr=1)]
+```
+Note the use of the `name` and `id` class attributes and the `validate(...)` method taking a single `commit` parameter.
+
+Contrast this with the following `LineRule` that can be found in [examples/my_line_rules.py](https://github.com/jorisroovers/gitlint/blob/main/examples/my_line_rules.py):
+
+```python
+# -*- coding: utf-8 -*-
+from gitlint.rules import LineRule, RuleViolation, CommitMessageTitle
+from gitlint.options import ListOption
+
+class SpecialChars(LineRule):
+ """ This rule will enforce that the commit message title does not contai
+ any of the following characters:
+ $^%@!*() """
+
+ # A rule MUST have a human friendly name
+ name = "title-no-special-chars"
+
+ # A rule MUST have a *unique* id, we recommend starting with UL
+ # for User-defined Line-rule), but this can really be anything.
+ id = "UL1"
+
+ # A line-rule MUST have a target (not required for CommitRules).
+ target = CommitMessageTitle
+
+ # A rule MAY have an option_spec if its behavior should be configurable.
+ options_spec = [ListOption('special-chars', ['$', '^', '%', '@', '!', '*', '(', ')'],
+ "Comma separated list of characters that should not occur in the title")]
+
+ def validate(self, line, _commit):
+ self.log.debug("SpecialChars: This will be visible when running `gitlint --debug`")
+
+ violations = []
+ # options can be accessed by looking them up by their name in self.options
+ for char in self.options['special-chars'].value:
+ if char in line:
+ msg = f"Title contains the special character '{char}'"
+ violation = RuleViolation(self.id, msg, line)
+ violations.append(violation)
+
+ return violations
+
+```
+
+Note the following 2 differences:
+
+- **extra `target` class attribute**: in this example set to `CommitMessageTitle` indicating that this `LineRule`
+should only be applied once to the commit message title. The alternative value for `target` is `CommitMessageBody`,
+ in which case gitlint will apply
+your rule to **every** line in the commit message body.
+- **`validate(...)` takes 2 parameters**: Line rules get the `line` against which they are applied as the first parameter and
+the `commit` object of which the line is part of as second.
+
+In addition, you probably also noticed the extra `options_spec` class attribute which allows you to make your rules configurable.
+Options are not unique to `LineRule`s, they can also be used by `CommitRule`s and are further explained in the
+[Options](user_defined_rules.md#options) section below.
+
+
+## The commit object
+Both `CommitRule`s and `LineRule`s take a `commit` object in their `validate(...)` methods.
+The table below outlines the various attributes of that commit object that can be used during validation.
+
+
+| Property | Type | Description |
+| -------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------ |
+| commit | `GitCommit` | Python object representing the commit |
+| commit.message | `GitCommitMessage` | Python object representing the commit message |
+| commit.message.original | `str` | Original commit message as returned by git |
+| commit.message.full | `str` | Full commit message, with comments (lines starting with #) removed. |
+| commit.message.title | `str` | Title/subject of the commit message: the first line |
+| commit.message.body | `str[]` | List of lines in the body of the commit message (i.e. starting from the second line) |
+| commit.author_name | `str` | Name of the author, result of `git log --pretty=%aN` |
+| commit.author_email | `str` | Email of the author, result of `git log --pretty=%aE` |
+| commit.date | `datetime.datetime` | Python `datetime` object representing the time of commit |
+| commit.is_merge_commit | `bool` | Boolean indicating whether the commit is a merge commit or not. |
+| commit.is_revert_commit | `bool` | Boolean indicating whether the commit is a revert commit or not. |
+| commit.is_fixup_commit | `bool` | Boolean indicating whether the commit is a fixup commit or not. |
+| commit.is_fixup_amend_commit | `bool` | Boolean indicating whether the commit is a (fixup) amend commit or not. |
+| commit.is_squash_commit | `bool` | Boolean indicating whether the commit is a squash commit or not. |
+| commit.parents | `str[]` | List of parent commit `sha`s (only for merge commits). |
+| commit.changed_files | `str[]` | List of files changed in the commit (relative paths). |
+| commit.changed_files_stats | `dict[str, GitChangedFilesStats]` | Dictionary mapping the changed files to a `GitChangedFilesStats` objects |
+| commit.changed_files_stats["path"].filepath | `pathlib.Path` | Relative path (compared to repo root) of the file that was changed. |
+| commit.changed_files_stats["path"].additions | `int` | Number of additions in the file. |
+| commit.changed_files_stats["path"].deletions | `int` | Number of deletions in the file. |
+| commit.branches | `str[]` | List of branch names the commit is part of |
+| commit.context | `GitContext` | Object pointing to the bigger git context that the commit is part of |
+| commit.context.current_branch | `str` | Name of the currently active branch (of local repo) |
+| commit.context.repository_path | `str` | Absolute path pointing to the git repository being linted |
+| commit.context.commits | `GitCommit[]` | List of commits gitlint is acting on, NOT all commits in the repo. |
+
+## Violations
+In order to let gitlint know that there is a violation in the commit being linted, users should have the `validate(...)`
+method in their rules return a list of `RuleViolation`s.
+
+!!! important
+ The `validate(...)` method doesn't always need to return a list, you can just skip the return statement in case there are no violations.
+ However, in case of a single violation, validate should return a **list** with a single item.
+
+The `RuleViolation` class has the following generic signature:
+
+```python
+RuleViolation(rule_id, message, content=None, line_nr=None):
+```
+With the parameters meaning the following:
+
+| Parameter | Type | Description |
+| --------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| rule_id | `str` | Rule's unique string id |
+| message | `str` | Short description of the violation |
+| content | `str` | (optional) the violating part of commit or line |
+| line_nr | `int` | (optional) line number in the commit message where the violation occurs. **Automatically set to the correct line number for `LineRule`s if not set explicitly.** |
+
+A typical `validate(...)` implementation for a `CommitRule` would then be as follows:
+```python
+def validate(self, commit)
+ for line_nr, line in commit.message.body:
+ if "Jon Snow" in line:
+ # we add 1 to the line_nr because we offset the title which is on the first line
+ return [RuleViolation(self.id, "Commit message has the words 'Jon Snow' in it", line, line_nr + 1)]
+ return []
+```
+
+The parameters of this `RuleViolation` can be directly mapped onto gitlint's output as follows:
+
+![How Rule violations map to gitlint output](images/RuleViolation.png)
+
+## Options
+
+In order to make your own rules configurable, you can add an optional `options_spec` attribute to your rule class
+(supported for both `LineRule` and `CommitRule`).
+
+```python
+# -*- coding: utf-8 -*-
+from gitlint.rules import CommitRule, RuleViolation
+from gitlint.options import IntOption
+
+class BodyMaxLineCount(CommitRule):
+ # A rule MUST have a human friendly name
+ name = "body-max-line-count"
+
+ # A rule MUST have a *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('max-line-count', 3, "Maximum body line count")]
+
+ def validate(self, commit):
+ line_count = len(commit.message.body)
+ max_line_count = self.options['max-line-count'].value
+ if line_count > max_line_count:
+ message = f"Body contains too many lines ({line_count} > {max_line_count})"
+ return [RuleViolation(self.id, message, line_nr=1)]
+```
+
+
+By using `options_spec`, you make your option available to be configured through a `.gitlint` file
+or one of the [other ways to configure gitlint](configuration.md). Gitlint automatically takes care of the parsing and input validation.
+
+For example, to change the value of the `max-line-count` option, add the following to your `.gitlint` file:
+```ini
+[body-max-line-count]
+body-max-line-count=1
+```
+
+As `options_spec` is a list, you can obviously have multiple options per rule. The general signature of an option is:
+`Option(name, default_value, description)`.
+
+Gitlint supports a variety of different option types, all can be imported from `gitlint.options`:
+
+| Option Class | Use for |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `StrOption ` | Strings |
+| `IntOption` | Integers. `IntOption` takes an optional `allow_negative` parameter if you want to allow negative integers. |
+| `BoolOption` | Booleans. Valid values: `true`, `false`. Case-insensitive. |
+| `ListOption` | List of strings. Comma separated. |
+| `PathOption` | Directory or file path. Takes an optional `type` parameter for specifying path type (`file`, `dir` (=default) or `both`). |
+| `RegexOption` | String representing a [Python-style regex](https://docs.python.org/library/re.html) - compiled and validated before rules are applied. |
+
+!!! note
+ Gitlint currently does not support options for all possible types (e.g. float, list of int, etc).
+ [We could use a hand getting those implemented](contributing.md)!
+
+
+## Configuration Rules
+
+_Introduced in gitlint v0.14.0_
+
+Configuration rules are special rules that are applied once per commit and *BEFORE* any other rules are run.
+Configuration rules are meant to dynamically change gitlint's configuration and/or the commit that is about to be
+linted.
+A typically use-case for this is when you want to modifying gitlint's behavior for all rules against a commit matching
+specific circumstances.
+
+!!! warning
+ Configuration rules can drastically change the way gitlint behaves and are typically only needed for more advanced
+ use-cases. We recommend you double check:
+
+ 1. Whether gitlint already supports your use-case out-of-the-box (special call-out for [ignore rules](rules.md#i1-ignore-by-title) which allow you to ignore (parts) of your commit message).
+ 2. Whether there's a [Contrib Rule](contrib_rules.md) that implements your use-case.
+ 3. Whether you can implement your use-case using a regular Commit or Line user-defined rule (see above).
+
+
+As with other user-defined rules, the easiest way to get started is by copying [`my_configuration.py` from the examples directory](https://github.com/jorisroovers/gitlint/tree/main/examples/my_configuration_rules.py) and modifying it to fit your need.
+
+```python
+# -*- coding: utf-8 -*-
+from gitlint.rules import ConfigurationRule
+from gitlint.options import IntOption
+
+class ReleaseConfigurationRule(ConfigurationRule):
+ """
+ This rule will modify gitlint's behavior for Release Commits.
+
+ This example might not be the most realistic for a real-world scenario,
+ but is meant to give an overview of what's possible.
+ """
+
+ # A rule MUST have a human friendly name
+ name = "release-configuration-rule"
+
+ # A rule MUST have a *unique* id, we recommend starting with UCR
+ # (for User-defined Configuration-Rule), but this can really be anything.
+ id = "UCR1"
+
+ # A rule MAY have an option_spec if its behavior should be configurable.
+ options_spec = [IntOption('custom-verbosity', 2, "Gitlint verbosity for release commits")]
+
+ def apply(self, config, commit):
+ self.log.debug("ReleaseConfigurationRule: This will be visible when running `gitlint --debug`")
+
+ # If the commit title starts with 'Release', we want to modify
+ # how all subsequent rules interpret that commit
+ if commit.message.title.startswith("Release"):
+
+ # If your Release commit messages are auto-generated, the
+ # body might contain trailing whitespace. Let's ignore that
+ config.ignore.append("body-trailing-whitespace")
+
+ # Similarly, the body lines might exceed 80 chars,
+ # let's set gitlint's limit to 200
+ # To set rule options use:
+ # config.set_rule_option(<rule-name>, <rule-option>, <value>)
+ config.set_rule_option("body-max-line-length", "line-length", 200)
+
+ # For kicks, let's set gitlint's verbosity to 2
+ # To set general options use
+ # config.set_general_option(<general-option>, <value>)
+ config.set_general_option("verbosity", 2)
+ # We can also use custom options to make this configurable
+ config.set_general_option("verbosity", self.options['custom-verbosity'].value)
+
+ # Strip any lines starting with $ from the commit message
+ # (this only affects how gitlint sees your commit message, it does
+ # NOT modify your actual commit in git)
+ commit.message.body = [line for line in commit.message.body if not line.startswith("$")]
+
+ # You can add any extra properties you want to the commit object,
+ # these will be available later on in all rules.
+ commit.my_property = "This is my property"
+```
+
+For all available properties and methods on the `config` object, have a look at the
+[LintConfig class](https://github.com/jorisroovers/gitlint/blob/main/gitlint-core/gitlint/config.py). Please do not use any
+properties or methods starting with an underscore, as those are subject to change.
+
+
+## Rule requirements
+
+As long as you stick with simple rules that are similar to the sample user-defined rules (see the
+[examples](https://github.com/jorisroovers/gitlint/blob/main/examples/my_commit_rules.py) directory), gitlint
+should be able to discover and execute them. While clearly you can run any python code you want in your rules,
+you might run into some issues if you don't follow the conventions that gitlint requires.
+
+While the [rule finding source-code](https://github.com/jorisroovers/gitlint/blob/main/gitlint-core/gitlint/rule_finder.py) is the
+ultimate source of truth, here are some of the requirements that gitlint enforces.
+
+### Rule class requirements
+
+- Rules **must** extend from `LineRule`, `CommitRule` or `ConfigurationRule`
+- Rule classes **must** have `id` and `name` string attributes. The `options_spec` is optional,
+ but if set, it **must** be a list of gitlint Options.
+- `CommitRule` and `LineRule` 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.
+- `ConfigurationRule` classes **must** have an `apply` method that take `config` and `commit` as first and second parameters.
+- LineRule classes **must** have a `target` class attributes that is set to either `CommitMessageTitle` or `CommitMessageBody`.
+- User Rule id's **cannot** start with `R`, `T`, `B`, `M` or `I` as these rule ids are reserved for gitlint itself.
+- Rules **should** have a case-insensitive unique id as only one rule can exist with a given id. While gitlint does not
+ enforce this, having multiple rules with the same id might lead to unexpected or undeterministic behavior.
+
+### extra-path requirements
+- If `extra-path` is a directory, it does **not** need to be a proper python package, i.e. it doesn't require an `__init__.py` file.
+- Python files containing user-defined rules must have a `.py` extension. Files with a different extension will be ignored.
+- The `extra-path` will be searched non-recursively, i.e. all rule classes must be present at the top level `extra-path` directory.
+- User rule classes must be defined in the modules that are part of `extra-path`, rules that are imported from outside the `extra-path` will be ignored.