gitlint
repository·main·Indexed 21 days ago
https://github.com/jorisroovers/gitlintA Python-based Git commit message linter that checks commit messages for style and consistency against defined rules. It can be installed as a git commit-msg hook, integrated with the pre-commit framework, or used in CI environments via gitlint-ci. Configuration is supported through a .gitlint INI file, CLI flags, and environment variables.
What's inside gitlint
- gitlint is a Python-based linter designed to check Git commit messages for style and consistency. It helps ensure that commit messages follow specific rules and standards within a project.
Understand the different types of gitlint rules
maingitlint categorizes rules into four distinct types that you can use to enforce commit message standards:
- Built-in rules: These are included with the core gitlint installation and are enabled by default.
- Community contributed rules (contrib): These are additional rules provided by the community. They are disabled by default but can be activated via your configuration file.
- User-defined rules: Custom rules that you can write yourself using a few lines of Python code.
- Named rules: This allows you to create multiple instances of a single rule, each identified by a unique name of your choosing.
How configuration rules work in gitlint
mainConfiguration rules are specialized user-defined rules that are applied once per commit BEFORE any other rules are run. They are designed to dynamically modify
gitlint's configuration or the commit object itself based on specific circumstances (e.g., modifying behavior for all 'Release' commits).Warning: Configuration rules can drastically change how
gitlintbehaves. Before implementing one, check if:gitlintsupports your use-case out-of-the-box (e.g., ignore rules).- A Contrib Rule already exists.
- You can achieve your goal using a standard Commit or Line user-defined rule.
To implement a configuration rule, inherit from
gitlint.rules.ConfigurationRuleand implement theapply(self, config, commit)method.from gitlint.rules import ConfigurationRule class MyConfigRule(ConfigurationRule): name = "my-rule-name" id = "UCR1" def apply(self, config, commit): # Modify config or commit here passHow CommitRule and LineRule differ
mainGitlint provides two types of user-defined rules for linting commit messages:
CommitRule: Applied once per commit. Use this for complex checks that span multiple lines or logic that should only execute once per commit (e.g., checking for the existence of a specific signature anywhere in the body).LineRule: Applied on a line-by-line basis. Use this for simpler, reusable checks targeting either the commit message title or every line in the body.
While every
LineRulecan be implemented as aCommitRule, the reverse is not true. The primary technical differences are thevalidate(...)method signature and the requirement of atargetattribute forLineRules.Understand gitlint configuration precedence
mainWhen multiple configuration sources are present, gitlint applies them in the following order (from highest priority to lowest):
- Commit specific config (e.g.,
gitlint-ignore: allin the commit message) - Configuration Rules (e.g., ignore-by-title)
- Commandline convenience flags (e.g.,
-vv,--silent,--ignore) - Environment variables (e.g.,
GITLINT_VERBOSITY=3) - Commandline configuration flags (e.g.,
-c title-max-length=123) - Configuration file (local
.gitlintfile, or file specified using-C/--config) - Default gitlint config
- Commit specific config (e.g.,
Use staged mode for manual commit message linting
mainWhen linting a commit message via stdin or
--msg-filename(where no actual git commit exists yet), enable--stagedto allow gitlint to use heuristics (like checkinggit configor staged changes) to guess metadata like author, email, and branch. Without this, rules requiring metadata (likeM1:author-valid-email) cannot be enforced.gitlint --staged # or in .gitlint [general] staged=trueEnable user-defined rules in gitlint
maingitlint allows you to write custom rules in Python. To use them, you must tell gitlint where to find your rule files using the
extra-pathoption. This option can point to either a directory containing Python files or a specific Python file. Paths can be relative to your current working directory or absolute paths.### Using .gitlint config ```ini [general] extra-path=tools/gitlint/myrulesUsing CLI
gitlint --extra-path "tools/gitlint/myrules" # Or using -c style config flags gitlint -c general.extra-path=tools/gitlint/myrulesUsing Environment Variables
GITLINT_EXTRA_PATH=tools/gitlint/myrules gitlintImplement a LineRule
mainTo create a
LineRule, extend theLineRuleclass.Requirements:
name: A human-friendly string.id: A unique identifier (it is recommended to start withULfor User-defined Line-rules).target: Specifies where the rule is applied. UseCommitMessageTitleto target only the title, orCommitMessageBodyto target every line in the body.validate(self, line, _commit): A method that receives the currentlineas the first argument and thecommitobject as the second.
Configurability: You can define
options_specusing classes likeListOptionto make your rule configurable. Options are accessed viaself.options["option-name"].value.Example: Enforcing that the commit title does not contain specific special characters.
from gitlint.rules import LineRule, RuleViolation, CommitMessageTitle from gitlint.options import ListOption class SpecialChars(LineRule): """Enforce that the commit message title does not contain specific characters.""" name = "title-no-special-chars" id = "UL1" target = CommitMessageTitle options_spec = [ ListOption( "special-chars", ["$", "^", "%", "@", "!", "*", "(", ")"], "Comma separated list of chars that cannot occur in the title", ) ] def validate(self, line, _commit): violations = [] 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 violationsImplement a CommitRule
mainTo create a
CommitRule, extend theCommitRuleclass.Requirements:
name: A human-friendly string.id: A unique identifier (it is recommended to start withUCfor User-defined Commit-rules).validate(self, commit): A method that receives a singlecommitargument. It should return aRuleViolation(or a list of them) if the commit fails validation, or return nothing if it passes.
Example: Checking for a "Signed-off-by" line in the commit body.
from gitlint.rules import CommitRule, RuleViolation class SignedOffBy(CommitRule): """Enforce that each commit contains a "Signed-off-by" line.""" name = "body-requires-signed-off-by" id = "UC2" def validate(self, commit): 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)]Use gitlint-ci in CI environments
mainFor Continuous Integration (CI) environments, use the
gitlint-cihook. This hook is designed to run in CI and can be configured to lint multiple commits.- Add both
gitlintandgitlint-cito your.pre-commit-config.yaml. - Invoke the hook in your CI environment using the
manualstage.
By default,
gitlint-cionly lints the latest commit. To lint a range of commits (e.g., all commits in a specific branch), pass the--commitsargument with the branch name.- repo: https://github.com/jorisroovers/gitlint rev: # Fill in a tag / sha here (e.g. v0.19.1) hooks: - id: gitlint - id: gitlint-ci args: [--debug, --commits, mybranch]# To run the CI hook pre-commit run --hook-stage manual gitlint-ci- Add both
Configure gitlint using a .gitlint file
mainThe recommended way to configure gitlint is by creating a
.gitlintfile in your repository. This file uses the INI format and allows you to manage general settings, ignore specific rules, enable community-contributed rules, and customize individual rule parameters.Key sections include:
[general]: Used for global settings likeignore,contrib, andextra-path.[rule-name]or[rule-id]: Used to configure specific rule parameters (e.g.,[T1]or[title-max-length]).
[general] # Ignore rules by name or ID ignore=title-trailing-punctuation, T3 # Enable community rules contrib=contrib-title-conventional-commits,CC1 # Path to user-defined rules extra-path=./gitlint_rules/my_rules.py [title-max-length] line-length=80 [title-min-length] min-length=5Report violations in user-defined rules
mainTo signal a violation within a custom rule, the
validate(...)method must return a list ofRuleViolationobjects.Key requirements for
validate(...):- If there are no violations, you can return an empty list
[]or simply skip the return statement. - If there is a single violation, you must return a list containing that one item (e.g.,
[RuleViolation(...)]). - For
LineRules, if you do not explicitly set theline_nr, gitlint will attempt to automatically set it to the correct line number.
def validate(self, commit): for line_nr, line in commit.message.body: if "Jon Snow" in line: # Add 1 to the line_nr to offset the title which is on the first line violation_line_nr = line_nr + 1 msg = "Commit message has the words 'Jon Snow' in it" return [RuleViolation(self.id, msg, line, violation_line_nr)] return []- If there are no violations, you can return an empty list