Python製のツールpre-commitでGitのpre-commit hookを楽々管理!!
はじめに
サーバーレス開発部@大阪の岩田です。 現在開発中のプロジェクトでソースコードの静的解析を強化すべくGitのpre-commit hookを便利・かつ簡単に管理する方法について調べていました。
色々調べたところ、Python製のpre-commitというツールが良さげだったので使い方を簡単にご紹介します。
pre-commitとは?
Gitのpre-commitフックスクリプトを管理・メンテナンスするためのフレームワーク(位置付け的にツールの方がしっくり来るので、この記事ではツールと呼びます)です。 pre-commit自体はPython製ですが、Python以外の言語で書かれたプロジェクトに導入したり、Python以外の言語で書かれたhookスクリプトを管理することもできます。
公式サイトでは以下のように説明されています。
A framework for managing and maintaining multi-language pre-commit hooks.
やってみる
実際に使ってみます。
pre-commitのインストール
まずpre-commitをインストールします。 現在開発中のプロジェクトではpipenvでライブラリを管理しているので、pipenvを使ってpre-commitを導入します。
pipenv install --dev pre-commit
設定ファイルの作成
インストールできたらpre-commitの設定ファイルを作成します。
設定ファイルは.pre-commit-config.yaml
というファイル名で作成します。
sample-config
というサブコマンドでサンプルの設定が出力できるので、まずはこれを使ってみます。
pre-commit sample-config > .pre-commit-config.yaml
作成された設定ファイルはこんな感じです。
# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files
詳細をみていきましょう。
repos
hookスクリプトが存在するリポジトリのリストを指定します。 GitHubで公開されているリポジトリを指定することで車輪の再発明が不要になります。
rev
repos
で指定されたリポジトリのリビジョンもしくはタグを指定します。
repos
で指定したリポジトリから、このセクションで指定されたバージョンのソースコードを取得してhookスクリプトとして利用します。
hooks
実際に実行するhookスクリプトをリストで指定します。
id
各hookスクリプトのIDを指定します。 リポジトリ内に存在するhookスクリプトのファイル名と一致(.pyは不要)するよう設定します。
Gitのhookスクリプトを導入
設定ファイルの準備ができたので、Gitのhookスクリプトを導入します。
pre-commit install
pre-commit installed at .git/hooks/pre-commit
という出力が表示されれば導入完了です。
適当にコミットしてみる
hookスクリプトが導入できたので、試しに何かコミットしてみます。
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks. [INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...........................................(no files to check)Skipped Check for added large files..............................................Passed
設定ファイルの記述に沿ってhookスクリプトの取得と実行が行われているのが分かります。
設定のカスタマイズ
サンプルが正しく動作していることが確認できたので、pre-commitで実行するスクリプトを追加してみます。 今回はLizardでソースコードのサイクロマチック数を計測し、閾値を超えていた場合はコミットさせないような設定を追加してみます。
まずLizardをインストールします
pipenv install --dev lizard
pre-commitの設定ファイル内に以下の記述を追加します。
- repo: local hooks: - id: lizard name: lizard entry: lizard -C 10 language: python_venv types: [file, python] exclude: ^test_.*$
repo
hookスクリプトが存在するリポジトリを指定します。
サンプルではGitHubのリポジトリが指定されていましたが、local
と指定することで、hookスクリプトを実行する自身のGitリポジトリを指定できます。
この機能を使うことでソースコードと自作のhookスクリプトをまとめて管理することが可能です。
name
hookスクリプト実行中に標準出力に表示される文字列を指定します。
repo
にGitHubのリポジトリを指定するサンプルの設定では特にname
が指定されていませんでしたが、
repo
にlocal
を指定している場合はid
,name
,language
,entry
に加えてfiles
もしくはtypes
の指定が必須となります。
entry
pre-commitのhookで実行するスクリプトを指定します。 今回はlizardの -C オプションでサイクロマチック数の閾値を10に設定しました
language
hookスクリプトを実行するための言語を指定します。
python
やpython_venv
以外にもruby
やnode
も指定可能です。
exclude
hookスクリプトの実行対象から除外するファイルを正規表現で指定します。
types
hookスクリプトのチェック対象を指定します。 [file, python]という指定することでコミット対象からpythonのファイルのみが抽出され、hookスクリプトの引数に渡ります。
例えばコミット対象が
- a.txt
- b.py
- c.py
の場合、実際に実行されるhookスクリプトは
lizard -C 10 b.py c.py
となります。
設定のテスト
設定がカスタマイズできたので、試しに滅茶苦茶なソースコードのコミットを試みます。
ソースコードを準備します。
def hoge(): if 1 == 1: print('hoge') elif 1 == 2: pass elif 1 == 3: pass elif 1 == 4: pass elif 1 == 5: pass elif 1 == 6: pass elif 1 == 7: pass elif 1 == 8: pass elif 1 == 9: pass elif 1 == 10: pass
このファイルのコミットを試みます。
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed lizard...................................................................Failed hookid: lizard ================================================ NLOC CCN token PARAM length location ------------------------------------------------ 21 11 67 0 21 hoge@1-21@hoge.py 1 file analyzed. ============================================================== NLOC Avg.NLOC AvgCCN Avg.token function_cnt file -------------------------------------------------------------- 21 21.0 11.0 67.0 1 hoge.py ========================================================================================= !!!! Warnings (cyclomatic_complexity > 10 or length > 1000 or parameter_count > 100) !!!! ================================================ NLOC CCN token PARAM length location ------------------------------------------------ 21 11 67 0 21 hoge@1-21@hoge.py ========================================================================================== Total nloc Avg.NLOC AvgCCN Avg.token Fun Cnt Warning cnt Fun Rt nloc Rt ------------------------------------------------------------------------------------------ 21 21.0 11.0 67.0 1 1 1.00 1.00
無事にコミットに失敗しました。 これで保守性の低いコードの混入を防止出来そうです。
まとめ
pre-commitの使い方について簡単にご紹介しました。 Gitのhookスクリプトは便利な仕組みではありますが、スクリプトを.gitディレクトリ以下に配置する必要があるため、バージョン管理やPJメンバーへの配布をどのように行うのかが課題になります。 pre-commitを利用することでこのような問題を解決し、より快適なGitライフが送れそうです。 hookスクリプトの管理に悩んでいる方は是非利用を検討してみて下さい。