「pre-commit-config.yaml」をリポジトリ毎に書かなくてもpre-commitを有効にする方法の検討
pre-commitでGitのhookを管理しておくと、
commit時にいろいろなチェックを行ってくれるので、
チェック漏れを事前に防げ便利なのですが、
プロジェクト毎に「.pre-commit-config.yaml」を用意するのも面倒なので、
設定ファイルを書かなくても、有効にできないかを検討してみました。
pre-commit
https://pre-commit.com/
実現方法の概要
「.pre-commit-config.yaml」の自動生成
「.pre-commit-config.yaml」が無いリポジトリでも、
リポジトリ内の設定ファイルを見ると、ある程度は実行すべきhookを判断することができます。
リポジトリ内の設定ファイルから「.pre-commit-config.yaml」を生成し、
生成した「.pre-commit-config.yaml」を使って、pre-commitを実行すれば、
リポジトリ個別に「.pre-commit-config.yaml」を用意する必要は無くなります。
Gitのテンプレートの活用
pre-commitのサイトでも紹介されていますが、
Gitのテンプレートを用意しておけば「pre-commit install」を実行せずとも、
Gitフックからpre-commitが呼ばれる状態にできます。
automatically enabling pre-commit on repositories | pre-commit
https://pre-commit.com/#automatically-enabling-pre-commit-on-repositories
設定の流れ
「pre-commit-config.yaml」の自動生成
「pre-commit-config.yaml」の自動生成するために、以下の2つのファイルを用意すします。
- ~/.gitconfig.d/build-pre-commit-config.py … pre-commit-configを生成するためのスクリプト
- ~/.gitconfig.d/pre-commit-config-template.yaml … pre-commit-configのテンプレートファイル
~/.gitconfig.d/build-pre-commit-config.py
#!/bin/env python
import os
import re
context = {
k: "stages: [manual]"
for k in [
"sqlfluff",
"prettier",
"flake8",
"black",
"ruff"
]
}
# detect needed hooks
repo_root = os.path.abspath(".")
if not os.path.exists(os.path.join(repo_root, ".pre-commit-config.yaml")):
if os.path.exists(os.path.join(repo_root, ".sqlfluff")):
context["sqlfluff"] = ""
if os.path.exists(os.path.join(repo_root, ".prettierrc")):
context["prettier"] = ""
if os.path.exists(os.path.join(repo_root, "pyproject.toml")):
with open(os.path.join(repo_root, "pyproject.toml")) as fp:
pyproject_body = fp.read()
for ln in pyproject_body.split("\n"):
if re.match("\[tool.flake8.*\]", ln):
context["flake8"] = ""
if re.match("\[tool.black.*\]", ln):
context["black"] = ""
if re.match("\[tool.ruff.*\]", ln):
context["ruff"] = ""
template_path = os.path.join(os.getenv("HOME"), ".gitconfig.d", "pre-commit-config-template.yaml")
output_config_path = "/tmp/pre-commit-config-tmp.yaml"
with open(template_path) as fp:
tpl_body = fp.read()
with open(output_config_path, "w") as fp:
fp.write(tpl_body.format(**context))
~/.gitconfig.d/pre-commit-config-template.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: mixed-line-ending
args:
- --fix=lf
- id: detect-aws-credentials
- id: detect-private-key
- repo: https://github.com/sqlfluff/sqlfluff
rev: 3.1.1
hooks:
- id: sqlfluff-lint
additional_dependencies:
- sqlfluff-templater-dbt==3.1.1
{sqlfluff}
- id: sqlfluff-fix
additional_dependencies:
- sqlfluff-templater-dbt==3.1.1
{sqlfluff}
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
{black}
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
hooks:
- id: ruff
# args:
# - --fix # commentin if you like autofix
{ruff}
- id: ruff-format
{ruff}
- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
hooks:
- id: flake8
{flake8}
- repo: local
hooks:
- id: prettier-yaml
name: Prettier YAML
entry: prettier --write
language: system
types: [yaml]
{prettier}
Gitのテンプレートの設定
テンプレートディレクトリを設定、作成します。
$ git config --global init.templateDir ~/.gitconfig.d/git-template
$ mkdir -p ~/.gitconfig.d/git-template/hooks
$ touch ~/.gitconfig.d/git-template/hooks/pre-commit
$ chmod +x ~/.gitconfig.d/git-template/hooks/pre-commit
pre-commitで実行するスクリプトを作成します。
~/.gitconfig.d/git-template/hooks/pre-commit
#!/usr/bin/env bash
# generate pre-commit-config.yaml
~/.gitconfig.d/build-pre-commit-config.py
# start templated
INSTALL_PYTHON=/usr/bin/python3
ARGS=(hook-impl --config=/tmp/pre-commit-config-tmp.yaml --hook-type=pre-commit --skip-on-missing-config)
# end templated
HERE="$(cd "$(dirname "$0")" && pwd)"
ARGS+=(--hook-dir "$HERE" -- "$@")
if [ -x "$INSTALL_PYTHON" ]; then
exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
elif command -v pre-commit > /dev/null; then
exec pre-commit "${ARGS[@]}"
else
echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
exit 1
fi
# default
# start templated
INSTALL_PYTHON=/usr/bin/python3
ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-commit --skip-on-missing-config)
# end templated
HERE="$(cd "$(dirname "$0")" && pwd)"
ARGS+=(--hook-dir "$HERE" -- "$@")
if [ -f .pre-commit-config.yaml ]; then
if [ -x "$INSTALL_PYTHON" ]; then
exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
elif command -v pre-commit > /dev/null; then
exec pre-commit "${ARGS[@]}"
else
echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
exit 1
fi
fi
試してみる
動作確認用のGitリポジトリを作成します。
$ mkdir sample-repo && cd $_
$ git init
適当なファイルを作成して、commitを実行します。
「~/.gitconfig.d/pre-commit-config-template.yaml」で設定している既定のhookが実行されます。
$ touch README.md
$ git add README.md
$ git commit -a -m "commit test default"
mixed line ending........................................................Passed
detect aws credentials...................................................Passed
detect private key.......................................................Passed
[master (root-commit) bccd1d0] commit test default
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
リポジトリのpyproject.tomlに、flake8の設定を追記して、commitを実行します。
既定のhookに加えて、flake8のhookも実行されます。
$ echo "[tool.flake8]" > pyproject.toml
$ git commit --allow-empty -m "commit test +flake8"
mixed line ending....................................(no files to check)Skipped
detect aws credentials...............................(no files to check)Skipped
detect private key...................................(no files to check)Skipped
flake8...............................................(no files to check)Skipped
[master 4a18813] commit test +flake8
以上。