pre-commit: Block files based on name with a custom “fail” hook
pre-commit’s “fail” virtual language fails all files that it matches. Combine it with pre-commit’s file name and type matching to block unwanted files in your repository.
Let’s try it out with an example from working with Django’s fixtures system. Imagine you’ve recently transitioned your project from JSON to YAML fixtures because you prefer YAML’s readability. After converting all of your existing fixtures, you’re concerned that JSON fixtures may still get added in the future, complicating ongoing maintenance.
You can prevent non-YAML fixtures from being added with a “fail” hook. Define such a hook in your .pre-commit-config.yaml
as below.
repos:
- repo: local
hooks:
- id: check-fixture-types
name: Check fixture types
language: fail
entry: Please convert non-YAML fixtures to YAML.
files: /fixtures/
exclude_types: [yaml]
Let’s take apart the hook definition.
The first thing to note is that the hook appears under a repo: local
declaration. This special repository location indicates that the hooks are defined in this repository rather than another.
Several keys then define the hook:
id
defines the hook ID. Use the ID to refer to the hook on the command line in commands likepre-commit run
.name
is what pre-commit displays when it runs the hook.language
defines which programming language to run the hook with - here, the virtual language “fail”.entry
is the command passed to the programming language. Forfail
, it’s not really a command but the message to display on failure.files
is a regular expression used to select files. pre-commit searches for a match anywhere in the Unix-form file path relative to the root of the repository, for exampleexample/fixtures/books.json
. Here, we’re using/fixtures/
to match all files inside any directory calledfixtures
.exclude_types
prevents the named file types from matching. File types are simple tags detected by the identify library. This hook should match (fail) on any file that isn’t YAML, so it excludes theyaml
file type. For information on determining file type(s), see the pre-commit documentation.
After adding a hook, you need to stage your configuration:
$ git add .pre-commit-config.yaml
You can then test the hook against a known bad file:
$ pre-commit run --files example/fixtures/books.json
Check fixture types......................................................Failed
- hook id: check-fixture-types
- exit code: 1
Please convert non-YAML fixtures to YAML.
example/fixtures/books.json
The failure message lists the matched files.
Before you commit a new hook, you should check that all files pass:
$ pre-commit run check-fixture-types --all-files
Check fixture types......................................................Passed
All looks in order - commit as usual:
$ git commit -m "Add pre-commit hook to block non-YAML fixtures"
Check fixture types..................................(no files to check)Skipped
[main 13d1507] Add pre-commit hook to block non-YAML fixtures
1 file changed, 8 insertions(+)
Such “fail” hooks are ideal for blocking unwanted files, matching based on file name and type. But you can also prevent files from being committed with Git’s .gitignore
file. In general:
- Use
.gitignore
for files that can exist but not be committed. For example, compiled Python code (__pycache__
directories) or bundled JavaScript files. - Use pre-commit “fail” hooks for files that should not exist at all. For example, legacy file types (as above) or where tools rely on a given file name pattern (for example, pytest’s changelog files.
Learn more about pre-commit in my Git DX book.
One summary email a week, no spam, I pinky promise.
Related posts:
- pre-commit: Various Ways to Run Hooks
- Git: How to skip hooks
- pre-commit: How to create hooks for unsupported tools
Tags: pre-commit, django