git-commit前にsyntaxチェックする

クライアントhookの機能でcommit前に介入することができます。

commit前に``.git/hooks/pre-commit``があれば、実行されて、結果が0以外の場合は、commitがキャンセルされます。
（引数は渡されません）

``.git/hooks/pre-commit.sample``というサンプルスクリプトがあるのでまずはこれを試してみます。

# pre-commit.sampleを使ってみる

百聞は一見に如かず。とりあえずやってみます。

以下のコマンドで``pre-commit.sample``を``pre-commit``にコピーします。

```bash
$ cp .git/hooks/pre-commit{.sample,}
```

２バイト文字のファイルを作成し、commitしてみます。

```bash
$ touch "ぺとあすか.txt"
$ git add .
$ git commit -m "ファイル追加"
Error: Attempt to add a non-ascii file name.

This can cause problems if you want to work
with people on other platforms.

To be portable it is advisable to rename the file ...

If you know what you are doing you can disable this
check using:

  git config hooks.allownonascii true
$ git rm --cached "ぺとあすか.txt"
$ rm !$
```

何やら怒られてしまいました。（理由は後述）
次は、以下の作業をコミットしてみます。

```bash
$ touch suzaki-nishi.txt
$ git add suzaki-nishi.txt
$ git commit -m "first"
$ echo -e "洲崎西\n\t" >> suzaki-nishi.txt
$ git commit -m "second"
suzaki-nishi.txt:2: trailing whitespace.
+
suzaki-nishi.txt:2: new blank line at EOF.
```

こちらもコミットがキャンセルされてしまうことが確認できました。
どんな仕組みなのかは以下で説明します。

# pre-commit.sampleの説明

サンプルスクリプトには２つの機能があります。
１．ascii文字以外のファイル名のコミットを禁止
２．空白行が含まれるファイルのコミットを禁止
　
4b825dc642cb6eb9a060e54bf8d69288fbee4904
は空のツリーを示す値です。
なので通常はHEAD（現在の最新のコミット）が$aginstにセットされます。
おまじないだとでも考えましょう。

```bash:.git/hook/pre-commit.sample
if git rev-parse --verify HEAD >/dev/null 2>&1
then
  against=HEAD
else
  # Initial commit: diff against an empty tree object
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
```

.git/configの値を取得しています。
空白行を許可していればチェックされません。

```bash:.git/hook/pre-commit.sample
allownonascii=$(git config hooks.allownonascii)
```

ここがファイル名をチェックしているところです。

```bash:.git/hook/pre-commit.sample
  test $(git diff --cached --name-only --diff-filter=A -z $against |
    LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
```

``git-add``された範囲の内容に対して、空白行のみが無いかチェック（--check）しています。
無ければ0が返ります。


```bash:pre-commit.sample
exec git diff-index --check --cached $against --
```

# syntaxチェック

ここからが本題です。
サンプルで``exit 0``以外を返せばよいことがわかったので、
``.git/hooks/pre-commit``に書いてみます。
Perlのsyntaxチェックは``-c``で、``-Mstrict``は``use strict``したことになります。

```bash:.git/hook/pre-commit
#!/bin/sh
if git rev-parse --verify HEAD >/dev/null 2>&1
then
  against=HEAD
else
  # Initial commit: diff against an empty tree object
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Redirect output to stderr.
exec 1>&2

for file in `git diff-index --cached --name-only HEAD`; do
  case "${file##*.}" in
  pl | cgi | pm | t)
    perl -Mstrict -Mwarnings -c $file || exit 1
    ;;
  php)
    php -l $file || exit 1;
    ;;
  *)
    ;;
  esac
done

# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
```

こんな感じでcommit時に自動でチェックできます。

```bash
$ git ci -m third
Global symbol "$undefind_value" requires explicit package name at a.pl line 1.
a.pl had compilation errors.
```


# 参考

git diffをカラー表示 - rochefort's blog
http://rochefort.hatenablog.com/entry/20110811/p1

bash 例解: 第 2 回 bash による初歩のプログラミングの続編
http://www.ibm.com/developerworks/jp/linux/library/l-bash2.html

case 文の使用方法 - UNIX & Linux コマンド・シェルスクリプト リファレンス
http://shellscript.sunone.me/case.html

Git - Git ポリシーの実施例
http://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%E3%81%AE%E5%AE%9F%E6%96%BD%E4%BE%8B

エラーのあるファイルはコミットしない(git編) - 肉とビールとパンケーキ by @sotarok
http://sotarok.hatenablog.com/entry/20090223/1235363846

　Gitのpre-commit.sampleにある4b825dc642cb6eb9a060e54bf8d69288fbee4904とは？ - Qiita
　http://qiita.com/fieldville/items/5518ed98ce38eb9df351