第2回:ローカル環境での品質チェック:pre-commitフック
前回はCI/CDの基本と全体像について解説しました。もし前回の記事をまだご覧になっていない方は、以下のリンクからぜひご確認ください。
GitLabで実現する実践CI/CDパイプライン構築ガイド【第1回:基本と全体像】
前回、「次回は.gitlab-ci.ymlの書き方を解説する」とお伝えしましたが、より効率的なCI/CDパイプラインを構築するために、今回は先にローカルでコードの品質を担保するpre-commitについて詳しく掘り下げていきます。pre-commitを学ぶことで、CIパイプラインの無駄な実行を減らし、開発をよりスムーズに進められます。
前提環境
本ブログでは、筆者が利用しているWindowsOSとVS Codeを想定して解説を進めます。ターミナルにはBashを使用し、Pythonのバージョンは現時点で最新の3.14.0rc2を前提とします。
コマンドはOSやシェルの種類、Pythonのバージョンによって異なる場合がありますので、ご自身の環境に合わせて適宜読み替えてください。
pre-commitとは?
pre-commitは、Gitのコミットフックという仕組みを利用して、コミット前に自動で特定の処理を実行するフレームワークです。
Gitフックは、Gitの特定イベント(コミット前(pre-commit)、プッシュ前(pre-push)など)をトリガーとして、任意のスクリプトを自動で実行する機能です。pre-commitは、このpre-commitフックを利用し、コードの整形(Formatter)や静的解析(Linter)を自動で実行します。
pre-commitを利用する利点としては以下があります。
-
リポジトリの品質維持
pre-commitの実行により、コードのスタイルや品質がプロジェクト全体で統一され、軽微なミス(例:空白やインデントなどの修正)がリモートリポジトリにマージされるのを防ぎます。結果として、コードレビューが本質的なロジックの確認となりレビュー効率が向上します。 -
CI/CDパイプラインの無駄な実行を削減
コードがリモートリポジトリにプッシュ/マージされるたびにCI/CDパイプラインが実行されるよう構築した場合、もしコードのフォーマットエラーや基本的な文法ミスがあった場合、パイプラインは失敗し、時間や計算リソースが無駄になってしまいます。pre-commitを導入すれば、これらの基本的なエラーをローカルのコミット段階で検出し、修正できます。これにより、CI/CDパイプラインをより高度なテストやデプロイに集中させ、結果的にリソースの節約とパイプラインの高速化につながります。 -
開発者への即時フィードバック
開発者は、CI/CDの実行を待つまでもなく、ローカルリポジトリへコミットした瞬間にフィードバックが得られます。これにより、小さなミスをその場で修正でき、後からまとめて修正する手間がなくなります。このリアルタイムなフィードバックは、開発者の生産性を高め、よりスムーズな開発体験をもたらします。
pre-commitの導入方法:Pythonプロジェクトの例
それでは、PythonプロジェクトにRuffを使ってpre-commitを導入する具体的な方法を解説します。Ruffは非常に高速で、複数のツール(Black, Flake8, isortなど)の機能を統合しているため、現代のPython開発では非常に人気があります。
-
仮想環境の作成と有効化
まず、プロジェクトごとに隔離された環境を構築するために、仮想環境を作成します。これにより、プロジェクト固有のライブラリやツールをグローバル環境に影響を与えることなく管理できます。Bash# 仮想環境を作成 python -m venv venv # 仮想環境を有効にする # WindowsでVS CodeのBashターミナル利用の場合 . venv/Scripts/activate
-
pre-commitとRuffのインストール
次に、pre-commitとRuffを仮想環境にインストールします。Bashpip install pre-commit ruff
-
設定ファイルの作成
次に、pre-commitの動作を定義する設定ファイルを作成します。プロジェクトのルートディレクトリに、.pre-commit-config.yamlという名前でファイルを作成してください。このファイルには、Ruffのフックを定義します。.pre-commit-config.yamlrepos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.13.0 # 最新のリリースバージョンに合わせる hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format
- ruffフックは、リンティングと自動修正(--fix)を行います。--exit-non-zero-on-fixを追加することで、自動修正が行われた場合にエラーとして扱い、再コミットを促すことができます。
-
ruff-formatフックは、コードのフォーマットを行います。
-
Gitフックのインストール
pre-commitはGitリポジトリ内で動作するため、プロジェクトをまだGitリポジトリとして初期化していない場合は、以下のコマンドを先に実行します。Bash# プロジェクトのルートディレクトリでGitリポジトリを初期化 git init
Bashpre-commit install
pre-commitの実行と動作
pre-commitのインストールが完了すれば、あとは通常通りgit commitするだけでフックが自動で実行されます。
試しに、意図的にフォーマットエラーを含んだ以下のシンプルなPythonコードをコミットしてみます。このコードは、defと関数名の間に余分なスペースが含まれており、Ruffのフォーマットルールに従っていません。
def get_message():
"""Returns a simple greeting message."""
return "Hello, World!"
def main():
"""Main function to print the greeting."""
print(get_message())
if __name__ == "__main__":
main()
このコードは、一見問題がないように見えますが、Ruffのフォーマットルールに従っていません。ここでgit commitを実行してみます。
$ git add .
$ git commit -m "Initial commit"
ruff (legacy alias)......................................................Passed
ruff format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook
1 file reformatted, 1 file left unchanged
実行結果を見ると、LinterのruffフックはPassedと成功していますが、Formatterのruff-formatフックがFailedと失敗として表示されました。これは、Ruffがコードのフォーマットを自動で修正したことを意味します。この段階ではまだコミットは完了していません。
pre-commitが自動修正する前と後を比べてみましょう。
Ruffは、余分なスペースを削除し、さらに関数の間に空行を挿入してPEP8(Pythonのスタイルガイド)に準拠したフォーマットへ自動で修正しました。
pre-commitによる自動修正を受け入れるには、修正されたファイルをステージングエリアに再度追加してコミットをやり直す必要があります。
$ git add .
$ git commit -m "Initial commit"
ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
今度はruff formatがPassedとなり、コミットが成功しました。
このように、pre-commitを導入すれば、コードの整形や静的解析といった作業が開発者のローカル環境で自動化できます。開発者はこれらの手作業から解放され、常に高品質で一貫性のあるコードを維持できます。
まとめと次回予告
今回は、CI/CDパイプラインをより効率的に運用するために不可欠なpre-commitフックの導入方法を解説しました。特に、高速で多機能なRuffをフックとして使用することで、基本的なコードの品質をローカルで担保し、CIパイプラインをより重要なタスクに集中させることができます。
次回は、CI/CDの心臓部である .gitlab-ci.yml に話を戻し、実際にGitLab CI上でコードをビルドし、テストを実行する方法を掘り下げていきます。