Gitにはフック(hook)という仕組みがあり、コミットやプッシュの前後にシェルスクリプトを自動実行できます。設定すれば、うっかり構文エラーのあるスクリプトをコミットするミスを手作業なしに防止できます。
この記事では、Git 2.9以降を想定環境として、pre-commitフックとpre-pushフックをシェルスクリプトで実装する方法を解説します。変更ファイルへの自動チェック組み込み、
git config core.hooksPathによるチームへの配布設計まで、順番に説明します。この記事のポイント
・Gitフックはexit 0で処理続行・非0でブロックというルールで動作するシェルスクリプトである
・pre-commitでgit diff --staged --name-onlyを使い変更ファイルにshellcheckを実行できる
・core.hooksPathでバージョン管理対象のディレクトリへ向けるとチームへ配布できる
・shellcheckが見つからない環境ではexit 0でスキップする設計にするのが実務上の鉄則
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
Gitフックとシェルスクリプトの関係
Gitフックは、コミット・プッシュ・マージといったGitのイベントの前後に自動実行されるシェルスクリプトです。実体は
.git/hooks/ディレクトリ配下のファイルで、ファイル名がフック名と一致し、実行権限(+x)が付いている場合に自動実行されます。# .git/hooks/ の内容を確認する ls .git/hooks/ # 出力例(環境により異なります) applypatch-msg.sample commit-msg.sample fsmonitor-watchman.sample pre-applypatch.sample pre-commit.sample pre-merge-commit.sample pre-push.sample pre-rebase.sample prepare-commit-msg.sample update.sample
.sampleという拡張子が付いており、実行されません。フック名から.sampleを外して実行権限を付与することで有効化できます。Gitフックがシェルスクリプトとして動作する基本ルールは次のとおりです。
・exit 0: Gitが処理を続行する(コミットやプッシュが実行される)
・exit 1以上: Gitが処理をブロックする(コミットやプッシュが中断される)
この仕組みを利用すると、チェック処理で問題を発見したときだけexit 1を返してコミットを止められます。
pre-commitフックを実装する基本手順
1. スクリプトを作成して実行権限を付与する
.git/hooks/pre-commitというファイルを作成します。# pre-commitフックのひな型を作成する cat > .git/hooks/pre-commit << 'HOOKEOF' #!/bin/bash set -euo pipefail echo "[pre-commit] フックが実行されました" exit 0 HOOKEOF # 実行権限を付与する chmod +x .git/hooks/pre-commit # 設定を確認する(出力例) ls -la .git/hooks/pre-commit -rwxr-xr-x 1 user user 72 Nov 7 17:30 .git/hooks/pre-commit
#!/bin/bashのshebangは/bin/shではなく/bin/bashを指定します。bash固有の構文([[ ]]によるパターンマッチングなど)を安全に使えるようになります。動作確認は
git commitを実行して「[pre-commit] フックが実行されました」という出力が表示されることで確認できます。2. 変更ファイル一覧を取得する
pre-commitフックでは、ステージングエリアの変更ファイルに対して処理します。git diff --staged --name-onlyでファイル一覧を取得します。# ステージングされたファイルを取得する(追加・コピー・変更のみ) STAGED_FILES=$(git diff --staged --name-only --diff-filter=ACM 2>/dev/null) # 1ファイルずつ処理する while IFS= read -r file; do echo "対象: $file" done <<< "$STAGED_FILES"
--diff-filter=ACMはAdded(追加)・Copied(コピー)・Modified(変更)のファイルだけを対象にするオプションです。削除済みファイルへの処理は不要なため、このフィルタを付けておくのが実務での設計です。3. shellcheckで変更ファイルを自動検査する
ステージングされたファイルのうち、シェルスクリプトに対してshellcheckを実行する設計です。#!/bin/bash set -euo pipefail # shellcheckが使えるか確認する(未インストール環境ではスキップする) if ! command -v shellcheck &>/dev/null; then echo "[pre-commit] shellcheckが見つかりません。チェックをスキップします。" exit 0 fi # ステージングされたファイルを取得する STAGED_FILES=$(git diff --staged --name-only --diff-filter=ACM 2>/dev/null) FAILED=0 while IFS= read -r file; do [[ -z "$file" ]] && continue [[ ! -f "$file" ]] && continue # .shファイル、またはshebangにshが含まれるファイルを対象にする if [[ "$file" == *.sh ]] || head -1 "$file" 2>/dev/null | grep -qE '^#!.*(ba)?sh'; then echo "[pre-commit] shellcheck: $file" if ! shellcheck "$file"; then FAILED=1 fi fi done <<< "$STAGED_FILES" if [[ $FAILED -ne 0 ]]; then echo "[pre-commit] shellcheckでエラーが見つかりました。修正してからコミットしてください。" exit 1 fi exit 0
command -v shellcheck &>/dev/nullでshellcheckの存在を確認してから処理に進みます。ツール未インストールの環境でexit 1を返すと、開発者がコミットできなくなるため、スキップする設計にしておくことが重要です。pre-pushフックでテストを強制実行する設計
1. pre-pushフックが受け取る情報
pre-pushフックは、プッシュ先のリモート名とURLを引数で受け取り、プッシュ対象のブランチ情報を標準入力で受け取ります。#!/bin/bash # 引数: $1=リモート名(例: origin)、$2=リモートURL REMOTE="$1" URL="$2" # 標準入力からプッシュ対象を受け取る # フォーマット: <ローカルref> <ローカルSHA> <リモートref> <リモートSHA> while read -r local_ref local_sha remote_ref remote_sha; do echo "プッシュ対象: $local_ref -> $remote_ref" done
2. mainブランチへのプッシュ時だけテストを実行する設計
フィーチャーブランチの開発速度を落とさないよう、mainブランチへのプッシュ時だけテストを実行する設計です。#!/bin/bash set -euo pipefail REMOTE="$1" URL="$2" # プッシュ先のブランチを確認する while read -r local_ref local_sha remote_ref remote_sha; do if [[ "$remote_ref" == refs/heads/main ]]; then echo "[pre-push] mainブランチへのプッシュ前にテストを実行します..." if [[ -f "./run-tests.sh" ]]; then if ! bash ./run-tests.sh; then echo "[pre-push] テストが失敗しました。プッシュを中断します。" exit 1 fi echo "[pre-push] テストが通過しました。" fi fi done exit 0
run-tests.shが存在しない場合は何もしないでexit 0します。初期セットアップ前の環境でプッシュを妨げないための設計です。core.hooksPathでチームへ配布する設計
.git/hooks/はバージョン管理の対象外です。git cloneしたときにフックは各開発者の環境に配布されません。チーム全体でフックを共有するには、core.hooksPathを使って管理対象ディレクトリへ向ける設計が有効です。1. .githooksディレクトリにフックを移す
# リポジトリのルートに.githooksディレクトリを作成する mkdir -p .githooks # .git/hooks/のフックを.githooksへ移動する mv .git/hooks/pre-commit .githooks/pre-commit mv .git/hooks/pre-push .githooks/pre-push # 実行権限が付いているか確認する(出力例) ls -la .githooks/ -rwxr-xr-x 1 user user 1234 Nov 7 17:30 pre-commit -rwxr-xr-x 1 user user 567 Nov 7 17:30 pre-push # gitの管理対象に追加する git add .githooks/ git commit -m "chore: Gitフックを.githooksへ移動"
.githooks/はリポジトリのルートに作成します。名前は慣例で.githooksが使われますが、任意の名前で構いません。2. core.hooksPathを設定するセットアップスクリプトを用意する
リポジトリにセットアップスクリプトを置いて、各開発者がcore.hooksPathを設定できるようにします。#!/bin/bash # setup.sh(リポジトリルートに置く) set -euo pipefail HOOKS_DIR=".githooks" if [[ ! -d "$HOOKS_DIR" ]]; then echo "エラー: $HOOKS_DIR が見つかりません" exit 1 fi # core.hooksPathをローカルgit設定に書き込む git config --local core.hooksPath "$HOOKS_DIR" echo "Gitフックのセットアップが完了しました。" echo "フックディレクトリ: $(git config core.hooksPath)"
bash setup.shを1回実行するだけで設定が完了します。3. 設定が有効になっていることを確認する
# core.hooksPathの設定を確認する(出力例) git config --local core.hooksPath .githooks # フックの実行権限を確認する(出力例) ls -la .githooks/ -rwxr-xr-x 1 user user 1234 pre-commit -rwxr-xr-x 1 user user 567 pre-push
.githooks/内のファイルに実行権限(先頭3文字がrwx)が付いていることを確認してから、チームへ案内します。シェルスクリプトの書き方をより体系的に学びたい方は、シェルスクリプト実践入門もあわせてご覧ください。
トラブルシュート・よくある問題
「フックが実行されない」場合の確認手順
# 1. 実行権限を確認する ls -la .git/hooks/pre-commit # -rwxr-xr-x なら権限OK。-rw-r--r-- なら chmod +x で付与する # 2. core.hooksPathの設定を確認する git config --local core.hooksPath # .githooksが返れば設定済み。空なら setup.sh を再実行する # 3. shebangを確認する head -1 .githooks/pre-commit # #!/bin/bash のような正しいshebangが必要
--no-verifyオプションについて
--no-verifyオプションを使うと、フックをスキップしてコミットできます。緊急対応時の手段として存在しますが、チームで乱用しないよう運用ルールを決めておくことが重要です。
# フックをスキップしてコミットする(緊急時のみ使用する) git commit --no-verify -m "hotfix: 緊急対応"
Windowsで作成したスクリプトの改行コードに注意する
Windowsで作成したスクリプトをそのまま使うと、改行コードが\r\n(CRLF)になってshebangが正しく解釈されないことがあります。dos2unixコマンドで変換できます。# 改行コードをLFに変換する dos2unix .githooks/pre-commit # 改行コードを確認する(出力例) file .githooks/pre-commit .githooks/pre-commit: Bourne-Again shell script, ASCII text executable
本記事のまとめ
| やりたいこと | 設計 |
|---|---|
| コミット前に自動チェックを実行したい | chmod +x .git/hooks/pre-commitでフックを有効化する |
| 変更ファイルにshellcheckを適用したい | git diff --staged --name-only --diff-filter=ACMで一覧取得してループする |
| プッシュ前にテストを強制実行したい | .git/hooks/pre-pushで標準入力を受け取りテストを実行する |
| チーム全体でフックを共有したい | git config --local core.hooksPath .githooksで管理対象ディレクトリへ向ける |
| フックを一時的にスキップしたい | git commit --no-verify(緊急時のみ使用する) |
特に
core.hooksPathによるチーム配布の設計は、フックを「個人の設定」から「プロジェクトの標準」に変える重要なポイントです。セットアップスクリプトと組み合わせて、チーム全員が同じチェックを実行できる環境を整えてください。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:シェルスクリプトのPATH設計|cron・systemd・SSH実行で環境差異を吸収する方法
- この記事の属するカテゴリ:シェルスクリプトへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます