git 2.9 の core.hooksPath でフックスクリプトの一元管理をやってみた

[git 2.9 の新機能](https://github.com/blog/2188-git-2-9-has-been-released#beautiful-diffs)で core.hooksPath という設定が出来るようになったようだ。

これは何かというと、今まで git のフックスクリプト置き場は `リポジトリルート/.git/hooks` というパスに固定だったのが、`core.hooksPath` を設定することでその場所を変えられるようになったというもののようだ。

指定可能なのは以下の3種類のどれか。

- 絶対パス (e.g. /home/kawaz/.config/git/hooks)
- `~` から辿れるパス (e.g. ~/.config/git/hooks)
- 相対パス（リポジトリルートから） (e.g. `myhooks` が指定してあったら `リポジトリルート/myhooks` の意味)

# 僕の事情：dotfiles のパスが不定なん…

大抵の環境では上述した3種類のパス記述で十分かもしれないが、僕は自分の設定を [kawaz/dotfiles](https://github.com/kawaz/dotfiles) で管理していて、これを各所で clone して使っている。そういった運用の都合上、僕の dotfiles はポータビリティを重視して最近は `XDG Base Directories` (参考[[1]](http://kledgeb.blogspot.jp/2013/04/ubuntu-10-xdg-base-directory.html),[[2]](http://kledgeb.blogspot.jp/2013/04/ubuntu-10-xdg-base-directory.html)) になるだけ準拠した感じに育てています。

で、実際の環境でどうなるかというと clone した先が `~/.dotfiles` や `~/.kawaz/dotfiles` だったり、ケースバイケースで置き場が変わってくるので、hooks ディレクトリの場所が絶対パスでも`~`からのパスでも相対パスでも書けなくなってしまいます。

それだと困るので苦肉の策として以下のようにしています。

- まず `$XDG_CONFIG_HOME/bash/rc/applications/git.sh` (bashrcからsourceされる) が、`$XDG_CACHE_HOME/gitconfig.local` を無ければ作ります。
- `$XDG_CACHE_HOME/gitconfig.local` はスクリプトから作られるので `$XDG_CONFIG_HOME/git/hooks` のパスを解決済みのフルパス状態で `core.hooksPath` にセットする感じのファイルになります。
- 仕上げに `$XDG_CONFIG_HOME/git/config` から `../../cache/gitconfig.local` を include して繋がります。
    - ちなみに僕の環境では `XDG_CONFIG_HOME` と `XDG_CACHE_HOME` の値はそれぞれ `/path/to/dotfiles/{config,cache}` になるようにしているので、`config/git/config` から `cache/gitconfig.local` は決め打ちの相対パスで書けるわけです。

これで無事自前の dotfiles リポジトリ内にポータビリティを保った形で共通フックスクリプトを管理できるようになりました。
めでたしめでたし。

# 練習用フックスクリプト：log.sh

今まで git のフックスクリプトはポータビリティが微妙で使いこなすモチベが無かったのでそっちの資産は全然ない。だけど今回の機能追加でスクリプトが外出し管理できるようになったので今後は充実させてみようかな、と。

とりあえずどんなフックがあってどんな値が渡されてくるのかの調査用に環境変数とコマンド引数をログに取る為の log.sh というスクリプトを作ってみました。

```bash:config/git/hooks/log.sh
#!/bin/bash
quoted=()
for t in "$0" "$@"; do
  quoted+=("$(printf %q "$t")")
done

log="${TMPDIR:-/tmp}/git-hooks-log.$(date +%w)"
find "$log" -maxdepth 0 -mtime +1 -delete 2>/dev/null
{
  ts=$(date +%Y-%m-%dT%H:%M:%S.%3N%z)
  printf "%s cd %q && " "$ts" "$PWD"
  for e in $(compgen -e GIT_); do
    printf '%q=%q ' "$e" "${!e}"
  done
  printf "%s\n" "${quoted[*]}"
} >> "$log"

# 作業のじゃまをしないようキッチリ0で終了する
exit 0
```

こいつは何をするかというと `$TMPDIR/git-hooks-log.[0-6]` というファイル名にフックスクリプトがどう呼ばれたかのログを取ります。（ファイル名の数字部分は曜日です、追記前に1日以上古いログだったら削除してるので1周間で雑にローテされます）

で、とりあえずこいつを全てのフックスクリプトから symlink してみます。フックスクリプト名は [githooks ドキュメント](https://git-scm.com/docs/githooks/) からクライアント側フック名を全部コピペで列挙してきました。

```bash:全てのフックをlog.shで受けてみる
cd "$XDG_CONFIG_HOME/git/hooks"
for f in applypatch-msg pre-applypatch post-applypatch pre-commit prepare-commit-msg commit-msg post-commit pre-rebase post-checkout post-merge pre-push; do
  ln -sfn log.sh $f
done
```

で、おもむろに何かコミットしてみたりプッシュしてみたりします。すると↓こんなログが出ます。

```log:$TMPDIR/git-hooks-log.4
2016-06-16T00:15:04.972+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1465999617\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_INDEX_FILE=.git/index GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/pre-commit
2016-06-16T00:15:04.993+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1465999617\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_INDEX_FILE=.git/index GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/prepare-commit-msg .git/COMMIT_EDITMSG commit HEAD
2016-06-16T00:22:25.959+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1465999617\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_INDEX_FILE=.git/index GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/commit-msg .git/COMMIT_EDITMSG
2016-06-16T00:22:25.987+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1465999617\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_INDEX_FILE=.git/index GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/post-commit
2016-06-16T00:22:26.012+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1465999617\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/post-rewrite amend
2016-06-16T00:23:14.834+0900 cd /Users/kawaz/.dotfiles && GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/pre-push origin git@github.com:kawaz/dotfiles.git
2016-06-16T01:04:17.115+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1466006657\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_EDITOR=: GIT_INDEX_FILE=/Users/kawaz/.dotfiles/.git/next-index-72309.lock GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/pre-commit
2016-06-16T01:04:17.132+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1466006657\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_EDITOR=: GIT_INDEX_FILE=/Users/kawaz/.dotfiles/.git/next-index-72309.lock GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/prepare-commit-msg .git/COMMIT_EDITMSG message
2016-06-16T01:04:17.147+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1466006657\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_EDITOR=: GIT_INDEX_FILE=/Users/kawaz/.dotfiles/.git/next-index-72309.lock GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/commit-msg .git/COMMIT_EDITMSG
2016-06-16T01:04:17.164+0900 cd /Users/kawaz/.dotfiles && GIT_AUTHOR_DATE=@1466006657\ +0900 GIT_AUTHOR_EMAIL=kawazzz@gmail.com GIT_AUTHOR_NAME=Yoshiaki\ Kawazu GIT_DIR=.git GIT_EDITOR=: GIT_INDEX_FILE=.git/index GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/post-commit
2016-06-16T01:04:21.806+0900 cd /Users/kawaz/.dotfiles && GIT_PREFIX='' /Users/kawaz/.dotfiles/config/git/hooks/pre-push origin git@github.com:kawaz/dotfiles.git
```

ローカルのリポジトリパス、`GIT_*` な環境変数、呼ばれたフックスクリプト名、とその引数、がいい感じでログに出るようになりました。諸々エスケープも抜かり無い筈なのてコピペで再実行も簡単にできます。
今後はこれの情報を参考に便利そうなフックスクリプトを作っていきたいなと思ってます。

## フックスクリプトの呼び出しログの閲覧コマンドも作っとこう

折角フックスクリプトの呼び出しログが綺麗に出せるようになったのに、出力ファイル名がローテ考慮してるせいで cat しにくいのがいまいち。そこでそれも簡単に見られるよう `hooks-log` というエイリアスを定義してみました。

```config/git/config
[alias]
  # see config/git/hooks/log.sh
  hooks-log = !"ls -rt \"${TMPDIR:-/tmp}\"/git-hooks-log.[0-6]|while read -r f;do [[ -f $f ]] && cat \"$f\";done"
```

これで `git hooks-log` を実行すればローテファイルを古い順に cat してくれるので最新ファイルを探す必要もなくログがみれるようになりました。便利。