Git hooksを使ってコミット時のルールを共有したい

タイトルの状況で色々と調べた内容をまとめました。
この辺を何もかも忘れているだろう数ヶ月後の自分に向けて書いています。

# 状況
<dl>
<dt>やりたいこと</dt>
<dd>READMEなど、無断で変更されると困るファイルが誤って更新されてしまうことを防ぐために、<strong>特定のファイルをコミットしようとした時はエラーを出すようにしたい</strong></dd>

<dt>使用OS</dt>
<dd>macOS 12.6</dd>
</dl>


# 手順
## コミット時のルールを決めたい(一人用)
(前提: `git init`等でGitリポジトリが作成されている)
1. プロジェクトのディレクトリ配下にある「.git」ディレクトリに移動
(隠しディレクトリなので、表示されない時は「cmd + shift + .」キー押下で表示する)
2. 「hooks」ディレクトリ内にある pre-commitファイルを開く
3. 上記ファイルにルールを書き込む
4. pre-commitファイルに実行権限を付与する

## コミット時のルールを共有したい(複数名向け)
1. テンプレートとする pre-commitファイルを用意し、メンバー間で共有する
2. 1のファイルを各自が任意のディレクトリに配置
3. コマンドを使用して、1のファイルをGitテンプレート (後述) に指定
4. 既存の pre-commitファイルを削除
5. Git管理するリポジトリで`git init`

4, 5 はプロジェクト内に既にpre-commitファイルが存在する場合にのみ必要です。

個人的には、2 もルール化して、3 のコマンドまでチーム内で共通にすると、設定ミスの可能性が減って より安全になるかと思います。
- - -
ここからは、調べている時に よく分からなかった部分のメモになります。

# そもそもGit hooksって何？
タイトルにある「Git hooks」(Gitフック)は、Gitでの操作に関連して実行されるスクリプトです。クライアントサイド・サーバーサイドともに、使用するタイミングに応じていくつか種類があります。 

雑な認識だと、「『コミット直前のミス探し』とか、『チェックアウト直後には特定のファイルの編集しないといけない』とか、そういうの自分で作業するの~~面倒~~大変だし、自動で処理してもらおうぜ」的なものです。(人間なので見落としもありますしね…)

今回はそのうちの一つ、**pre-commitフック**を使用して、コミットしようとした時に自動的にファイル名のチェックを行ってもらおうと思います。


# コミット時のルールを決める
今回は『CannotCommitActivity』という名前のKotlinファイルをコミットされないように設定していきます。

上述の手順にあるように、プロジェクトのルートディレクトリ配下にある「.git」ディレクトリに pre-commitフックを配置します。

作成した pre-commitフックはこちら。
```bash: pre-commit
#!/bin/sh

# 特定ファイルのコミットを禁止する


# 変更対象となっているファイル(パスを含む)の一覧を取得
pathes=$(git diff --cached --name-only)


# 一覧として取得したpathesの各要素を見ていく
for path in ${pathes[@]}; do

  # 変更対象のファイル名だけを取得
  filename=$(basename ${path});

  # ファイル名が「CannotCommitActivity.kt」であればコミット処理を中断する
  if [ "$filename" = "CannotCommitActivity.kt" ]; then
    echo "CannotCommitActivity.ktをコミットしようとしたため、コミットを中断します。";
    exit 1;
  else
    echo "コミット可能なファイルです。";
  fi

done
```
~~ファイル名ベタ打ちという何とも大雑把で決め打ちな方法ではありますが、~~ これで該当のファイルのコミットは防止できます。

コミット抑止をSourceTreeで見ると以下のような出力になります。
<img width="95%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243398/dd39fecb-18c3-4a14-7d2c-8d93ecb0be9f.png">

実際に使う場合は、条件分岐の部分をワイルドカードでの文字列検索にしたり、対象のファイルを配列で保持しておいて検索するものと思います。

# コミット時のルールをチームで共有する
ここからは複数名向けの内容になります。

先ほど作成したルールをチーム内でも共有したいのですが、プロジェクトをクローンしてくるたびに隠しフォルダを開いて同じファイルを配置するのは手間がかかってしまいます。それこそ自動化してほしいです。

そこで、今回はGit hooksのテンプレートを使って、クローン時にクライアント側で pre-commitファイルを自動生成してもらおうと思います。

(Gitでの管理もできはすると思いますが、[Gitの公式サイト](https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック#_クライアントサイドフック)に「*クライアントサイドフックはリポジトリをクローンする際には コピーされません 。*」と書かれていたので今回はやりません。)

# Git hooksのテンプレートって何？
Gitフックが作成される際のテンプレートです。「テンプレートディレクトリ」というディレクトリに入っているファイルを指します。

例えば、テンプレートディレクトリの中に pre-commitファイルが存在する時、これから作成されるGitリポジトリには、自動的に同じ内容の pre-commitファイルが生成されます。

テンプレートディレクトリはコマンドから指定が可能です。

**例**
`git config --local init.templatedir '.git_template'`

上記コマンドを実行すると、Gitの設定ファイルに以下のような設定が書き込まれます。
<img width="95%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3243398/c2c3988f-603f-02eb-19b8-908bf2be1fad.png">

上記は`git init` 実行時 (Gitリポジトリの初期化時)に使用するテンプレートの設定で、ルートディレクトリに置かれた「.git_template」というディレクトリを参照するようになっています。

# 難点
Gitリポジトリ生成時にルールをコピーしてくれるGitテンプレートですが、既存のGitリポジトリに対しては自動生成してくれないという難点があります。
(= 既にクローンしてきているプロジェクトに対しては、いちいち pre-commitファイル使用の設定を行わないといけない)

また、テンプレートディレクトリを更新しても、既にプロジェクト内に存在するGitフックは更新されません。
(= テンプレートとプロジェクトｄコミット時ルールにバージョン違いが生じる可能性がある)

加えて、既に同名のGitフックが存在する場合は、テンプレートでの自動生成が行われません。
(= Gitリポジトリ更新による上書き保存はされない)

既存プロジェクトでテンプレートと同じルールを適用したい場合は、必要に応じて古いGitフックを削除してから、対象のプロジェクトのリポジトリで`git init`を行いましょう。

# ちなみに
コミットを禁止できることは確認した上で所属チームに提案しましたが、よりルール共有に適した Gitフックの使い方が見つかったので、そちらが採用されました。
そちらの使い方も調べたので書くかもしれないです。忘れないうちに。

# 参考
[Git のカスタマイズ - Git フック](https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック)
[Git フックの基本的な使い方](https://qiita.com/noraworld/items/c562de68a627ae792c6c)
[Git hooks pre-commitを使って意図しない変更のpushを防ぐ](https://qiita.com/taku-0728/items/2a4709e6e1db7f6e8dbf)
[Gitで特定ファイルのcommitを禁止 (Windows)](https://qiita.com/pilot/items/b53190b6b6a6163b2850)
[git-init　テンプレートディレクトリ](https://git-scm.com/docs/git-init/ja#_テンプレートディレクトリ)