Git フックを実装する

完了

開発プロセスでコードの品質を優先することは、ローカル コード開発から始める必要があります。 コード品質の潜在的な問題を検出して訂正するための pull request を始める前からであっても、これを実践する機会を見つけ出すことが重要です。

Git フックによって絶好の機会が提供されます。 それは、コミット、マージ、プッシュなど、Git ライフサイクル内の重要なイベントへの応答でカスタム スクリプトを実行するためのメカニズムとして機能します。 リポジトリの .git\hooks ディレクトリ内にあるスクリプトが提供する事実上無制限の柔軟性を利用して、ソフトウェア開発タスクを自動化し、開発標準を適用できます。

Git フックを実装する方法

まず、クライアント側の Git フックについて説明します。 repo.git\hooks ディレクトリに移動します。そこには、拡張子 sample が付いたファイルが多数あります。 この拡張子は、その目的を示しているだけでなく、それらの実行を効果的に防いでもいます。 ファイル名は、sample 拡張子を削除した後でその実行をトリガーする Git アクションを指定しています。

自動化のための Git フック ファイルのスクリーンショット。

pre-commit sample ファイルの名前を pre-commit に変更します。 ファイルの名前が示すように、それに含まれるスクリプトは、ユーザーが git コミット アクションを呼び出すたびに実行されます。 コミットは、pre-commit スクリプトが戻り値 0 で終了した場合にのみ、続いて行われます。

ただし、既定では、どの Windows オペレーティング システムでもこれは意図したとおりに機能しないことに注意してください。 この動作について見落とされることがよくある理由は、スクリプトの最初の行です。

#!/bin/sh

Linux オペレーティング システムでは、#! プレフィックスはファイルの残りの部分に解釈されるスクリプトが含まれることをプログラム ローダーに示し、/bin/sh は使う必要があるインタープリターへの完全なパスです。

Git for Windows では Bash コマンドとシェル スクリプトがサポートされていますが、ファイル システム パスを指定するときは同じ規則に従いません。 sh.exe ファイルに完全なパスを提供するには、代わりにドライブ文字で始める必要があります。

ただし、Git for Windows は既定で C:\Program Files ディレクトリにインストールされるという事実によって生じる、追加の注意事項があります。 このディレクトリの名前にはスペースが含まれるため、結果として得られる sh.exe ファイルへのパスが 2 つの個別のパスとして解釈されて、エラーが発生します。 これを避けるには、エスケープ文字として機能する円記号 (\) を 1 つ、スペースの前に追加する必要があります。 実質的に、64 ビット バージョンの Git for Windows を使うときは、スクリプトの最初の行の形式は次のようになります。

#!C:/Program\ Files/Git/usr/bin/sh.exe

方法

Git の pre-commit スクリプトの新しく検出された機能を使うにはどうすればよいでしょう。 シークレットが誤って GitHub に漏えいするのを防ぐ場合はどうでしょう。

Git フックを使って、ローカル リポジトリにコミットされるコードで、特定のキーワードをスキャンしてみましょう。 pre-commit シェル ファイルの内容を、次のコードに置き換えます。

#!C:/Program\ Files/Git/usr/bin/sh.exe
matches=$(git diff-index --patch HEAD | grep '^+' | grep -Pi 'password|secret')
if [ ! -z "$matches" ]
then
  cat <<\EOT
Error: Words from the blocked list were present in the diff:
EOT
echo $matches
exit 1
fi

この例は、本格的なソリューションではなく概念を説明することが目的なので、キーワード リストはわざと簡単にしてあります。 正規表現を使うと、スコープと柔軟性を大幅に高めることができます。 また、外部ファイルを参照することもでき、継続的なメンテナンスがとても簡単になります。

動作のしくみ

呼び出された pre-commit フック スクリプトは、git diff と grep コマンドを使って、コードにコミットされる増分変更内のキーワードまたはパターンを識別します。 一致が検出された場合、スクリプトはエラー メッセージを生成して、コミットが実行されないようにします。

その他の用途:

pre-commit フック スクリプトの一般的なユース ケースとしては、他に、コードの書式設定、リンティング、コミットがプロジェクト標準に準拠していることを確認するためのカスタム テストの実行などがあります。 prepare-commit-msg は、コミット メッセージ エディターが起動される前に実行します。 これにより、指定されたプレフィックスの使用 (例: フィーチャーの場合は feat:、バグ修正の場合は fix:) など、名前付け規則を適用するためのコミット メッセージを動的に生成できます。

たとえば、次の prepare-commit-msg スクリプトは、新しいコミットの作成時に、現在のブランチ名をコミット メッセージの前に自動的に付加します。 ファイルの先頭で、ブランチ名、コロン、スペースの順に追加して、コミット メッセージ ファイル ($1) を変更します。

#!C:/Program\ Files/Git/usr/bin/sh.exe
# Get the current branch name
branch_name=$(git branch --show-current)
# Check if the commit message file exists
if [[ -f "$1" ]]; then
  # Prepend the branch name to the commit message
  sed -i "1s/^/$branch_name: /" "$1"
fi

post-commit スクリプトは、コミットが完了した後に実行されます。 それは、通知のトリガーやドキュメントの生成に使用できます。

たとえば、次のスクリプトは、各コミットの後で指定された受信者にメール通知を送信します。 受信者のメール アドレス、SMTP サーバー、メールの件名と本文を変更して、このスクリプトをカスタマイズできます。 さらに、環境や要件に応じて、Send-MailMessage PowerShell コマンドレットを使ってメールを送信したり、別の方法を使って通知を送信したりするように、システムを構成することが必要になる場合があります。

#!C:/Program\ Files/Git/usr/bin/sh.exe
# Set the recipient email address
$recipient="your@email.com"
# Set the subject of the email
$subject="Git Commit Notification"
# Set the body of the email
$body="A new commit has been made to the repository."
# Send the email notification
Send-MailMessage -To $recipient -Subject $subject -Body $body -SmtpServer "your.smtp.server"

リポジトリの .git\hooks フォルダーはソース管理にコミットされないことに注意してください。 開発したスクリプトを開発チームの他のメンバーと共有する方法があるかどうか気になるかもしれません。 幸い、Git バージョン 2.9 以降では、ソース管理にコミットできるフォルダーに Git フックをマップできます。 これを行うには、Git リポジトリのグローバル設定構成を更新します。

Git config --global core.hooksPath '~/.githooks'

クライアント側で設定した Git フックを上書きする必要がある場合は、次のように no-verify スイッチを使用します。

Git commit --no-verify

サーバー側フック

クライアント側の Git フックでは、開発ワークフローを強化するための堅牢な機能が提供されますが、Azure Repos には、pull request の作成のサポートなど、開発プロセスをさらに強化するためのサーバー側フックも用意されています。 詳しくは、Azure Repos の「サービス フック イベント」リファレンスをご覧ください。