diff options
Diffstat (limited to '')
-rw-r--r-- | docs/configuration.md | 628 | ||||
-rw-r--r-- | docs/contrib_rules.md | 83 | ||||
-rw-r--r-- | docs/contributing.md | 213 | ||||
-rw-r--r-- | docs/demos/asciicinema.json | 3798 | ||||
-rw-r--r-- | docs/demos/scenario.txt | 75 | ||||
-rw-r--r-- | docs/extra.css | 12 | ||||
-rw-r--r-- | docs/extra.js | 5 | ||||
-rw-r--r-- | docs/images/RuleViolation.png | bin | 0 -> 27806 bytes | |||
-rw-r--r-- | docs/images/RuleViolations.graffle | bin | 0 -> 3291 bytes | |||
-rw-r--r-- | docs/images/dev-container.png | bin | 0 -> 212226 bytes | |||
-rw-r--r-- | docs/images/gitlint-packages.drawio.svg | 351 | ||||
-rw-r--r-- | docs/images/gitlint-packages.png | bin | 0 -> 51975 bytes | |||
-rw-r--r-- | docs/images/readme-gitlint.png | bin | 0 -> 348007 bytes | |||
-rw-r--r-- | docs/index.md | 506 | ||||
-rw-r--r-- | docs/rules.md | 461 | ||||
-rw-r--r-- | docs/user_defined_rules.md | 415 |
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 Binary files differnew file mode 100644 index 0000000..410dca9 --- /dev/null +++ b/docs/images/RuleViolation.png diff --git a/docs/images/RuleViolations.graffle b/docs/images/RuleViolations.graffle Binary files differnew file mode 100644 index 0000000..1fea2dd --- /dev/null +++ b/docs/images/RuleViolations.graffle diff --git a/docs/images/dev-container.png b/docs/images/dev-container.png Binary files differnew file mode 100644 index 0000000..6cac5a2 --- /dev/null +++ b/docs/images/dev-container.png 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="<mxfile><diagram id="x7jBp0SZ1TbX-vMHKkT6" name="Page-1">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</diagram></mxfile>"> + <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>=8 + <br/> + <span> + arrow>=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>=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: "helvetica" ; 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: "helvetica" ; 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: "helvetica" ; 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: "helvetica" ; 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 Binary files differnew file mode 100644 index 0000000..00d3ec1 --- /dev/null +++ b/docs/images/gitlint-packages.png diff --git a/docs/images/readme-gitlint.png b/docs/images/readme-gitlint.png Binary files differnew file mode 100644 index 0000000..516c915 --- /dev/null +++ b/docs/images/readme-gitlint.png 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 <= 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 >= 5 chars. | +| B1 | body-max-line-length | >= 0.1.0 | Lines in the body must be <= 80 chars | +| B2 | body-trailing-whitespace | >= 0.1.0 | Body cannot have trailing whitespace (space or tab) | +| B3 | body-hard-tab | >= 0.1.0 | Body cannot contain hard tab characters (\t) | +| 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 <= 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 >= 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 <= 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. |