同じgit-hooksをリポジトリ個別指定で使えるようにするスクリプトを書いた

先日、[globalなgit\-hooksを設定して、すべてのリポジトリで共有のhooksを使う](https://qiita.com/ik-fib/items/55edad2e5f5f06b3ddd1)という記事で、git-hooksをグローバルに展開する方法を書きました。

今回は、それとは逆にRepositoryごとに個別にスクリプトを展開することが目的です。

## グローバルなgit-hooksを用意したくないケース

基本的には[グローバルなgit\-hooksの設定](https://qiita.com/ik-fib/items/55edad2e5f5f06b3ddd1)を用意すればいいですが、Repositoryごとに行うかどうかを変えたいということもあります。

例えば、会社のgitリポジトリにコミットする際に、git.emailが個人のものになっていないかチェックするスクリプトは、会社のRepositoryには適用したいですが、個人のRepositoryには適用したくないといった形です。

これを実現するために以下のスクリプトを書きました。

```sh:copy-git-hooks.sh
#!/bin/bash -eu

function echo_usage() {
  echo "usage: ${0} target_git-hooks_name target_git_dir_root_path"
  exit 1
}

if [ $# -ne 2 ]; then
  echo_usage
fi

target_script=$1
target_dir=$2

GIT_ROOT=$(git rev-parse --show-superproject-working-tree --show-toplevel | head -1)
SCRIPT_DIR="${GIT_ROOT}/git-hooks"
if [ ! -e "${SCRIPT_DIR}/${target_script}" ]; then
  echo "${SCRIPT_DIR}/${target_script} not found."
  echo ""
  echo "ls ${SCRIPT_DIR}/*"
  ls ${SCRIPT_DIR}/*
  echo ""
  echo_usage
fi

if [ ! -d "${target_dir}/.git" ]; then
  echo "${target_dir} is not git root dir."
  echo ""
  echo_usage
fi

script_name=$(echo ${target_script} | cut -d "." -f 1)
script_type=$(echo ${target_script} | cut -d "." -f 2)

# まだ対象のhooksファイルがない場合、自動生成
if [ ! -e "${target_dir}/.git/hooks/${script_type}" ]; then
  echo '#!/bin/bash' >>"${target_dir}/.git/hooks/${script_type}"
  echo "" >>"${target_dir}/.git/hooks/${script_type}"
  chmod 700 "${target_dir}/.git/hooks/${script_type}"
fi

ln -s "${SCRIPT_DIR}/${target_script}" "${target_dir}/.git/hooks/__${script_name}"

echo "source \$(dirname \${BASH_SOURCE})/__${script_name}" >>"${target_dir}/.git/hooks/${script_type}"
```

## 使い方

以下のようなディレクトリ構成でスクリプトを用意します。

```text
.
├── README.md
├── git-hooks
│   └── personal_address_not_confirm.pre-commit
└── script
    └── copy-git-hooks.sh
```

ここでgit-hooksディレクトリには、`スクリプトの名前.git-hookのアクション名`という命名規則で、スクリプトのテンプレートを用意しておきます。

たとえば、先程例にあげた「会社のgitリポジトリにコミットする際に、git.emailが個人のものになっていないかチェックするスクリプト」であれば、以下のようになります。

```sh:personal_address_not_confirm.pre-commit
#!/bin/bash

# personal_address_not_confirm
## git configをチェックして、user.emailが個人アドレスになっていないか確認

COMPANY_DOMAIN="example.com"
git config user.email | grep -i ${COMPANY_DOMAIN} > /dev/null
if [ $? -ne 0 ]; then
  echo "git user.email is $(git config user.email)."
  echo "It is not a company email address."
  exit 1
fi
```

これをスクリプトを使って、gitリポジトリに展開するときは、以下のコマンドを実行します。

```sh
./script/copy-git-hooks.sh personal_address_not_confirm.pre-commit /path/to/target-git-repository
```

これを実行することで、ファイル拡張子から自動的にアクション名を取得、`.git/hooks/`以下にスクリプトを配置します。
.git/hooks/にファイルが存在しない場合は、自動作成も行います。

また、配置されるスクリプトは、`__${スクリプト名}`という命名で`.git/hooks/`以下にシンボリックリンクを貼った上で、各アクションのスクリプトからsoruceで呼び出す形になっています。

たとえば、上記のコマンドで　`personal_address_not_confirm.pre-commit` を展開した場合、 `/path/to/target-git-repository/.git/hooks/pre-commit`には以下が書き込まれます。

```sh
#!/bin/bash

source $(dirname ${BASH_SOURCE})/__personal_address_not_confirm
```

そして、`__personal_address_not_confirm`は、`personal_address_not_confirm.pre-commit`へのシンボリックリンクになっています。

```sh
$ ls -la /path/to/target-git-repository/.git/hooks/
drwxr-xr-x  16 user  staff   512  2 18 05:56 ./
drwxr-xr-x  12 user  staff   384  2 18 05:33 ../
lrwxr-xr-x   1 user  staff    92  2 18 05:56 __personal_address_not_confirm@ -> /path/to/script_dir/git-hooks/personal_address_not_confirm.pre-commit
-rwx------   1 user  staff    67  2 18 05:56 pre-commit*
(以下略)
```

これにより、git-hooksスクリプトを機能ごとに分割・一括管理した上で、Repositoryごとに展開できるようになりました。