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:
iddefines the hook ID. Use the ID to refer to the hook on the command line in commands likepre-commit run.nameis what pre-commit displays when it runs the hook.languagedefines which programming language to run the hook with - here, the virtual language “fail”.entryis the command passed to the programming language. Forfail, it’s not really a command but the message to display on failure.filesis 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_typesprevents 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 theyamlfile 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
.gitignorefor 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.
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
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