Git Hooksでgit cloneだけをフックする

## やりたいこと
言わば、`post-clone`フックです。`git clone`実行後だけリポジトリディレクトリに対して特定の処理がしたい。その際、clone先ディレクトリに応じて処理を分岐させたい。つまり、`git clone --template`ではうまくいかないケース。

## 結論
workaround的なもので、他のフックとは違ってスクリプトファイル1つでお終いというものではないのです。また、Bashの例ですが、他のシェルでも考え方は流用できると思います。

- `trap DEBUG`で`git clone`を叩いたことを検知して、コマンド実行前にフラグを立てる。
- post-checkoutスクリプト内でフラグが立っているときだけ処理を行い、処理後フラグを倒す。
- 今回の記事の本筋ではないが、注意点として、GitHooksは標準入力を利用しないので、処理の中で入力を待ちたい場合はターミナルからリダイレクションする必要あり。

## 背景
[前の記事](https://qiita.com/KeyAmam/items/f9291bb125ee94b52b78)で、gitアカウントを自動で切り替えるために`git clone`前に処理をする必要があったので、`cd`と`git`のラッパー関数を作り、エイリアスで元の`cd`と`git`を上書きして、`cd`をフックに`git`コマンドが使用する変数を切り替えるようにしました。しかし、何らかのプラグインを利用して`cd`以外でディレクトリ移動するようなケースを考えるとやはり汎用性に欠け、取り急ぎとはいえ筋が良くなかったので、潔く`git clone`を直接フックすることにしました。

意外にも`git clone`だけをフックする方法に関する記事を見かけなかったので書きました。記事がないということは全く必要とされていないのかも。

ちなみに、GitHooksとは、というお話は以下を参照。
[git hook はじめの一歩](https://qiita.com/mhimuro/items/adfc4e5a168d2d9295ae)
[git hookでできること](https://qiita.com/quattro_4/items/d8a8d498c9f853a445f5)


## 問題
Git Hooksで`git clone`をフックしたいとき、`post-checkout`を使う必要がありますが、これだと`git checkout`のときにも実行されます。

また、clone先ディレクトリに応じて処理を分岐させるので、共通テンプレートを設定するだけでは実現できません。

## 解決策
### 環境
macOS

```bash
sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.5
BuildVersion:	18F132
```

Git

```bash
git --version
git version 2.23.0
```

Bash

```bash
bash --version
GNU bash, version 5.0.11(1)-release (x86_64-apple-darwin18.6.0)
```

### `git clone`の実行を検知する
[Gist: .bash_hooks](https://gist.github.com/KeyAmam/a6afcabc3a724fc4a541aca7629c3ff6)として置いておきました。使い方はGist冒頭に書いてあります。このGistには、ディレクトリ移動の検知や、コマンド実行前だけでなくコマンド実行後に実行される処理もまとめて書いているのですが、今回の記事に関わるポイントは以下の3点です。

- 各コマンドの実行前に`__hooks__before_each_command`が実行されるようにする

```bash
trap '__hooks__before_each_command' DEBUG
```

- `__hooks__before_each_command`内で`BASH_COMMAND`で直前のコマンド名（=叩かれたコマンド名）を取得
- フックがアクティベートされていたら（この上記Gistの最後でアクティベートしてます）`__hooks__set_git_clone_flag`に直前のコマンド名を渡して実行

```bash
function __hooks__before_each_command() {
    local last_command=( $BASH_COMMAND )

    if [ -z "$__HOOKS__ACTIVATED" ]; then
        return
    fi

    # before each command
    __hooks__set_git_clone_flag "${last_command[@]}";

    if [ -z "$__HOOKS__EXECUTING_LINE" ]; then
        return
    fi

    unset __HOOKS__EXECUTING_LINE
    # before each first command of a comamnd line
}
```

- 直前に叩かれたコマンド名が`git clone`だった場合、`__hooks__set_git_clone_flag`内で`__HOOKS__ON_GIT_CLONE`フラグを立てる

```bash
function __hooks__set_git_clone_flag() {
    if [ 'git' = "$1" ] && [ 'clone' = "$2" ]; then
        echo "Setting __HOOKS__ON_GIT_CLONE=true in $filename"
        export __HOOKS__ON_GIT_CLONE=true
    fi
}
```

### `post-checkout`内でフラグをチェックして処理する
[Gist: post-checkout](https://gist.github.com/KeyAmam/6a0e8805c0b6a662adb6bcf8118a089a)として置いておきました。`.gitvariables`というファイルを置いてgitアカウントの情報をロードするようにして、そのファイル自体は`~/.gitignore`に登録してトラックしないようにしてあります。

- ここでフラグをチェックして、フラグが立ってなかったら`exit`してます。

```bash
if [ -z "$__HOOKS__ON_GIT_CLONE" ]; then
    exit
fi
```

- 処理の最後にフラグを倒します。

```bash

unset __HOOKS__ON_GIT_CLONE
```

### GitHooks中でユーザの入力を待ちたいときの注意点
オフトピックですが注意点として、GitHooksは標準入力を利用しないので、処理の中で入力を待ちたい場合はターミナルからリダイレクションする必要があります。ちょっとハマりました。

```bash
read -rp 'Type anything you like:' < /dev/tty
```

## まとめ
コードが散在することと、暗示的にフックすることでハマったりしないように、多用には注意が必要ではありますが、cdとgit上書きより汎用的かつ拡張しやすくなった感はあります。

ちなみに、Bashのコマンドの実行前後をフックする方法の解説は[Chuan Ji: DEBUG trap and PROMPT_COMMAND in Bash](https://jichu4n.com/posts/debug-trap-and-prompt_command-in-bash/)の記事がわかりやすいです。

## 参考
- [Chuan Ji: DEBUG trap and PROMPT_COMMAND in Bash](https://jichu4n.com/posts/debug-trap-and-prompt_command-in-bash/)
- [stackoverflow: How to ask for user input in a Git hook?
](https://stackoverflow.com/questions/45495061/how-to-ask-for-user-input-in-a-git-hook)