22 KiB
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 or as part of your gating script in a CI pipeline (e.g. Jenkins).
!!! note Gitlint works on Windows, but there are some known issues.
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. Also works with pre-commit.
- Easily integrated: Gitlint is designed to work with your own scripts or CI system.
- Sane defaults: Many of gitlint's validations are based on well-known, community, standards, 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.
- Community contributed rules: Conventions that are common but not universal can be selectively enabled.
- User-defined rules: Want to do more then what gitlint offers out of the box? Write your own user defined rules.
- 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, ruff), good documentation, widely used, proven track record.
Getting Started
Installation
# 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
# 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:
$ 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.
Shell completion
# 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 and Rules pages.
Short example .gitlint
file (full reference):
[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:
# 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:
$ 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.
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
gitlint
can be configured as a plugin for the pre-commit
git hooks
framework. Simply add the configuration to your .pre-commit-config.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:
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:
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) or modify the gitlint invocation in
your .pre-commit-config.yaml
file like so:
- 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:
- 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:
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:
- 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. 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:
# 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
:
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):
# 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 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.
#!/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 violation. Most users don't want this, so we disable linting on Merge and Revert commits by default.
For squash and fixup (including fixup=amend) 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) 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.
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.
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:
[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 rule.
# 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:
# 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.
$ 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
# .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
- You can configure them using any of the ways you can configure regular gitlint rules
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, 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 |