はじめに

「コードレビューでフォーマットの指摘ばかり受ける」「リント警告のあるコードがそのままコミットされてしまう」といった課題を抱えているチームは多いのではないでしょうか。

Git Hooksは、コミットやプッシュなどのGit操作をトリガーとしてスクリプトを自動実行する仕組みです。この機能を活用すれば、コミット前に自動でコードフォーマットを適用したり、リンターを実行してエラーがあればコミットを中止したりできます。

本記事では、Git Hooksの基本的な仕組みから、huskyとlint-stagedを使った実践的な自動チェック環境の構築方法まで解説します。この記事を読み終えると、以下のことができるようになります。

  • Git Hooksの種類と役割を理解できる
  • pre-commit、commit-msg、pre-pushフックを設定できる
  • huskyを使ってチーム全体でGit Hooksを共有できる
  • lint-stagedでステージされたファイルのみを対象に自動チェックを実行できる
  • ESLintやPrettierと連携した自動フォーマット環境を構築できる

実行環境と前提条件

本記事の内容は、以下の環境で動作確認を行っています。

項目 要件
Git 2.40以上
Node.js 18以上(LTS推奨)
OS Windows 10/11、macOS 12以上、Ubuntu 22.04以上
ターミナル コマンドプロンプト、PowerShell、Terminal.app、bash等
エディタ VS Code推奨

前提条件として、以下の知識があることを想定しています。

  • コマンドライン操作の基礎知識(cdls/dirmkdir等)
  • テキストエディタの基本操作

Git Hooksとは

Git Hooksは、Gitの特定のアクション(コミット、プッシュなど)が実行される際に自動的にスクリプトを実行する仕組みです。Gitリポジトリの.git/hooks/ディレクトリに配置されたスクリプトが、対応するGitコマンドの実行時にトリガーされます。

Git Hooksの特徴

Git Hooksには以下の特徴があります。

  • 自動実行: 手動操作なしで、Gitコマンドの前後に処理を挿入できます
  • ローカル実行: フックはクライアント側で実行されるため、サーバーへの負荷がありません
  • カスタマイズ可能: シェルスクリプトやNode.jsなど、任意の言語でフックを記述できます
  • 中断可能: フックが非ゼロの終了コードを返すと、対応するGit操作を中止できます

クライアントサイドフックの種類

Gitには13種類のクライアントサイドフックが用意されています。本記事では、特に利用頻度の高い3つのフックに焦点を当てます。

フック名 実行タイミング 主な用途
pre-commit コミット作成前 コードフォーマット、リント、テスト実行
commit-msg コミットメッセージ入力後 コミットメッセージの検証
pre-push プッシュ前 テスト実行、ビルド検証

その他のフックには、prepare-commit-msg(コミットメッセージのテンプレート生成)、post-commit(コミット後の通知)、pre-rebase(リベース前のチェック)などがあります。

Git Hooksの基本的な設定方法

まずは、huskyを使わずにGit Hooksを直接設定する方法を確認しましょう。

フックファイルの配置場所

Git Hooksは、リポジトリの.git/hooks/ディレクトリに配置します。新しいリポジトリを初期化すると、サンプルファイルが自動的に作成されます。

1
2
3
4
5
6
7
8
# リポジトリのhooksディレクトリを確認
ls .git/hooks/

# 出力例
applypatch-msg.sample  pre-commit.sample      pre-rebase.sample
commit-msg.sample      pre-merge-commit.sample prepare-commit-msg.sample
fsmonitor-watchman.sample  pre-push.sample    update.sample
post-update.sample     pre-receive.sample

pre-commitフックの作成

pre-commitフックは、git commitコマンドの実行直前にトリガーされます。このフックが非ゼロの終了コードを返すと、コミットは中止されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# pre-commitフックファイルを作成
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/sh
echo "pre-commitフックを実行中..."

# JavaScriptファイルにconsole.logが含まれていないかチェック
if git diff --cached --name-only | grep '\.js$' | xargs grep -l 'console\.log' 2>/dev/null; then
    echo "エラー: console.logが含まれているファイルがあります"
    exit 1
fi

echo "チェック完了"
exit 0
EOF

# 実行権限を付与(macOS/Linux)
chmod +x .git/hooks/pre-commit

Windowsの場合、Git Bashを使用するか、以下のようにPowerShellでファイルを作成します。

1
2
3
4
5
6
# PowerShellでpre-commitフックを作成
@"
#!/bin/sh
echo "pre-commitフックを実行中..."
exit 0
"@ | Out-File -FilePath .git/hooks/pre-commit -Encoding utf8 -NoNewline

commit-msgフックの作成

commit-msgフックは、コミットメッセージが入力された後に実行されます。コミットメッセージの形式を検証するのに使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# commit-msgフックファイルを作成
cat > .git/hooks/commit-msg << 'EOF'
#!/bin/sh
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# コミットメッセージが10文字未満の場合はエラー
if [ ${#COMMIT_MSG} -lt 10 ]; then
    echo "エラー: コミットメッセージは10文字以上必要です"
    exit 1
fi

exit 0
EOF

chmod +x .git/hooks/commit-msg

pre-pushフックの作成

pre-pushフックは、git pushコマンドの実行前にトリガーされます。プッシュ前のテスト実行などに使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# pre-pushフックファイルを作成
cat > .git/hooks/pre-push << 'EOF'
#!/bin/sh
echo "pre-pushフックを実行中..."

# テストを実行
npm test
if [ $? -ne 0 ]; then
    echo "エラー: テストが失敗しました。プッシュを中止します"
    exit 1
fi

echo "テスト完了"
exit 0
EOF

chmod +x .git/hooks/pre-push

Git Hooksを直接設定する場合の課題

.git/hooks/ディレクトリにフックを直接配置する方法には、以下の課題があります。

  • バージョン管理されない: .git/ディレクトリはGitの管理対象外のため、チームメンバーと共有できません
  • 手動セットアップが必要: 新しいメンバーは手動でフックをコピーする必要があります
  • 環境依存: シェルスクリプトはWindowsとUnix系で互換性の問題が生じることがあります

これらの課題を解決するために、huskyというツールが広く使われています。

huskyによるGit Hooksの管理

huskyは、Git Hooksをnpmパッケージとして管理し、チーム全体で共有するためのツールです。Gitのcore.hooksPath機能を活用して、プロジェクトディレクトリ内のフックファイルを使用します。

huskyの特徴

huskyには以下の特徴があります。

  • 軽量: gzip圧縮で約2KBと非常に軽量
  • 高速: 約1ミリ秒で実行される高いパフォーマンス
  • クロスプラットフォーム: Windows、macOS、Linuxで動作
  • バージョン管理可能: .husky/ディレクトリはGitで管理できる
  • ゼロ依存: 外部パッケージへの依存がない

huskyのインストールと初期設定

huskyをプロジェクトに導入する手順を説明します。

1
2
3
4
5
# huskyをdev dependencyとしてインストール
npm install --save-dev husky

# huskyを初期化(推奨)
npx husky init

husky initコマンドを実行すると、以下の処理が自動的に行われます。

  1. .husky/ディレクトリの作成
  2. .husky/pre-commitファイルの作成(デフォルトでnpm testを実行)
  3. package.jsonprepareスクリプトにhuskyを追加

実行後のディレクトリ構造は以下のようになります。

プロジェクト/
├── .husky/
│   └── pre-commit     # pre-commitフックスクリプト
├── package.json       # prepareスクリプトが追加される
└── ...

huskyのpre-commitフック設定

husky initで作成された.husky/pre-commitファイルを編集して、任意の処理を実行できます。

1
2
3
# .husky/pre-commit
npm run lint
npm run format:check

フックを追加する場合は、.husky/ディレクトリに対応するフック名のファイルを作成します。

1
2
3
4
5
# commit-msgフックを追加(Linux/macOS)
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg

# pre-pushフックを追加
echo 'npm test' > .husky/pre-push

Windowsの場合は以下のようにします。

1
2
3
4
5
# PowerShellでcommit-msgフックを追加
"npx --no -- commitlint --edit `$1" | Out-File -FilePath .husky/commit-msg -Encoding utf8

# pre-pushフックを追加
"npm test" | Out-File -FilePath .husky/pre-push -Encoding utf8

huskyフックの動作確認

フックが正しく設定されたか確認するには、実際にコミットを試みます。

1
2
3
4
5
6
# テスト用ファイルを作成してステージング
echo "test" > test.txt
git add test.txt

# コミットを実行(pre-commitフックがトリガーされる)
git commit -m "test commit"

期待される結果として、.husky/pre-commitに記述したコマンドが実行されます。コマンドが失敗すると、コミットは中止されます。

huskyフックのスキップ方法

緊急時にフックをスキップしたい場合は、--no-verify(または-n)オプションを使用します。

1
2
3
4
5
# pre-commitとcommit-msgフックをスキップしてコミット
git commit -m "緊急修正" --no-verify

# pre-pushフックをスキップしてプッシュ
git push --no-verify

環境変数HUSKY=0を設定することで、一時的にすべてのフックを無効化することも可能です。

1
2
3
4
5
6
7
# 一時的にフックを無効化(Linux/macOS)
HUSKY=0 git commit -m "フック無効で実行"

# 複数コマンドでフックを無効化
export HUSKY=0
git rebase -i HEAD~3
unset HUSKY

CI/Docker環境でのhusky設定

CI環境やDockerコンテナではGit Hooksが不要な場合があります。HUSKY=0環境変数を設定することで、フックのインストールと実行をスキップできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# GitHub Actionsでの設定例
name: CI
on: [push, pull_request]
env:
  HUSKY: 0
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

また、本番環境(NODE_ENV=production)や依存関係のみをインストールする場合に対応するため、prepareスクリプトを以下のように修正することも有効です。

1
2
3
4
5
{
  "scripts": {
    "prepare": "husky || true"
  }
}

lint-stagedによるステージファイルの自動チェック

lint-stagedは、Gitでステージされたファイルのみを対象にリンターやフォーマッターを実行するツールです。プロジェクト全体ではなく、変更されたファイルのみを対象とすることで、処理時間を大幅に短縮できます。

lint-stagedの特徴

lint-stagedには以下の特徴があります。

  • 高速: ステージされたファイルのみを対象とするため、処理が高速
  • 柔軟な設定: globパターンでファイルをフィルタリング可能
  • 複数コマンド実行: 1つのファイルタイプに対して複数のコマンドを順次実行可能
  • 自動ステージング: フォーマット後の変更を自動的にステージに追加

lint-stagedのインストール

lint-stagedをプロジェクトにインストールします。

1
2
# lint-stagedをインストール
npm install --save-dev lint-staged

lint-stagedの設定方法

lint-stagedの設定は、package.json内に記述するか、専用の設定ファイルを作成します。

package.jsonでの設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "prepare": "husky"
  },
  "devDependencies": {
    "husky": "^9.0.0",
    "lint-staged": "^16.0.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,css,scss}": [
      "prettier --write"
    ]
  }
}

専用設定ファイルでの設定

.lintstagedrc.jsonファイルを作成して設定することも可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "prettier --write"
  ],
  "*.{json,md,css,scss}": [
    "prettier --write"
  ],
  "*.py": [
    "black",
    "flake8"
  ]
}

ESモジュール形式で記述する場合は、lint-staged.config.mjsを使用します。

1
2
3
4
5
// lint-staged.config.mjs
export default {
  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
  '*.{json,md,css,scss}': ['prettier --write'],
}

huskyとlint-stagedの連携

huskyとlint-stagedを連携させて、コミット時に自動でリント・フォーマットを実行する設定を行います。

1
2
# .husky/pre-commitを編集してlint-stagedを実行
echo 'npx lint-staged' > .husky/pre-commit

これにより、git commit実行時に以下の処理が自動的に行われます。

  1. ステージされたファイルの一覧を取得
  2. globパターンにマッチするファイルを抽出
  3. 設定されたコマンドを順次実行(ESLint、Prettier等)
  4. コマンドが成功すれば、変更を自動的にステージに追加
  5. いずれかのコマンドが失敗すれば、コミットを中止

lint-stagedの設定パターン

lint-stagedの設定パターンをいくつか紹介します。

基本的なJavaScript/TypeScriptプロジェクト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "*.{css,scss,less}": [
      "stylelint --fix",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml}": [
      "prettier --write"
    ]
  }
}

型チェックを含む設定

TypeScriptの型チェックは、個別のファイルではなくプロジェクト全体で行う必要があります。以下のように設定します。

1
2
3
4
5
// lint-staged.config.mjs
export default {
  '*.{ts,tsx}': () => 'tsc --noEmit',  // 引数なしでtscを実行
  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
}

ファイルパスに基づく除外設定

特定のディレクトリを除外する場合は、globパターンの否定(!)を使用します。

1
2
3
4
5
6
7
{
  "lint-staged": {
    "!(node_modules|dist|build)/**/*.{js,ts}": [
      "eslint --fix"
    ]
  }
}

lint-stagedの実行結果

lint-stagedが正常に動作すると、以下のような出力が表示されます。

✔ Backed up original state in git stash
❯ Running tasks for staged files...
  ❯ package.json — 3 files
    ❯ *.{js,jsx,ts,tsx} — 2 files
      ✔ eslint --fix
      ✔ prettier --write
    ❯ *.{json,md} — 1 file
      ✔ prettier --write
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...

エラーが発生した場合は、該当するコマンドの出力が表示され、コミットは中止されます。

✖ eslint --fix:
  /path/to/file.js
    1:10  error  'unused' is defined but never used  no-unused-vars

✖ 1 problem (1 error, 0 warnings)

実践的なGit Hooks設定例

ここでは、実際のプロジェクトで使える具体的な設定例を紹介します。

基本的なNode.jsプロジェクトの設定

新規プロジェクトでhusky + lint-staged + ESLint + Prettierを設定する手順です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# プロジェクトを初期化
mkdir my-project && cd my-project
npm init -y
git init

# 必要なパッケージをインストール
npm install --save-dev husky lint-staged eslint prettier @eslint/js

# ESLintを設定
npm init @eslint/config@latest

# huskyを初期化
npx husky init

# pre-commitフックをlint-stagedに変更
echo 'npx lint-staged' > .husky/pre-commit

package.jsonに以下の設定を追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{
  "scripts": {
    "prepare": "husky",
    "lint": "eslint .",
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,css}": [
      "prettier --write"
    ]
  }
}

Conventional Commitsと組み合わせた設定

commitlintと組み合わせて、コミットメッセージの形式も検証する設定です。

1
2
3
4
5
6
7
8
# commitlintをインストール
npm install --save-dev @commitlint/cli @commitlint/config-conventional

# commitlint設定ファイルを作成
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs

# commit-msgフックを追加
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg

この設定により、以下のようなConventional Commits形式のメッセージのみが許可されます。

feat: ユーザー認証機能を追加
fix: ログイン時のエラーハンドリングを修正
docs: READMEにインストール手順を追記

pre-pushでテストを実行する設定

プッシュ前にテストを実行して、テストが失敗したらプッシュを中止する設定です。

1
2
3
4
5
# pre-pushフックを作成
cat > .husky/pre-push << 'EOF'
npm test
npm run build
EOF

ブランチ名に基づくフックの制御

特定のブランチへのプッシュ時のみテストを実行する設定です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# .husky/pre-push
#!/bin/sh

BRANCH=$(git rev-parse --abbrev-ref HEAD)

# mainまたはdevelopブランチへのプッシュ時のみフルテストを実行
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "develop" ]; then
    echo "Running full test suite..."
    npm test
else
    echo "Skipping full tests for feature branch"
    npm run test:quick
fi

Git Hooks設定時の注意点とベストプラクティス

Git Hooksを効果的に活用するための注意点とベストプラクティスを紹介します。

パフォーマンスへの配慮

pre-commitフックで重い処理を実行すると、コミットのたびに待ち時間が発生します。以下の点に注意してください。

  • lint-stagedを活用: プロジェクト全体ではなく、ステージされたファイルのみを対象にする
  • キャッシュを活用: ESLintの--cacheオプションなど、キャッシュ機能を有効にする
  • 重い処理はpre-pushへ: フルテストやビルドはpre-pushフックに移動する
1
2
3
4
5
6
7
{
  "lint-staged": {
    "*.{js,ts}": [
      "eslint --fix --cache"
    ]
  }
}

エラーメッセージの改善

フックが失敗した際に、開発者が原因を素早く特定できるよう、明確なエラーメッセージを出力しましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# .husky/pre-commit
#!/bin/sh

echo "コード品質チェックを実行中..."

if ! npx lint-staged; then
    echo ""
    echo "============================================"
    echo "コード品質チェックに失敗しました"
    echo "上記のエラーを修正してから再度コミットしてください"
    echo "緊急の場合は: git commit --no-verify"
    echo "============================================"
    exit 1
fi

チーム導入時の段階的アプローチ

既存プロジェクトにGit Hooksを導入する際は、段階的に進めることをお勧めします。

  1. 警告のみ: 最初はエラーを出さず、警告のみを表示する
  2. 段階的に厳格化: 徐々にルールを厳格化していく
  3. ドキュメント整備: チームメンバー向けにセットアップ手順を文書化する
1
2
3
4
5
6
7
{
  "lint-staged": {
    "*.js": [
      "eslint --max-warnings=10"
    ]
  }
}

トラブルシューティング

Git Hooks設定時によくある問題と解決策を紹介します。

フックが実行されない

1
2
3
4
5
6
# Gitのcore.hooksPathを確認
git config core.hooksPath
# .huskyが出力されれば正常

# 設定されていない場合は再設定
npx husky

Windows環境での改行コード問題

Windowsでフックが実行されない場合、改行コードが原因の可能性があります。

1
2
# .gitattributesでフックファイルの改行コードをLFに固定
echo "*.husky/* text eol=lf" >> .gitattributes

Node.jsバージョンマネージャー使用時の問題

nvm、fnmなどのバージョンマネージャーを使用している場合、GUIクライアントからのコミット時にNode.jsが見つからないことがあります。~/.config/husky/init.shにバージョンマネージャーの初期化処理を追加してください。

1
2
3
# ~/.config/husky/init.sh
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

まとめ

本記事では、Git Hooksを活用したコミット前の自動チェック環境の構築方法について解説しました。

Git Hooksの主要なポイントは以下の通りです。

  • pre-commit: コミット前にコードフォーマット・リントを実行
  • commit-msg: コミットメッセージの形式を検証
  • pre-push: プッシュ前にテスト・ビルドを実行
  • husky: Git Hooksをチーム全体で共有するためのツール
  • lint-staged: ステージされたファイルのみを対象に高速チェックを実行

Git Hooksとhusky、lint-stagedを組み合わせることで、コード品質を自動的に維持し、レビュー負荷を軽減できます。まずは基本的なpre-commitフックから始めて、徐々に設定を拡充していくことをお勧めします。

参考リンク