A selection of quality-of-life tools for use with pre-commit.
- 1. Usage
- 2. Hooks
- 3. Development
Add the following to the .pre-commit-config.yaml in your repo (or create it if necessary):
default_install_hook_types: [pre-commit, prepare-commit-msg]
repos:
- repo: https://github.com/BenjaminMummery/pre-commit-hooks
rev: ''
hooks:
- id: add-copyright
- id: update-copyright
- id: add-msg-issue
- id: sort-file-contents
files: .gitignore
- id: no-import-testtools-in-src
- id: americaniseEven if you've already installed pre-commit, it may be necessary to run:
pre-commit install(this is because add-msg-issue runs at the prepare-commit-msg stage which Pre-commit does not install to by default).
You should see the following output:
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
pre-commit installed at .git/hooks/prepare-commit-msgFor more information on pre-commit, see https://github.com/pre-commit/pre-commit
The following is a minimal example of a .git/hooks/prepare-commit-msg to run add-msg-issue:
#!/usr/bin/env bash
add-msg-issue $1Note that this assumes that you've installed add-msg-issue in your global python environment.
Check changed source files for something that looks like a copyright comment. If one is not found, insert one.
By default, the copyright message is constructed as a comment in the format
Copyright (c) <year> <name>
where the year is the current year, and the name is sourced from the git user.name configuration.
If required, the default behaviour can be overruled either by command line arguments or configuration files.
add-copyright [-n NAME] [-f FORMAT] [FILES]
- repo: https://github.com/BenjaminMummery/pre-commit-hooks
rev: v1.5.0
hooks:
- id: add-copyright
args: ["-n NAME", "-f FORMAT"]We currently support configuration options in pyproject.toml and setup.cfg files.
[tool.add_copyright]
name = "NAME"
format = "FORMAT"Behaviour for individual languages can also be configured:
[tool.add_copyright.python]
format = "FORMAT"
docstr = truewhere there is a conflict between individual language settings and the global tool settings, the language settings are given authority.
The add-copyright hook accepts the following command line arguments to control the values inserted into new copyright messages:
| Flag | Description |
|---|---|
-n / --name |
Set a custom name to be used rather than git's user.name |
-f / --format |
Set a custom f-string for the copyright to be inserted. Must contain {name} and {year}. |
If you're using a .pre-commit-config.yaml, these can be configured as follows:
repos:
- repo: https://github.com/BenjaminMummery/pre-commit-hooks
rev: v1.4.0
hooks:
- id: add-copyright
args: ["-n", "James T. Kirk", "-f", "Property of {name} as of {year}"]
- id: add-msg-issueAlternatively, a local configuration file can be specified:
[tool.add_copyright]
name = "James T. Kirk"
format = "Property of {name} as of {year}"The config file can contain name and format. Any properties that are not set by the config file will be inferred from the current year / git user name.
If command line arguments are not specified, the hook will look for a file named .add-copyright-hook-config.yaml in the root of the git repo, and read the name and year from there.
This file should be formatted as follows:
name: James T. Kirk
format: Property of {name} as of {year}The add-copyright hook currently runs on changed source files of the following types:
| Language | File Extension | Example |
|---|---|---|
| C++ | .cpp |
// Copyright (c) 1969 Buzz |
| C# | .cs |
/* Copyright (c) 1969 Buzz */ |
| CSS | .css |
/* Copyright (c) 1969 Buzz */ |
| Dart | .dart |
// Copyright (c) 1969 Buzz |
| HTML | .html |
<!--- Copyright (c) 1969 Buzz --> |
| Java | .java |
// Copyright (c) 1969 Buzz |
| Javascript | .js |
// Copyright (c) 1969 Buzz |
| Kotlin | .kt |
// Copyright (c) 1969 Buzz |
| Lua | .lua |
-- Copyright (c) 1969 Buzz |
| Markdown | .md |
<!--- Copyright (c) 1969 Buzz --> |
| Perl | .pl |
# Copyright (c) 1969 Buzz |
| PHP | .PHP |
// Copyright (c) 1969 Buzz |
| Python1 | .py |
# Copyright (c) 1969 Buzz |
| Ruby | .rb |
# Copyright (c) 1969 Buzz |
| Rust | .rst |
// Copyright (c) 1969 Buzz |
| Scala | .scala |
// Copyright (c) 1969 Buzz |
| SQL | .sql |
-- Copyright (c) 1969 Buzz |
| Swift | .swift |
// Copyright (c) 1969 Buzz |
Check changed source files for something that looks like a copyright comment. If one is found, the end date is checked against the current date and updated if it is out of date.
Search the branch name for something that looks like an issue message, and insert it into the commit message.
In a branch called feature/TEST-01/demo, the command git commit -m "test commit" -m "Some more description about our test commit." produces a commit message that reads
test commit
[TEST-01]
Some more description about our test commit.
If a message is not specified in the command line, the issue ID is instead inserted into the message prior to it opening in the editor. You should be greeted with something that looks like:
[TEST-01]
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch feature/TEST-01/demo
# Changes to be committed:
# new file: test.py
#Note that this means that the commit will not be aborted due to an empty message unless you delete the inserted ID.
If the default template is not to your liking, you can define your own by passing the --template argument:
- repo: https://github.com/BenjaminMummery/pre-commit-hooks
rev: v1.0.0
hooks:
- id: add-msg-issue
args: ["--template", "{issue_id}: {subject}\n\n{body}"]The template must include the following keywords:
{issue_id}
{subject}
{body}These correspond to the issue id, subject line, and body of the commit message. The default template is:
"{subject}\n\n[{issue_id}]\n{body}"The sort-file-contents hook sorts the lines in the specified files while retaining sections.
This is primarily aimed at managing large .gitignore files.
Sections are identified as sets of sequential lines preceded by a comment and separated from other sections by a blank line. The contents of each section are sorted alphabetically, while the overall structure of sections is unchanged. For example:
# section 1
delta
bravo
# section 2
charlie
alphawould be sorted as:
# section 1
bravo
delta
# section 2
alpha
charlieDevelopment of this hook was motivated by encountering file contents sorters that would produce the following:
# section 1
# section 2
alpha
bravo
charlie
deltaThe -u or --unique flag causes the hook to check the sorted lines for uniqueness.
Duplicate entries within the same section will be removed automatically;
lines that are duplicated between sections will be left in place and a warning raised to the user.
This latter behaviour is due to us not knowing which section the line should belong to.
This hook checks for imports of pytest and/or unittest in source files that are not test files (i.e. do not have 'test' somewhere in their path).
This hook checks for common non-US spellings of english words (e.g. 'initialise' rather than 'initialize') and corrects them. The hook will try to match the case of the original word, although this may be imprecise for complex case patterns when the correct spelling of the word is a different length.
Additional words can be manually added in the .pre-commit-config.yaml. For example, if we want to change all instances of absence to absence and all instances of forth to fourth, the configuration would be:
repos:
- repo: https://github.com/BenjaminMummery/pre-commit-hooks
rev: ''
hooks:
- id: americanise
args: ["-w absence:absence", "-w forth:fourth"]Individual instances can be excluded from this hook by marking them with an inline comment reading pragma: no americanise. For example:
def initialise(): # pragma: no americanise
print("initialise")will be corrected to:
def initialise(): # pragma: no americanise
print("initialize")Tests are organised in three levels:
- Unit: tests for individual methods. All other methods should be mocked. Hooks have a single entry point so are best tested with the integration tests, unit tests should be used where necessary.
- Integration: tests for combinations of methods.
- System: end-to-end tests.
Uses the
pre-commit try_repofacility.
The provided Makefile defines commands for running various combinations of tests:
- General Purpose:
test: run unit and integration tests and show coverage (fail fast).test_all: astest, but also runs system tests (fail fast).clean: remove the test venv and all temporary files.
- Testing by Level: run all tests of the specified level and show coverage (fail fast).
test_unittest_integrationtest_system
- Testing by hook: run all tests for the specified hook and show coverage (fail slow).
test_add_copyrighttest_add_issuetest_sort_file_contentstest_update_copyright
- Testing shared resources:
test_shared: run all unit tests for utilities on which multiple hooks rely and show coverage (fail fast).
Footnotes
-
For python files we also support inserting copyright info into/as module-level docstrings. To enable this, insert the following lines into your
pyproject.tomlor.add-copyright-hook-config.yaml:
↩[tool.add-copyright.python] docstr=true