Implement `git` hook rules
Pitch
We could implement pre-commit within this project without much effort as we can leverage all of Bazel caching/downloading. As we already have bazel-git, we can make the hooks entirely hermetic.
Strawperson
load("@rules_git//git/hook/file/identity/or:defs.bzl", "git_hook_file_identity_or")
load("@rules_git//git/hook/run:defs.bzl", "git_hook_run")
load("@rules_git//git/hook:defs.bzl", "git_hook")
# Uses the `pre-commit/identity` Python library to determine file "identities"
# Provides `GitHookFileIdentity` with `kind == "or"`
# Can be combined into `git_hook_file_identity_and#deps`
git_hook_file_identity_or(
name = "ecmascript",
identities = ["typescript", "javascript"],
)
# A `bazel run` target that runs the tool
# Provides `GitHookFileIdentities`, `GitHook`
# In the future, we could provide `git_hook_aspect`, `git_hook_build`, etc.
git_hook_run(
name = "prettier",
src = ["@prettier"],
args = ["--write", "--list-different", "--ignore-unknown"],
identities = [":ecmascript"],
)
# A `bazel run` target that _either_:
# - Takes a list of file paths and runs hooks on them
# - Uses `bazel-git ls-tree` to figure out which files to run on
# Has an `install` sub-command to put a script into `.git/hooks`
git_hook(
name = "pre-commit",
deps = [":prettier", "@buf//:hook"], # Use an externally defined `git_hook`
# hook = "pre-commit", # Override `name` for the hook
# out = "{{workspace}}/hooks/{{hook}}", # Override the default `{{workspace}}/.git/hooks/{{hook}}` location
)
Usage
$ bazel run -- tools/hooks:pre-commit install
Hook installed at `.git/hooks/pre-commit`
The script installed is not bazel run -- tools/hooks:pre-commit, it is the underlying Python script that runs the identify, then runs the specific bazel run -- <target>. This means that we lazily download/compile the tools only when the relevant file identities have changed. Just like pre-commit.
Ecosystem
This would allow hooks to be shared. A Bazel module would just need to expose a target that exposes the GitHook provider. For example, the @prettier module could expose that, or a @git-hook-prettier, or a @git-hooks that contains multiple hooks. It would allow teams/companies to have an internal module that contains all the common hooks used for that community.