Pre-commit: framework for managing/maintaining multi-language pre-commit hooks
pre-commit.com
pre-commit.com
It must run during normal CI/CD because pre-commit hooks can be skipped.
So now I have two different black calls: in the pre-commit hook and in the CI/CD. And they must be of the same version.
Ad infinitum for all other tests.
This is the reason I don't use pre-commit framework. It leads to "double accounting". I have to sync the pre-commit tests and the CI/CD tests. Or am I missing something? Can the framework run off the venv dir?
Granted, I haven't really looked into it too deeply, so I am a bit embarrassed with my low-effort comment above :-).
The objective is that ultimately no non-compliant code can be merged to master; the pre-commit hook (along with all other tests or whatever your CI enforces) is a mandatory step before a PR can be merged, and nobody is allowed to push to master directly.
But if you're in a private repository, you can just have a job that runs pre-commit. The versions of the pre-commit tests are hard-coded into the config.
There's also a bot that keeps your pre-commit config up to date with the newest released versions of the checks.
Therefore, regardless of whether you use pre commit, your cicd should be verifying that the software, including source code is satisfying the specification.
As a collorary, pre commit doesn't need to validate. Eg tell you where exactly the errors are.
These are subtle but important distinctions as it nullifies your double accounting concern. The only doubling that exist is that the pre commit and cicd are both made aware of the specification. Maybe that is your concern.
But some developers in my team do like that convenience of just being able to commit, so that's an option that can be configured in our internal tooling.
The pre-commit is just a convenience for the developer. It gives faster feedback - immediately when trying to commit, instead of a couple of minutes later. And if a developer on my team wants to disable it, it doesn't affect others.
And yes, it's important that you use the same version in dev and on CI/CD. The same applies for practically any other tool you use in your project. I see the CI/CD config as the "master" version - it's a known-working setup, that any developer should be able to use.
I'd say that running linters in precommit hooks is the ideal use case, as linters do not belong in CICD pipelines (i.e., it would make absolutely no sense to block a release just because a whitespace didn't matched an arbitrary rule) and linting stuff before committing also eliminates iterations in code reviews to handle inane stuff like where to break a line or how many spaces come before and after a bracket.
While one could argue these are the job of the compiler, they are not today, and so linting during CI is not only appropriate, but practically required.
I disagree. Stylistic choices should not block a pipeline. It's impossible to argue in favour of blocking a release just because there's a whitespace in a place where it makes some random developer frown. The robustness principle also applies here.
And no, build/critical errors are not the same as stylistic/linting errors.
And if there is an emergency hotfix that needs to go out as soon as possible, we do have processes in place to bypass the CI/CD checks.
What's the problem, exactly? I worked on projects which enforced precommit hooks running unit tests, and all the precommit and CICD test stages did was run the tests that were already in the repo.
Also, I don't understand what would be the problem of calling out-of-synch tests in precommit hooks and the CICD, given that precommit tests are used as a failsafe to not block the pipeline with broken commits.
We install enough utilities with `brew` to get pyenv working, use that to build all python versions. Then iirc `brew install pipx`, maybe it's `pip3 install --user pipx`. Anyway, that's the only python library binary installed outside a venv.
Pipx installs isort, black, dvc, and pre-commit.
Every repo has a Makefile. This drives all the common operations. Pyproject.toml (/eslint.json?) set the config for isort and black (or eslint). `make format` runs isort and black on python, eslint on js. `make lint` just verifies.
Pre-commit only runs the lint, it doesn't format. It also runs some scripts to ensure you aren't accidentally committing large files. Pre-commit also runs several DVC actions (the default dvc hooks) on commit, push, and checkout. These run in a venv managed by pre-commit. We just pin the version.
Github actions has a dedicated lint.yaml which runs a python linter action. We use the black version here to define which black pipx installs. We use `act` if we wanna see how an action runs without sending a commit just to trigger jobs.
As an aside, I'm still fiddling with the dvc `pre-commit` post-checkout hooks. They don't always pull the files when they ought to.
Most of the actual unit/integration tests run in containers, but they can run in a venv with the same logic, thanks to makefile. We use a dvc action to sync files in CI.
So yeah there's technically 2 copies of black and dvc, but we just use pinning. In practice, we've only had one issue with discrepancies in behavior locally vs CI, which was local black not catching a rule to avoid ''' for docstrings; using """ fixed it. On the whole, pre-commit saves against a lot of annoying goofs, but CI system is law, so we largely harmonize against that.
IMHO, this is the least egregious "double accounting" we have in local vs staging ci vs production ci (I lost that battle, manager would rather keep staing.yaml and production.yaml, rather than parameterize. Shrug.gif).
Other knowledge nuggets:
- pre-commit manages its own dependencies. This leads to surprising behavior if you aren't expecting it. Eg you need a special line to specify dvc[s3].
- black has yet to release a non-beta semver, which messes with solvers. This is super annoying. They might as well use 0ver if they don't want to commit to stability. Don't expect any kind of stability of formatting between versions. Hope they settle down soon.
- git-lfs is a nightmare. Two projects at $lastco used it. It's more trouble than it's worth. Just use DVC for yucky files. I have no affiliation with dvc, other than a few bug reports.
- makefiles are great and IMHO underrated. But they have their limits. More complex logic should be broken out into scripts.
- python dependency management is still a kafkaesque nightmare. I say this with over a decade of python experience and it's my favorite language despite this.
- Suggestions welcome!
Technologies referenced:
https://github.com/iterative/setup-dvc
> Can the framework run off the venv dir?
It can, but it means not using the hooks from the tool's repo and writing your own. This blog post [0] shows the pipenv version of some common hooks; mine look the same but s/pipenv/poetry.
If there's a way to use the upstream hooks with your local venv, so I can trust the tool maintainers for proper configuration, I'd love to know about it.
For example: https://github.com/trailofbits/pip-audit/blob/main/Makefile
Making sure every commit have a ticket reference (and auto add it from the branch name if possible) is a fantastic convenience that also avoids commits referencing the wrong ticket.
You can also have a sanity check on what branches you don’t want to cowboy commit on unless you deliberately want to, like the main branch.
Black is a tool that is better to run on save. The key to a good pre-commit hook is that it has to be _fast_ otherwise parts of the team will stop using it.
In pre-commit you want to run Black in full formatter mode on the staged files. (VS Code's "Format After Save" does most of the work already, but every now and then it slips on something.) In CI/CD you want the check that formatting occurred and block it as a failed test. Those are different commands.
For what it is worth, I was evaluating lint-staged [1] and husky [2] for my pre-commit hooks.
My least favourite bug is that it doesn't always play nicely with NixOS [1], and the maintainer locked me out of the issue for pointing it out.
Oof. Does this solve the problem https://github.com/cachix/pre-commit-hooks.nix (using Nix to manage dependencies)?
I was looking at pre-commit the other day, and wanted to incorporate it into the Nix setup of my projects.
He's doing this for free, yeah?
> NixOS isn't broken. I'm running into the same problem (link above).
The response: > if you don't have a functioning ld or libstdc++ that's on you, sorry
So they basically consider the way NixOS works to be "broken", and then go out of their way to block me from responding further to the discussion. I've been maintaining and contributing to FOSS for many years, and this is one of the most rude responses I've seen so far.Ofc, as for every tool there is good and bad practice. For example, don't put long running tasks (eg. tests) into the pipeline. Things like code formatters, linter or import sorter is just simplify coders life.
rev: 148ec47877499e3d671f6366f9eed812db181b40 # v1.2.0This question also applies to GitHub Actions, which is something I'd like to implement too.
Also, I’ve found it very helpful to always have command run from local venv, rather than remote downloads. Downloading can lead to import errors.
Overall, a very, very nice tool, but one you have to put some effort into learning.