Use Pre-commit in Any Repository Without Installing

Janne Kemppainen |

Pre-commit is a great tool written in Python that can be used to manage and maintain pre-commit hooks in Git projects. The traditional way to set it up is to install it globally or locally to the project Python virtual environment with pip. Here I’m going to share how to use pre-commit without installing it at all.

Pre-commit is only needed to configure the pre-commit hooks for a repository. After the initial setup it isn’t really needed until you need to edit the configs again. Therefore, we can just download the executable Python zip archive, install the hooks, and then delete the extra files.

I have previously written about using Make for Python development. In this post I’m going to continue using a Makefile because I think it is a clean and easy way to provide setup scripts, but you can choose to use any method you want. The commands should be easy to adapt to a Bash script if that’s what you prefer.

Your system needs to have the make command available for this to work properly.

Setup without installing

So, if you go to the pre-commit documentation and check the installation instructions you’ll notice that it can be installed as a 0-dependency zipapp. You can download the .pyz file from the GitHub releases page and run it with Python. The zipapp contains everything that’s needed to setup the Git hooks, so you don’t need to worry about virtual environments and such.

The zipapp file is always named in this format: pre-commit-<version-number>.pyz, so we can easily download the version that we want.

Create a new file called Makefile at the root of your project. Note that this file must be indented with tabs!

PRECOMMIT_VERSION="2.18.0"

.PHONY: pre-commit
pre-commit:
	wget -O pre-commit.pyz https://github.com/pre-commit/pre-commit/releases/download/v${PRECOMMIT_VERSION}/pre-commit-${PRECOMMIT_VERSION}.pyz
	python3 pre-commit.pyz install
	python3 pre-commit.pyz install --hook-type commit-msg
	rm pre-commit.pyz

Now you can run the command make pre-commit inside your project. It downloads the .pyz file and saves it as pre-commit.pyz, then it installs any configured hooks, and finally removes the zipapp.

The hooks can work at different stages, and each stage needs to be installed separately. The default installation uses the pre-commit type that works before commits. You can use the --hook-type argument to install the stages that you want, for example commit-msg to check the commit message.

With this method you don’t have to worry about which version of pre-commit the other developers are using since it always downloads the one that you’ve defined in the Makefile. Your project doesn’t need to contain a Python virtual environment for local installation, so this makes it easy to setup hooks for Node.js projects too.

Configure the hooks

Since we don’t have a configuration file yet, the setup doesn’t do anything meaningful. Here you can find a list of supported hooks.

For a Node.js project your .pre-commit-config.yaml file (at the root of the project) could look like this:

default_stages: [commit]
repos:
  - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
    rev: v4.1.0
    hooks:
      - id: commitlint
        stages: [commit-msg]
        additional_dependencies: ["@commitlint/config-conventional"]
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v2.2.1
    hooks:
      - id: prettier

The configuration format defines repositories that contain the hook definitions. This example installs commitlint-pre-commit-hook and Prettier.

The Prettier hook checks the formatting of your files and makes them pretty if they don’t follow your chosen style. If you’re using plugins with Prettier then you need to declare them under the additional_dependencies section. Find more information from the prettier mirror repository.

The commitlint-pre-commit-hook works at the commit-msg stage, which is why I added that in the Makefile too. It checks the structure of your commit messages to make sure that they follow the defined convention. Commitlint requires a configuration file, you can find more details from the reference configuration. A simple configuration file could look like this commitlint.config.js (note that this time the filename does not start with a dot):

module.exports = {
  extends: ["@commitlint/config-conventional"],
};

In this example I use the Conventional Commits specification. This needs to be defined as an additional dependency in the pre-commit configuration too!

Now, if you run make pre-commit again, it will install these two hooks inside your Git repository. The next time you commit changes it will first check the files with Prettier. If the changed files don’t follow the style configuration they will be automatically formatted and you need to commit the changes again.

Only after the file checks have passed it moves on to checking the commit message. If it doesn’t follow the style convention the whole commit will be rejected, and you’ll have to fix the message and try again.

The Conventional Commits specification uses commit messages that look like “fix: prevent unauthorized login”, “feat: add new color options”, “test: add unit tests for login view”, and so on. When this format is enforced the release numbers can be calculated automatically with semantic versioning.

Conclusion

Setting up pre-commit is quite quick and easy with Makefiles. You don’t need to worry about installing the command to globally to the developer machine or locally to a virtual environment. As long as make , wget and python3 are available on the system this thing will work. The setup instructions are easy to add to the repository README file, so new team members can quickly start working on new features.

If you liked this post or have any feedback you can respond to the embedded tweet below!

Discuss on Twitter

Subscribe to my newsletter

What’s new with PäksTech? Subscribe to receive occasional emails where I will sum up stuff that has happened at the blog and what may be coming next.

powered by TinyLetter | Privacy Policy